diff --git a/src/Table.zig b/src/Table.zig index 6f98d60..ec84f86 100644 --- a/src/Table.zig +++ b/src/Table.zig @@ -1,5 +1,6 @@ const std = @import("std"); const fmt = std.fmt; +const log = std.log; const mem = std.mem; const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; @@ -80,7 +81,7 @@ pub fn create( const db_name = args[1]; if (!mem.eql(u8, "main", db_name)) { cb_ctx.setErrorMessage( - "only 'main' db currently supported, got {s}", + "unable to create table in db `{s}`: only `main` db supported", .{db_name}, ); return InitError.UnsupportedDb; @@ -89,8 +90,7 @@ pub fn create( // Use the tmp arena because the schema def is not stored with the table. The data is converted // into a Schema and the Schema is stored. const def = SchemaDef.parse(cb_ctx.arena, args[3..]) catch |e| { - cb_ctx.setErrorMessage("error parsing schema definition: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to parse schema definition", .{}); }; self.allocator = allocator; @@ -103,8 +103,7 @@ pub fn create( self.schema_manager = SchemaManager.init(&self.ctx.base); errdefer self.schema_manager.deinit(); self.schema_manager.table().create(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("error creating columns table: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to create shadow table `{s}_columns`", .{name}); }; self.ctx.schema = self.schema_manager.create( @@ -112,32 +111,31 @@ pub fn create( cb_ctx.arena, &def, ) catch |e| { - cb_ctx.setErrorMessage("error creating schema: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to save schema", .{}); }; self.table_data = TableData.init(&self.ctx.base); errdefer self.table_data.deinit(); self.table_data.table().create(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("error creating tabledata table: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to create shadow table `{s}_tabledata`", .{name}); }; self.blob_manager = BlobManager.init(&self.table_static_arena, &self.ctx.base) catch |e| { - cb_ctx.setErrorMessage("error creating blob manager: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to init blob manager", .{}); }; errdefer self.blob_manager.deinit(); self.blob_manager.table().create(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("error creating blobs table: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to create shadow table `{s}_blobs`", .{name}); }; self.row_group_index = RowGroupIndex.init(&self.ctx); errdefer self.row_group_index.deinit(); self.row_group_index.table().create(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("error creating rowgroupindex table: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg( + e, + "failed to create shadow table `{s}_rowgroupindex`", + .{name}, + ); }; self.pending_inserts = PendingInserts.init( @@ -146,13 +144,15 @@ pub fn create( &self.ctx, &self.table_data, ) catch |e| { - cb_ctx.setErrorMessage("error creating pending inserts: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to init pending inserts", .{}); }; errdefer self.pending_inserts.deinit(); self.pending_inserts.table().create(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("error creating pendinginserts table: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg( + e, + "failed to create shadow table `{s}_pendinginserts`", + .{name}, + ); }; self.row_group_creator = RowGroupCreator.init( @@ -164,8 +164,7 @@ pub fn create( &self.pending_inserts, 10_000, ) catch |e| { - cb_ctx.setErrorMessage("error creating row group creator: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to init row group creator", .{}); }; self.dirty = false; @@ -210,7 +209,10 @@ pub fn connect( const db_name = args[1]; if (!mem.eql(u8, "main", db_name)) { - cb_ctx.setErrorMessage("only 'main' db currently supported, got {s}", .{db_name}); + cb_ctx.setErrorMessage( + "unable to create table in db `{s}`: only `main` db supported", + .{db_name}, + ); return InitError.UnsupportedDb; } @@ -224,37 +226,31 @@ pub fn connect( self.schema_manager = SchemaManager.init(&self.ctx.base); errdefer self.schema_manager.deinit(); self.schema_manager.table().verifyExists(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("columns shadow table does not exist: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "`{s}_columns` shadow table does not exist", .{name}); }; self.ctx.schema = self.schema_manager.load(&self.table_static_arena, cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("error loading schema: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to load schema", .{}); }; self.table_data = TableData.init(&self.ctx.base); errdefer self.table_data.deinit(); self.table_data.table().verifyExists(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("tabledata shadow table does not exist: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "`{s}_tabledata` shadow table does not exist", .{name}); }; self.blob_manager = BlobManager.init(&self.table_static_arena, &self.ctx.base) catch |e| { - cb_ctx.setErrorMessage("error creating blob manager: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to init blob manager", .{}); }; errdefer self.blob_manager.deinit(); self.blob_manager.table().verifyExists(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("blobs shadow table does not exist: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "`{s}_blobs` shadow table does not exist", .{name}); }; self.row_group_index = RowGroupIndex.init(&self.ctx); errdefer self.row_group_index.deinit(); self.row_group_index.table().verifyExists(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("rowgroupindex shadow table does not exist: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "`{s}_rowgroupindex` shadow table does not exist", .{name}); }; self.pending_inserts = PendingInserts.init( @@ -263,13 +259,15 @@ pub fn connect( &self.ctx, &self.table_data, ) catch |e| { - cb_ctx.setErrorMessage("error opening pending inserts: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to init pending inserts", .{}); }; errdefer self.pending_inserts.deinit(); self.pending_inserts.table().verifyExists(cb_ctx.arena) catch |e| { - cb_ctx.setErrorMessage("pendinginserts shadow table does not exist: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg( + e, + "`{s}_pendinginserts` shadow table does not exist", + .{name}, + ); }; self.row_group_creator = RowGroupCreator.init( @@ -281,15 +279,15 @@ pub fn connect( &self.pending_inserts, 10_000, ) catch |e| { - cb_ctx.setErrorMessage("error creating row group creator: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "failed to init row group creator", .{}); }; self.dirty = false; } pub fn disconnect(self: *Self) void { - std.log.debug("disconnecting from table {s}", .{self.ctx.vtabName()}); + log.debug("disconnecting from vtab {s}", .{self.ctx.vtabName()}); + self.row_group_creator.deinit(); self.pending_inserts.deinit(); self.row_group_index.deinit(); @@ -301,33 +299,35 @@ pub fn disconnect(self: *Self) void { } pub fn destroy(self: *Self, cb_ctx: *vtab.CallbackContext) void { + log.debug("destroying vtab {s}", .{self.ctx.vtabName()}); + self.pending_inserts.table().drop(cb_ctx.arena) catch |e| { - std.log.err( - "failed to drop shadow table {s}_pendinginserts: {any}", + log.err( + "failed to drop shadow table {s}_pendinginserts: {!}", .{ self.ctx.vtabName(), e }, ); }; self.row_group_index.table().drop(cb_ctx.arena) catch |e| { - std.log.err( - "failed to drop shadow table {s}_rowgroupindex: {any}", + log.err( + "failed to drop shadow table {s}_rowgroupindex: {!}", .{ self.ctx.vtabName(), e }, ); }; self.blob_manager.table().drop(cb_ctx.arena) catch |e| { - std.log.err( - "failed to drop shadow table {s}_blobs: {any}", + log.err( + "failed to drop shadow table {s}_blobs: {!}", .{ self.ctx.vtabName(), e }, ); }; self.table_data.table().drop(cb_ctx.arena) catch |e| { - std.log.err( - "failed to drop shadow table {s}_tabledata: {any}", + log.err( + "failed to drop shadow table {s}_tabledata: {!}", .{ self.ctx.vtabName(), e }, ); }; self.schema_manager.table().drop(cb_ctx.arena) catch |e| { - std.log.err( - "failed to drop shadow table {s}_columns: {any}", + log.err( + "failed to drop shadow table {s}_columns: {!}", .{ self.ctx.vtabName(), e }, ); }; @@ -344,7 +344,7 @@ pub fn ddl(self: *Self, allocator: Allocator) ![:0]const u8 { } pub fn rename(self: *Self, cb_ctx: *vtab.CallbackContext, new_name: [:0]const u8) !void { - std.log.debug("renaming to {s}", .{new_name}); + log.debug("renaming to {s}", .{new_name}); // TODO savepoint try self.table_data.table().rename(cb_ctx.arena, new_name); @@ -367,33 +367,32 @@ pub fn update( const change_type = change_set.changeType(); if (change_type == .Insert) { rowid.* = self.pending_inserts.insert(cb_ctx.arena, change_set) catch |e| { - cb_ctx.setErrorMessage("failed to add pending insert: {any}", .{e}); - return e; + return cb_ctx.captureErrMsg(e, "error inserting into pending inserts", .{}); }; - self.dirty = true; - return; } if (!self.warned_update_delete_not_supported) { - std.log.warn("stanchion tables do not (yet) support UPDATE or DELETE", .{}); + log.warn("stanchion tables do not (yet) support UPDATE or DELETE", .{}); self.warned_update_delete_not_supported = true; } } -const BestIndexError = error{} || Allocator.Error || vtab.BestIndexError; - pub fn bestIndex( self: *Self, cb_ctx: *vtab.CallbackContext, best_index_info: vtab.BestIndexInfo, -) BestIndexError!void { - try index.chooseBestIndex(cb_ctx.arena, self.ctx.sortKey(), best_index_info); +) !bool { + index.chooseBestIndex(cb_ctx.arena, self.ctx.sortKey(), best_index_info) catch |e| { + return cb_ctx.captureErrMsg(e, "error occurred while choosing the best index", .{}); + }; + // There is always a query solution because a table scan always works + return true; } pub fn begin(_: *Self, _: *vtab.CallbackContext) !void { - std.log.debug("txn begin", .{}); + log.debug("txn begin", .{}); } pub fn sync(self: *Self, cb_ctx: *vtab.CallbackContext) !void { @@ -402,7 +401,7 @@ pub fn sync(self: *Self, cb_ctx: *vtab.CallbackContext) !void { } pub fn commit(self: *Self, cb_ctx: *vtab.CallbackContext) !void { - std.log.debug("txn commit", .{}); + log.debug("txn commit", .{}); if (self.dirty) { try self.pending_inserts.persistNextRowid(cb_ctx.arena); // TODO should this be called in sync so that an error causes the transaction to be @@ -413,32 +412,32 @@ pub fn commit(self: *Self, cb_ctx: *vtab.CallbackContext) !void { } pub fn rollback(self: *Self, cb_ctx: *vtab.CallbackContext) !void { - std.log.debug("txn rollback", .{}); + log.debug("txn rollback", .{}); if (self.dirty) { try self.pending_inserts.loadNextRowid(cb_ctx.arena); } } pub fn savepoint(_: *Self, _: *vtab.CallbackContext, savepoint_id: i32) !void { - std.log.debug("txn savepoint {d} begin", .{savepoint_id}); + log.debug("txn savepoint {d} begin", .{savepoint_id}); } pub fn release(self: *Self, cb_ctx: *vtab.CallbackContext, savepoint_id: i32) !void { - std.log.debug("txn savepoint {d} release", .{savepoint_id}); + log.debug("txn savepoint {d} release", .{savepoint_id}); if (self.dirty) { try self.pending_inserts.persistNextRowid(cb_ctx.arena); } } pub fn rollbackTo(self: *Self, cb_ctx: *vtab.CallbackContext, savepoint_id: i32) !void { - std.log.debug("txn savepoint {d} rollback", .{savepoint_id}); + log.debug("txn savepoint {d} rollback", .{savepoint_id}); if (self.dirty) { try self.pending_inserts.loadNextRowid(cb_ctx.arena); } } pub fn isShadowName(suffix: [:0]const u8) bool { - std.log.debug("checking shadow name: {s}", .{suffix}); + log.debug("checking shadow name: {s}", .{suffix}); inline for (.{ TableData, BlobManager, SchemaManager, RowGroupIndex, PendingInserts }) |st| { if (mem.eql(u8, st.ShadowTable.suffix, suffix)) { return true; @@ -451,7 +450,7 @@ pub fn open( self: *Self, _: *vtab.CallbackContext, ) !Cursor { - std.log.debug("open cursor", .{}); + log.debug("open cursor", .{}); return Cursor.init( self.allocator, &self.blob_manager, @@ -509,25 +508,21 @@ pub const Cursor = struct { _: [:0]const u8, filter_args: vtab.FilterArgs, ) !void { - const best_index = try Index.deserialize(@bitCast(index_id_num), filter_args); + const best_index = Index.deserialize(@bitCast(index_id_num), filter_args) catch |e| { + return cb_ctx.captureErrMsg(e, "failed to deserialize index id", .{}); + }; if (best_index) |idx| { switch (idx) { - .sort_key => |sk_range| { - std.log.debug("cursor begin: using sort key index: {}", .{sk_range}); - self.rg_index_cursor = try self.row_group_index.cursorPartial( - cb_ctx.arena, - sk_range, - ); - self.pend_inserts_cursor = try self.pend_inserts.cursorPartial( - cb_ctx.arena, - sk_range, - ); - }, + .sort_key => |sk_range| try self.beginSortKeyIndex(cb_ctx, sk_range), } } else { - std.log.debug("cursor begin: doing table scan", .{}); - self.rg_index_cursor = try self.row_group_index.cursor(cb_ctx.arena); - self.pend_inserts_cursor = try self.pend_inserts.cursor(cb_ctx.arena); + log.debug("cursor begin: doing table scan", .{}); + self.rg_index_cursor = self.row_group_index.cursor(cb_ctx.arena) catch |e| { + return cb_ctx.captureErrMsg(e, "failed to open row group index cursor", .{}); + }; + self.pend_inserts_cursor = self.pend_inserts.cursor(cb_ctx.arena) catch |e| { + return cb_ctx.captureErrMsg(e, "failed to open pending inserts cursor", .{}); + }; } if (!self.rg_index_cursor.eof()) { @@ -537,6 +532,26 @@ pub const Cursor = struct { self.begun = true; } + fn beginSortKeyIndex( + self: *Cursor, + cb_ctx: *vtab.CallbackContext, + sk_range: index.SortKeyRange, + ) !void { + log.debug("cursor begin: using sort key index: {}", .{sk_range}); + self.rg_index_cursor = self.row_group_index.cursorPartial( + cb_ctx.arena, + sk_range, + ) catch |e| { + return cb_ctx.captureErrMsg(e, "failed to open partial row group index cursor", .{}); + }; + self.pend_inserts_cursor = self.pend_inserts.cursorPartial( + cb_ctx.arena, + sk_range, + ) catch |e| { + return cb_ctx.captureErrMsg(e, "failed to open partial pending inserts cursor", .{}); + }; + } + pub fn eof(self: *Cursor) bool { return self.rg_index_cursor.eof() and self.pend_inserts_cursor.eof(); } diff --git a/src/functions/SegmentInfo.zig b/src/functions/SegmentInfo.zig index 7f4fc71..94cad5a 100644 --- a/src/functions/SegmentInfo.zig +++ b/src/functions/SegmentInfo.zig @@ -9,7 +9,6 @@ const ArenaAllocator = std.heap.ArenaAllocator; const sqlite = @import("../sqlite3.zig"); const Conn = sqlite.Conn; const vtab = sqlite.vtab; -const BestIndexError = vtab.BestIndexError; const BestIndexInfo = vtab.BestIndexInfo; const Result = vtab.Result; @@ -69,7 +68,7 @@ pub fn bestIndex( _: *Self, _: *vtab.CallbackContext, best_index_info: vtab.BestIndexInfo, -) BestIndexError!void { +) !bool { var req_constraints: struct { table_name: bool, segment_id: bool } = .{ .table_name = false, .segment_id = false, @@ -94,9 +93,9 @@ pub fn bestIndex( } if (req_constraints.table_name and req_constraints.segment_id) { - return; + return true; } - return BestIndexError.QueryImpossible; + return false; } pub fn open( diff --git a/src/functions/Segments.zig b/src/functions/Segments.zig index 89534f1..5d2a248 100644 --- a/src/functions/Segments.zig +++ b/src/functions/Segments.zig @@ -9,7 +9,6 @@ const ArenaAllocator = std.heap.ArenaAllocator; const sqlite = @import("../sqlite3.zig"); const Conn = sqlite.Conn; const vtab = sqlite.vtab; -const BestIndexError = vtab.BestIndexError; const BestIndexInfo = vtab.BestIndexInfo; const Result = vtab.Result; @@ -60,7 +59,7 @@ pub fn bestIndex( _: *Self, _: *vtab.CallbackContext, best_index_info: vtab.BestIndexInfo, -) BestIndexError!void { +) !bool { // Find the equality constraint on `table_name` for (0..best_index_info.constraintsLen()) |idx| { const c = best_index_info.constraint(idx); @@ -68,11 +67,11 @@ pub fn bestIndex( if (c.usable() and c.columnIndex() == 4 and c.op() == .eq) { c.setOmitTest(true); c.includeArgInFilter(1); - return; + return true; } } - return BestIndexError.QueryImpossible; + return false; } pub fn open( diff --git a/src/row_group/Cursor.zig b/src/row_group/Cursor.zig index 1892008..f996d84 100644 --- a/src/row_group/Cursor.zig +++ b/src/row_group/Cursor.zig @@ -69,7 +69,11 @@ const Self = @This(); /// Initializes the row group cursor with an undefined row group. Set the row group before /// iterating -pub fn init(allocator: Allocator, blob_manager: *BlobManager, schema: *const Schema) !Self { +pub fn init( + allocator: Allocator, + blob_manager: *BlobManager, + schema: *const Schema, +) Allocator.Error!Self { const col_len = schema.columns.len; var arena = ArenaAllocator.init(allocator); diff --git a/src/sqlite3/ChangeSet.zig b/src/sqlite3/ChangeSet.zig index 4e41f1a..75340fd 100644 --- a/src/sqlite3/ChangeSet.zig +++ b/src/sqlite3/ChangeSet.zig @@ -11,6 +11,14 @@ pub const ChangeType = enum { Insert, Update, Delete, + + pub fn name(self: ChangeType) []const u8 { + return switch (self) { + .Insert => "INSERT", + .Update => "UPDATE", + .Delete => "DELETE", + }; + } }; pub fn init(values: []?*c.sqlite3_value) Self { diff --git a/src/sqlite3/vtab.zig b/src/sqlite3/vtab.zig index a392484..c95c9a1 100644 --- a/src/sqlite3/vtab.zig +++ b/src/sqlite3/vtab.zig @@ -4,6 +4,7 @@ const std = @import("std"); const debug = std.debug; const fmt = std.fmt; const heap = std.heap; +const log = std.log; const mem = std.mem; const meta = std.meta; const testing = std.testing; @@ -16,11 +17,11 @@ const Conn = @import("Conn.zig"); const ValueRef = @import("value.zig").Ref; /// CallbackContext is only valid for the duration of a callback from sqlite into the -/// virtual table instance. It should no tbe saved between calls, and it is provided to +/// virtual table instance. It should not be saved between calls, and it is provided to /// every callback function. pub const CallbackContext = struct { arena: *heap.ArenaAllocator, - error_message: []const u8 = "unknown error", + error_message: []const u8 = "unspecified error", pub fn init(arena: *heap.ArenaAllocator) CallbackContext { return .{ .arena = arena }; @@ -39,6 +40,35 @@ pub const CallbackContext = struct { error.OutOfMemory => "can't set diagnostic message, out of memory", }; } + + pub fn captureErrMsg( + self: *CallbackContext, + err: anytype, + comptime format_string: []const u8, + values: anytype, + ) @TypeOf(err) { + switch (comptime meta.activeTag(@typeInfo(@TypeOf(err)))) { + .ErrorSet => { + self.setErrorMessage(format_string ++ ": {!}", values ++ .{err}); + return err; + }, + else => @compileError("`err` must be an ErrorSet"), + } + } + + pub fn wrapErrorMessage( + self: *CallbackContext, + comptime error_context_msg: []const u8, + values: anytype, + ) void { + self.error_message = fmt.allocPrint( + self.arena.allocator(), + error_context_msg ++ ": {s}", + values ++ .{self.error_message}, + ) catch |err| switch (err) { + error.OutOfMemory => self.error_message, + }; + } }; const ArenaPool = struct { @@ -84,8 +114,6 @@ const ArenaPool = struct { } }; -pub const BestIndexError = error{QueryImpossible}; - pub const BestIndexInfo = struct { index_info: ?*c.sqlite3_index_info, @@ -282,6 +310,15 @@ pub fn VirtualTable(comptime Table: type) type { fn reclaimCbCtx(self: *@This(), cb_ctx: *CallbackContext) void { self.arena_pool.giveBack(cb_ctx.arena); } + + fn setErrorMsg(self: *@This(), err_msg: []const u8) void { + // Any existing error message must be freed before a new one is set + // See: https://www.sqlite.org/vtab.html#implementation + if (self.vtab.zErrMsg != null) { + c.sqlite3_free(self.vtab.zErrMsg); + } + self.vtab.zErrMsg = dupeToSQLiteString(err_msg); + } }; const CursorState = struct { @@ -290,6 +327,10 @@ pub fn VirtualTable(comptime Table: type) type { arena_pool: *ArenaPool, cursor: Table.Cursor, + fn vtabState(self: @This()) *State { + return @fieldParentPtr(State, "vtab", self.vtab_cursor.pVtab); + } + fn cbCtx(self: *@This()) !CallbackContext { const arena = try self.arena_pool.take(self.allocator); return CallbackContext.init(arena); @@ -343,8 +384,8 @@ pub fn VirtualTable(comptime Table: type) type { }; defer state.reclaimCbCtx(&cb_ctx); - const initTableFn = Table.create; - initTableFn(&state.table, allocator, conn, &cb_ctx, args) catch { + Table.create(&state.table, allocator, conn, &cb_ctx, args) catch { + cb_ctx.wrapErrorMessage("failed to create virtual table `{s}`", .{args[2]}); err_str.* = dupeToSQLiteString(cb_ctx.error_message); return c.SQLITE_ERROR; }; @@ -371,8 +412,9 @@ pub fn VirtualTable(comptime Table: type) type { // Ensure the cb_ctx is reclaimed before the rest of the cleanup happens var cb_ctx = state.cbCtx() catch { - // TODO try to allocate error string with sqlite_malloc? - std.log.err("error allocating arena for callback context. out of memory", .{}); + state.setErrorMsg( + "failed to allocate arena for callback context. out of memory", + ); return c.SQLITE_ERROR; }; defer state.reclaimCbCtx(&cb_ctx); @@ -398,7 +440,7 @@ pub fn VirtualTable(comptime Table: type) type { ) callconv(.C) c_int { const state = @fieldParentPtr(State, "vtab", vtab); var cb_ctx = state.cbCtx() catch { - std.log.err("error allocating arena for callback context. out of memory", .{}); + state.setErrorMsg("failed to allocate arena for callback context. out of memory"); return c.SQLITE_ERROR; }; defer state.reclaimCbCtx(&cb_ctx); @@ -406,10 +448,9 @@ pub fn VirtualTable(comptime Table: type) type { const values = ChangeSet.init( @as([*c]?*c.sqlite3_value, @ptrCast(argv))[0..@intCast(argc)], ); - state.table.update(&cb_ctx, @ptrCast(row_id_ptr), values) catch |e| { - std.log.err("error calling update on vtab: {any}", .{e}); - // TODO how to set the error message? - //err_str.* = dupeToSQLiteString(cb_ctx.error_message); + state.table.update(&cb_ctx, @ptrCast(row_id_ptr), values) catch { + cb_ctx.wrapErrorMessage("{s} failed", .{values.changeType().name()}); + state.setErrorMsg(cb_ctx.error_message); return c.SQLITE_ERROR; }; @@ -422,29 +463,29 @@ pub fn VirtualTable(comptime Table: type) type { //! transactions fn xBegin(vtab: [*c]c.sqlite3_vtab) callconv(.C) c_int { - return callTableCallback("begin", Table.begin, .{}, vtab); + return callTableCallback("BEGIN", Table.begin, .{}, vtab); } fn xSync(vtab: [*c]c.sqlite3_vtab) callconv(.C) c_int { - return callTableCallback("sync", Table.sync, .{}, vtab); + return callTableCallback("SYNC", Table.sync, .{}, vtab); } fn xCommit(vtab: [*c]c.sqlite3_vtab) callconv(.C) c_int { - return callTableCallback("commit", Table.commit, .{}, vtab); + return callTableCallback("COMMIT", Table.commit, .{}, vtab); } fn xRollback(vtab: [*c]c.sqlite3_vtab) callconv(.C) c_int { - return callTableCallback("rollback", Table.rollback, .{}, vtab); + return callTableCallback("ROLLBACK", Table.rollback, .{}, vtab); } fn xSavepoint(vtab: [*c]c.sqlite3_vtab, savepoint_id: c_int) callconv(.C) c_int { const sid: i32 = @intCast(savepoint_id); - return callTableCallback("savepoint", Table.savepoint, .{sid}, vtab); + return callTableCallback("SAVEPOINT", Table.savepoint, .{sid}, vtab); } fn xRelease(vtab: [*c]c.sqlite3_vtab, savepoint_id: c_int) callconv(.C) c_int { const sid: i32 = @intCast(savepoint_id); - return callTableCallback("release", Table.release, .{sid}, vtab); + return callTableCallback("RELEASE", Table.release, .{sid}, vtab); } fn xRollbackTo( @@ -452,26 +493,27 @@ pub fn VirtualTable(comptime Table: type) type { savepoint_id: c_int, ) callconv(.C) c_int { const sid: i32 = @intCast(savepoint_id); - return callTableCallback("rollbackTo", Table.rollbackTo, .{sid}, vtab); + return callTableCallback("ROLLBACK TO", Table.rollbackTo, .{sid}, vtab); } fn callTableCallback( - comptime functionName: []const u8, + comptime op_name: []const u8, comptime function: anytype, args: anytype, vtab: [*c]c.sqlite3_vtab, ) c_int { const state = @fieldParentPtr(State, "vtab", vtab); var cb_ctx = state.cbCtx() catch { - std.log.err("error allocating arena for callback context. out of memory", .{}); + state.setErrorMsg("failed to allocate arena for callback context. out of memory"); return c.SQLITE_ERROR; }; defer state.reclaimCbCtx(&cb_ctx); const full_args = .{ &state.table, &cb_ctx } ++ args; - @call(.auto, function, full_args) catch |e| { - std.log.err("error calling {s} on table: {any}", .{ functionName, e }); + @call(.auto, function, full_args) catch { + cb_ctx.wrapErrorMessage(op_name ++ " failed", .{}); + state.setErrorMsg(cb_ctx.error_message); return c.SQLITE_ERROR; }; @@ -483,14 +525,15 @@ pub fn VirtualTable(comptime Table: type) type { fn xRename(vtab: [*c]c.sqlite3_vtab, new_name: [*c]const u8) callconv(.C) c_int { const state = @fieldParentPtr(State, "vtab", vtab); var cb_ctx = state.cbCtx() catch { - std.log.err("error allocating arena for callback context. out of memory", .{}); + state.setErrorMsg("failed to allocate arena for callback context. out of memory"); return c.SQLITE_ERROR; }; defer state.reclaimCbCtx(&cb_ctx); const new_name_checked: [:0]const u8 = std.mem.span(new_name); - state.table.rename(&cb_ctx, new_name_checked) catch |e| { - std.log.err("error calling rename on table: {any}", .{e}); + state.table.rename(&cb_ctx, new_name_checked) catch { + cb_ctx.wrapErrorMessage("rename table to {s} failed", .{new_name_checked}); + state.setErrorMsg(cb_ctx.error_message); return c.SQLITE_ERROR; }; @@ -549,15 +592,14 @@ pub fn VirtualTable(comptime Table: type) type { return false; } - fn setup( - comptime create: bool, + fn xConnect( db: ?*c.sqlite3, module_context_ptr: ?*anyopaque, argc: c_int, argv: [*c]const [*c]const u8, vtab: [*c][*c]c.sqlite3_vtab, err_str: [*c][*c]const u8, - ) c_int { + ) callconv(.C) c_int { const allocator = getModuleAllocator(module_context_ptr).*; var tmp_arena = heap.ArenaAllocator.init(allocator); @@ -590,12 +632,12 @@ pub fn VirtualTable(comptime Table: type) type { }; defer state.reclaimCbCtx(&cb_ctx); - const initTableFn = if (create) Table.create else Table.connect; - initTableFn(&state.table, allocator, conn, &cb_ctx, args) catch { + Table.connect(&state.table, allocator, conn, &cb_ctx, args) catch { + cb_ctx.wrapErrorMessage("failed to connect to virtual table `{s}`", .{args[2]}); err_str.* = dupeToSQLiteString(cb_ctx.error_message); return c.SQLITE_ERROR; }; - errdefer if (create) state.table.destroy() else state.table.disconnect(); + errdefer state.table.disconnect(); const schema = state.table.ddl(cb_ctx.arena.allocator()) catch { err_str.* = dupeToSQLiteString("out of memory"); @@ -611,17 +653,6 @@ pub fn VirtualTable(comptime Table: type) type { return c.SQLITE_OK; } - fn xConnect( - db: ?*c.sqlite3, - module_context_ptr: ?*anyopaque, - argc: c_int, - argv: [*c]const [*c]const u8, - vtab: [*c][*c]c.sqlite3_vtab, - err_str: [*c][*c]const u8, - ) callconv(.C) c_int { - return setup(false, db, module_context_ptr, argc, argv, vtab, err_str); - } - fn xDisconnect(vtab: [*c]c.sqlite3_vtab) callconv(.C) c_int { const state = @fieldParentPtr(State, "vtab", vtab); @@ -632,53 +663,29 @@ pub fn VirtualTable(comptime Table: type) type { return c.SQLITE_OK; } - fn xUpdate( - vtab: [*c]c.sqlite3_vtab, - argc: c_int, - argv: [*c]?*c.sqlite3_value, - row_id_ptr: [*c]c.sqlite3_int64, - ) callconv(.C) c_int { - const state = @fieldParentPtr(State, "vtab", vtab); - var cb_ctx = state.cbCtx() catch { - std.log.err("error allocating arena for callback context. out of memory", .{}); - return c.SQLITE_ERROR; - }; - defer state.reclaimCbCtx(&cb_ctx); - - const values = ChangeSet.init( - @as([*c]?*c.sqlite3_value, @ptrCast(argv))[0..@intCast(argc)], - ); - state.table.update(&cb_ctx, @ptrCast(row_id_ptr), values) catch |e| { - std.log.err("error calling update on vtab: {any}", .{e}); - // TODO how to set the error message? - //err_str.* = dupeToSQLiteString(cb_ctx.error_message); - return c.SQLITE_ERROR; - }; - - return c.SQLITE_OK; - } - fn xBestIndex( vtab: [*c]c.sqlite3_vtab, index_info_ptr: [*c]c.sqlite3_index_info, ) callconv(.C) c_int { const state = @fieldParentPtr(State, "vtab", vtab); var cb_ctx = state.cbCtx() catch { - std.log.err("error allocating arena for callback context. out of memory", .{}); + state.setErrorMsg("failed to allocate arena for callback context. out of memory"); return c.SQLITE_ERROR; }; defer state.reclaimCbCtx(&cb_ctx); const best_index = BestIndexInfo{ .index_info = @ptrCast(index_info_ptr) }; - state.table.bestIndex(&cb_ctx, best_index) catch |e| { - if (e == BestIndexError.QueryImpossible) { - std.log.debug("query not possible with specified constraints", .{}); - return c.SQLITE_CONSTRAINT; - } - std.log.err("error calling bestIndex on table: {any}", .{e}); + const found_solution = state.table.bestIndex(&cb_ctx, best_index) catch { + cb_ctx.wrapErrorMessage("error occurred during query planning", .{}); + state.setErrorMsg(cb_ctx.error_message); return c.SQLITE_ERROR; }; + if (!found_solution) { + log.debug("no query solution with specified constraints", .{}); + return c.SQLITE_CONSTRAINT; + } + return c.SQLITE_OK; } @@ -688,25 +695,27 @@ pub fn VirtualTable(comptime Table: type) type { ) callconv(.C) c_int { const state = @fieldParentPtr(State, "vtab", vtab); var cb_ctx = state.cbCtx() catch { - std.log.err("error allocating arena for callback context. out of memory", .{}); + state.setErrorMsg("failed to allocate arena for callback context. out of memory"); return c.SQLITE_ERROR; }; defer state.reclaimCbCtx(&cb_ctx); const cursor_state = state.allocator.create(CursorState) catch { - std.log.err("out of memory", .{}); + state.setErrorMsg("failed to allocate cursor: out of memory"); return c.SQLITE_ERROR; }; errdefer state.allocator.destroy(cursor_state); + const cursor = state.table.open(&cb_ctx) catch { + cb_ctx.wrapErrorMessage("failed to create vtab cursor", .{}); + state.setErrorMsg(cb_ctx.error_message); + return c.SQLITE_ERROR; + }; cursor_state.* = .{ .vtab_cursor = mem.zeroes(c.sqlite3_vtab_cursor), .allocator = state.allocator, .arena_pool = &state.arena_pool, - .cursor = state.table.open(&cb_ctx) catch |e| { - std.log.err("error creating cursor: {any}", .{e}); - return c.SQLITE_ERROR; - }, + .cursor = cursor, }; vtab_cursor.* = @ptrCast(cursor_state); @@ -727,13 +736,16 @@ pub fn VirtualTable(comptime Table: type) type { const state = @fieldParentPtr(CursorState, "vtab_cursor", vtab_cursor); var cb_ctx = state.cbCtx() catch { - std.log.err("error allocating arena for callback context. out of memory", .{}); + state.vtabState().setErrorMsg( + "failed to allocate arena for callback context. out of memory", + ); return c.SQLITE_ERROR; }; defer state.reclaimCbCtx(&cb_ctx); - state.cursor.begin(&cb_ctx, index_id_num, index_id_str, filter_args) catch |e| { - std.log.err("error calling begin (xFilter) on cursor: {any}", .{e}); + state.cursor.begin(&cb_ctx, index_id_num, index_id_str, filter_args) catch { + cb_ctx.wrapErrorMessage("failed to init vtab cursor with filter", .{}); + state.vtabState().setErrorMsg(cb_ctx.error_message); return c.SQLITE_ERROR; }; @@ -747,8 +759,8 @@ pub fn VirtualTable(comptime Table: type) type { fn xNext(vtab_cursor: [*c]c.sqlite3_vtab_cursor) callconv(.C) c_int { const state = @fieldParentPtr(CursorState, "vtab_cursor", vtab_cursor); - state.cursor.next() catch |e| { - std.log.err("error calling next on cursor: {any}", .{e}); + state.cursor.next() catch { + state.vtabState().setErrorMsg("error in next on vtab cursor"); return c.SQLITE_ERROR; }; return c.SQLITE_OK; @@ -761,8 +773,8 @@ pub fn VirtualTable(comptime Table: type) type { ) callconv(.C) c_int { const state = @fieldParentPtr(CursorState, "vtab_cursor", vtab_cursor); const result = Result{ .ctx = ctx }; - state.cursor.column(result, @intCast(n)) catch |e| { - std.log.err("error calling column on cursor: {any}", .{e}); + state.cursor.column(result, @intCast(n)) catch { + state.vtabState().setErrorMsg("error fetching column value from vtab cursor"); return c.SQLITE_ERROR; }; return c.SQLITE_OK; @@ -773,8 +785,8 @@ pub fn VirtualTable(comptime Table: type) type { rowid_ptr: [*c]c.sqlite3_int64, ) callconv(.C) c_int { const state = @fieldParentPtr(CursorState, "vtab_cursor", vtab_cursor); - rowid_ptr.* = state.cursor.rowid() catch |e| { - std.log.err("error calling rowid on cursor: {any}", .{e}); + rowid_ptr.* = state.cursor.rowid() catch { + state.vtabState().setErrorMsg("error fetching rowid value from vtab cursor"); return c.SQLITE_ERROR; }; return c.SQLITE_OK; @@ -808,10 +820,19 @@ fn parseModuleArguments( return res; } -fn dupeToSQLiteString(s: []const u8) [*c]const u8 { +fn dupeToSQLiteString(s: []const u8) [*c]u8 { const len: c_int = @intCast(s.len); var buffer: [*c]u8 = @ptrCast(c.sqlite3_malloc(len + 1)); + // sqlite3_malloc returns a null pointer if it can't allocate the memory + if (buffer == null) { + log.err( + "failed to alloc diagnostic message in sqlite: out of memory. original message: {s}", + .{s}, + ); + return null; + } + @memcpy(buffer[0..s.len], s); buffer[s.len] = 0;