summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2022-03-22 00:33:49 +0100
committerGravatar Vincent Rischmann2022-04-03 03:15:38 +0200
commit88e7283d2de4cf820b5d698c804c9ba1a6c8a6c4 (patch)
tree5c42bb9c674ab9ff4b6a0bde68ecec8b12883ac8 /sqlite.zig
parentonly use sqlite3_error_offset if compatible (diff)
downloadzig-sqlite-88e7283d2de4cf820b5d698c804c9ba1a6c8a6c4.tar.gz
zig-sqlite-88e7283d2de4cf820b5d698c804c9ba1a6c8a6c4.tar.xz
zig-sqlite-88e7283d2de4cf820b5d698c804c9ba1a6c8a6c4.zip
add createScalarFunction to create a user-defined scalar function
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig292
1 files changed, 292 insertions, 0 deletions
diff --git a/sqlite.zig b/sqlite.zig
index f6632b6..e27abb5 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -600,6 +600,150 @@ pub const Db = struct {
600 pub fn savepoint(self: *Self, name: []const u8) Savepoint.InitError!Savepoint { 600 pub fn savepoint(self: *Self, name: []const u8) Savepoint.InitError!Savepoint {
601 return Savepoint.init(self, name); 601 return Savepoint.init(self, name);
602 } 602 }
603
604 pub const CreateFunctionFlag = struct {
605 deterministic: bool = true,
606 direct_only: bool = true,
607 };
608
609 /// Creates a scalar SQLite function with the given name.
610 ///
611 /// When the SQLite function is called in a statement, `func` will be called with the input arguments.
612 /// Each SQLite argument is converted to a Zig value according to the following rules:
613 /// * TEXT values can be either sqlite.Text or []const u8
614 /// * BLOB values can be either sqlite.Blob or []const u8
615 /// * INTEGER values can be any Zig integer
616 /// * REAL values can be any Zig float
617 ///
618 /// The return type of the function is converted to a SQLite value according to the same rules but reversed.
619 ///
620 pub fn createScalarFunction(self: *Self, func_name: [:0]const u8, comptime func: anytype, comptime create_flags: CreateFunctionFlag) !void {
621 const Type = @TypeOf(func);
622
623 const fn_info = switch (@typeInfo(Type)) {
624 .Fn => |fn_info| fn_info,
625 else => @compileError("expecting a function"),
626 };
627 if (fn_info.is_generic) @compileError("function can't be generic");
628 if (fn_info.is_var_args) @compileError("function can't be variadic");
629
630 const ArgTuple = std.meta.ArgsTuple(Type);
631
632 var flags: c_int = c.SQLITE_UTF8;
633 if (create_flags.deterministic) {
634 flags |= c.SQLITE_DETERMINISTIC;
635 }
636 if (create_flags.direct_only) {
637 flags |= c.SQLITE_DIRECTONLY;
638 }
639
640 const result = c.sqlite3_create_function_v2(
641 self.db,
642 func_name,
643 fn_info.args.len,
644 flags,
645 null,
646 struct {
647 fn sliceFromValue(sqlite_value: *c.sqlite3_value) []const u8 {
648 const size = @intCast(usize, c.sqlite3_value_bytes(sqlite_value));
649
650 const value = c.sqlite3_value_text(sqlite_value);
651 debug.assert(value != null); // TODO(vincent): how do we handle this properly ?
652
653 return value.?[0..size];
654 }
655
656 fn bindValue(comptime ArgType: type, arg: *ArgType, sqlite_value: *c.sqlite3_value) void {
657 switch (ArgType) {
658 Text => arg.*.data = sliceFromValue(sqlite_value),
659 Blob => arg.*.data = sliceFromValue(sqlite_value),
660 else => switch (@typeInfo(ArgType)) {
661 .Int => |info| if ((info.bits + if (info.signedness == .unsigned) 1 else 0) <= 32) {
662 const value = c.sqlite3_value_int(sqlite_value);
663 arg.* = @intCast(ArgType, value);
664 } else if ((info.bits + if (info.signedness == .unsigned) 1 else 0) <= 64) {
665 const value = c.sqlite3_value_int64(sqlite_value);
666 arg.* = @intCast(ArgType, value);
667 } else {
668 @compileError("integer " ++ @typeName(ArgType) ++ " is not representable in sqlite");
669 },
670 .Float => {
671 const value = c.sqlite3_value_double(sqlite_value);
672 arg.* = @floatCast(ArgType, value);
673 },
674 .Bool => {
675 const value = c.sqlite3_value_int(sqlite_value);
676 arg.* = value > 0;
677 },
678 .Pointer => |ptr| switch (ptr.size) {
679 .Slice => switch (ptr.child) {
680 u8 => arg.* = sliceFromValue(sqlite_value),
681 else => @compileError("cannot use an argument of type " ++ @typeName(ArgType)),
682 },
683 else => @compileError("cannot use an argument of type " ++ @typeName(ArgType)),
684 },
685 else => @compileError("cannot use an argument of type " ++ @typeName(ArgType)),
686 },
687 }
688 }
689
690 fn setResult(ctx: ?*c.sqlite3_context, result: anytype) void {
691 const ResultType = @TypeOf(result);
692
693 switch (ResultType) {
694 Text => c.sqlite3_result_text(ctx, result.data.ptr, @intCast(c_int, result.data.len), c.SQLITE_TRANSIENT),
695 Blob => c.sqlite3_result_blob(ctx, result.data.ptr, @intCast(c_int, result.data.len), c.SQLITE_TRANSIENT),
696 else => switch (@typeInfo(ResultType)) {
697 .Int => |info| if ((info.bits + if (info.signedness == .unsigned) 1 else 0) <= 32) {
698 c.sqlite3_result_int(ctx, result);
699 } else if ((info.bits + if (info.signedness == .unsigned) 1 else 0) <= 64) {
700 c.sqlite3_result_int64(ctx, result);
701 } else {
702 @compileError("integer " ++ @typeName(ResultType) ++ " is not representable in sqlite");
703 },
704 .Float => c.sqlite3_result_double(ctx, result),
705 .Bool => c.sqlite3_result_int(ctx, if (result) 1 else 0),
706 .Array => |arr| switch (arr.child) {
707 u8 => c.sqlite3_result_blob(ctx, &result, arr.len, c.SQLITE_TRANSIENT),
708 else => @compileError("cannot use a result of type " ++ @typeName(ResultType)),
709 },
710 .Pointer => |ptr| switch (ptr.size) {
711 .Slice => switch (ptr.child) {
712 u8 => c.sqlite3_result_text(ctx, result.ptr, @intCast(c_int, result.len), c.SQLITE_TRANSIENT),
713 else => @compileError("cannot use a result of type " ++ @typeName(ResultType)),
714 },
715 else => @compileError("cannot use a result of type " ++ @typeName(ResultType)),
716 },
717 else => @compileError("cannot use a result of type " ++ @typeName(ResultType)),
718 },
719 }
720 }
721
722 fn xFunc(ctx: ?*c.sqlite3_context, argc: c_int, argv: [*c]?*c.sqlite3_value) callconv(.C) void {
723 debug.assert(argc == fn_info.args.len);
724
725 const sqlite_args = argv.?[0..fn_info.args.len];
726
727 var fn_args: ArgTuple = undefined;
728 inline for (fn_info.args) |arg, i| {
729 const ArgType = arg.arg_type.?;
730
731 bindValue(ArgType, &fn_args[i], sqlite_args[i].?);
732 }
733
734 const result = @call(.{}, func, fn_args);
735
736 setResult(ctx, result);
737 }
738 }.xFunc,
739 null,
740 null,
741 null,
742 );
743 if (result != c.SQLITE_OK) {
744 return errors.errorFromResultCode(result);
745 }
746 }
603}; 747};
604 748
605/// Savepoint is a helper type for managing savepoints. 749/// Savepoint is a helper type for managing savepoints.
@@ -3177,6 +3321,154 @@ test "sqlite: one with all named parameters" {
3177 try testing.expectEqual(@as(usize, 20), id.?); 3321 try testing.expectEqual(@as(usize, 20), id.?);
3178} 3322}
3179 3323
3324test "sqlite: create scalar function" {
3325 var db = try getTestDb();
3326 defer db.deinit();
3327
3328 {
3329 try db.createScalarFunction(
3330 "myInteger",
3331 struct {
3332 fn run(input: u16) u16 {
3333 return input * 2;
3334 }
3335 }.run,
3336 .{},
3337 );
3338
3339 const result = try db.one(usize, "SELECT myInteger(20)", .{}, .{});
3340
3341 try testing.expect(result != null);
3342 try testing.expectEqual(@as(usize, 40), result.?);
3343 }
3344
3345 {
3346 try db.createScalarFunction(
3347 "myInteger64",
3348 struct {
3349 fn run(input: i64) i64 {
3350 return @intCast(i64, input) * 2;
3351 }
3352 }.run,
3353 .{},
3354 );
3355
3356 const result = try db.one(usize, "SELECT myInteger64(20)", .{}, .{});
3357
3358 try testing.expect(result != null);
3359 try testing.expectEqual(@as(usize, 40), result.?);
3360 }
3361
3362 {
3363 try db.createScalarFunction(
3364 "myMax",
3365 struct {
3366 fn run(a: f64, b: f64) f64 {
3367 return std.math.max(a, b);
3368 }
3369 }.run,
3370 .{},
3371 );
3372
3373 const result = try db.one(f64, "SELECT myMax(2.0, 23.4)", .{}, .{});
3374
3375 try testing.expect(result != null);
3376 try testing.expectEqual(@as(f64, 23.4), result.?);
3377 }
3378
3379 {
3380 try db.createScalarFunction(
3381 "myBool",
3382 struct {
3383 fn run() bool {
3384 return true;
3385 }
3386 }.run,
3387 .{},
3388 );
3389
3390 const result = try db.one(bool, "SELECT myBool()", .{}, .{});
3391
3392 try testing.expect(result != null);
3393 try testing.expectEqual(true, result.?);
3394 }
3395
3396 {
3397 try db.createScalarFunction(
3398 "mySlice",
3399 struct {
3400 fn run() []const u8 {
3401 return "foobar";
3402 }
3403 }.run,
3404 .{},
3405 );
3406
3407 const result = try db.oneAlloc([]const u8, testing.allocator, "SELECT mySlice()", .{}, .{});
3408 try testing.expect(result != null);
3409 try testing.expectEqualStrings("foobar", result.?);
3410 testing.allocator.free(result.?);
3411 }
3412
3413 {
3414 const Blake3 = std.crypto.hash.Blake3;
3415
3416 var expected_hash: [Blake3.digest_length]u8 = undefined;
3417 Blake3.hash("hello", &expected_hash, .{});
3418
3419 try db.createScalarFunction(
3420 "blake3",
3421 struct {
3422 fn run(input: []const u8) [std.crypto.hash.Blake3.digest_length]u8 {
3423 var hash: [Blake3.digest_length]u8 = undefined;
3424 Blake3.hash(input, &hash, .{});
3425 return hash;
3426 }
3427 }.run,
3428 .{},
3429 );
3430
3431 const hash = try db.one([Blake3.digest_length]u8, "SELECT blake3('hello')", .{}, .{});
3432
3433 try testing.expect(hash != null);
3434 try testing.expectEqual(expected_hash, hash.?);
3435 }
3436
3437 {
3438 try db.createScalarFunction(
3439 "myText",
3440 struct {
3441 fn run() Text {
3442 return Text{ .data = "foobar" };
3443 }
3444 }.run,
3445 .{},
3446 );
3447
3448 const result = try db.oneAlloc(Text, testing.allocator, "SELECT myText()", .{}, .{});
3449 try testing.expect(result != null);
3450 try testing.expectEqualStrings("foobar", result.?.data);
3451 testing.allocator.free(result.?.data);
3452 }
3453
3454 {
3455 try db.createScalarFunction(
3456 "myBlob",
3457 struct {
3458 fn run() Blob {
3459 return Blob{ .data = "barbaz" };
3460 }
3461 }.run,
3462 .{},
3463 );
3464
3465 const result = try db.oneAlloc(Blob, testing.allocator, "SELECT myBlob()", .{}, .{});
3466 try testing.expect(result != null);
3467 try testing.expectEqualStrings("barbaz", result.?.data);
3468 testing.allocator.free(result.?.data);
3469 }
3470}
3471
3180test "sqlite: empty slice" { 3472test "sqlite: empty slice" {
3181 var arena = std.heap.ArenaAllocator.init(testing.allocator); 3473 var arena = std.heap.ArenaAllocator.init(testing.allocator);
3182 defer arena.deinit(); 3474 defer arena.deinit();