diff options
| -rw-r--r-- | sqlite.zig | 292 |
1 files changed, 292 insertions, 0 deletions
| @@ -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 | ||
| 3324 | test "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 | |||
| 3180 | test "sqlite: empty slice" { | 3472 | test "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(); |