summaryrefslogtreecommitdiff
path: root/helpers.zig
blob: b9a6131058e7eb916a9502a2d4d4ac5224c4b133 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
const std = @import("std");
const debug = std.debug;

const c = @import("c.zig").c;

const Blob = @import("sqlite.zig").Blob;
const Text = @import("sqlite.zig").Text;

/// Sets the result of a function call in the context `ctx`.
///
/// Determines at compile time which sqlite3_result_XYZ function to use based on the type of `result`.
pub fn setResult(ctx: ?*c.sqlite3_context, result: anytype) void {
    const ResultType = @TypeOf(result);

    switch (ResultType) {
        Text => c.sqlite3_result_text(ctx, result.data.ptr, @intCast(result.data.len), c.sqliteTransientAsDestructor()),
        Blob => c.sqlite3_result_blob(ctx, result.data.ptr, @intCast(result.data.len), c.sqliteTransientAsDestructor()),
        else => switch (@typeInfo(ResultType)) {
            .Int => |info| if ((info.bits + if (info.signedness == .unsigned) 1 else 0) <= 32) {
                c.sqlite3_result_int(ctx, result);
            } else if ((info.bits + if (info.signedness == .unsigned) 1 else 0) <= 64) {
                c.sqlite3_result_int64(ctx, result);
            } else {
                @compileError("integer " ++ @typeName(ResultType) ++ " is not representable in sqlite");
            },
            .Float => c.sqlite3_result_double(ctx, result),
            .Bool => c.sqlite3_result_int(ctx, if (result) 1 else 0),
            .Array => |arr| switch (arr.child) {
                u8 => c.sqlite3_result_blob(ctx, &result, arr.len, c.sqliteTransientAsDestructor()),
                else => @compileError("cannot use a result of type " ++ @typeName(ResultType)),
            },
            .Pointer => |ptr| switch (ptr.size) {
                .Slice => switch (ptr.child) {
                    u8 => c.sqlite3_result_text(ctx, result.ptr, @intCast(result.len), c.sqliteTransientAsDestructor()),
                    else => @compileError("cannot use a result of type " ++ @typeName(ResultType)),
                },
                else => @compileError("cannot use a result of type " ++ @typeName(ResultType)),
            },
            else => @compileError("cannot use a result of type " ++ @typeName(ResultType)),
        },
    }
}

/// Sets a type using the provided value.
///
/// Determines at compile time which sqlite3_value_XYZ function to use based on the type `ArgType`.
pub fn setTypeFromValue(comptime ArgType: type, arg: *ArgType, sqlite_value: *c.sqlite3_value) void {
    switch (ArgType) {
        Text => arg.*.data = sliceFromValue(sqlite_value),
        Blob => arg.*.data = sliceFromValue(sqlite_value),
        else => switch (@typeInfo(ArgType)) {
            .Int => |info| if ((info.bits + if (info.signedness == .unsigned) 1 else 0) <= 32) {
                const value = c.sqlite3_value_int(sqlite_value);
                arg.* = @intCast(value);
            } else if ((info.bits + if (info.signedness == .unsigned) 1 else 0) <= 64) {
                const value = c.sqlite3_value_int64(sqlite_value);
                arg.* = @intCast(value);
            } else {
                @compileError("integer " ++ @typeName(ArgType) ++ " is not representable in sqlite");
            },
            .Float => {
                const value = c.sqlite3_value_double(sqlite_value);
                arg.* = @floatCast(value);
            },
            .Bool => {
                const value = c.sqlite3_value_int(sqlite_value);
                arg.* = value > 0;
            },
            .Pointer => |ptr| switch (ptr.size) {
                .Slice => switch (ptr.child) {
                    u8 => arg.* = sliceFromValue(sqlite_value),
                    else => @compileError("cannot use an argument of type " ++ @typeName(ArgType)),
                },
                else => @compileError("cannot use an argument of type " ++ @typeName(ArgType)),
            },
            else => @compileError("cannot use an argument of type " ++ @typeName(ArgType)),
        },
    }
}

fn sliceFromValue(sqlite_value: *c.sqlite3_value) []const u8 {
    const size: usize = @intCast(c.sqlite3_value_bytes(sqlite_value));

    const value = c.sqlite3_value_text(sqlite_value);
    debug.assert(value != null); // TODO(vincent): how do we handle this properly ?

    return value[0..size];
}

// Returns true if the type T has a function named `name`.
pub fn hasFn(comptime T: type, comptime name: []const u8) bool {
    if (!@hasDecl(T, name)) {
        return false;
    }

    const decl = @field(T, name);
    const decl_type = @TypeOf(decl);
    const decl_type_info = @typeInfo(decl_type);

    return switch (decl_type_info) {
        .Fn => true,
        else => false,
    };
}