@@ -14,6 +14,26 @@ const site_identity = @import("../site_identity.zig");
1414const compare_values = @import ("../compare_values.zig" );
1515
1616const MAX_SQL_BUF = 4096 ;
17+ const MAX_ERR_BUF = 512 ;
18+
19+ // Thread-local error buffer for diagnostics
20+ threadlocal var last_error_buf : [MAX_ERR_BUF ]u8 = undefined ;
21+ threadlocal var last_error_len : usize = 0 ;
22+
23+ fn setLastError (msg : []const u8 ) void {
24+ const len = @min (msg .len , MAX_ERR_BUF - 1 );
25+ @memcpy (last_error_buf [0.. len ], msg [0.. len ]);
26+ last_error_len = len ;
27+ last_error_buf [len ] = 0 ;
28+ }
29+
30+ fn setLastErrorFmt (comptime fmt : []const u8 , args : anytype ) void {
31+ const msg = std .fmt .bufPrint (& last_error_buf , fmt , args ) catch {
32+ setLastError ("error formatting failed" );
33+ return ;
34+ };
35+ last_error_len = msg .len ;
36+ }
1737
1838const ColumnInfo = struct {
1939 name : [128 ]u8 ,
@@ -55,7 +75,10 @@ fn getTableInfo(db: ?*api.sqlite3, table_name: []const u8) ?TableInfo {
5575
5676 var stmt : ? * api.sqlite3_stmt = null ;
5777 const rc = api .prepare_v2 (db , pragma_sql , -1 , & stmt , null );
58- if (rc != api .SQLITE_OK ) return null ;
78+ if (rc != api .SQLITE_OK ) {
79+ setLastErrorFmt ("getTableInfo PRAGMA prepare failed for table {s}" , .{table_name });
80+ return null ;
81+ }
5982 defer _ = api .finalize (stmt );
6083
6184 while (api .step (stmt ) == api .SQLITE_ROW ) {
@@ -80,15 +103,27 @@ fn getTableInfo(db: ?*api.sqlite3, table_name: []const u8) ?TableInfo {
80103 info .count += 1 ;
81104 }
82105
106+ // Debug: log what we found
107+ if (info .count == 0 ) {
108+ setLastErrorFmt ("getTableInfo found 0 columns for table {s}" , .{table_name });
109+ }
110+
83111 return info ;
84112}
85113
86114fn getPkColumnName (info : * const TableInfo , pk_order : usize ) ? []const u8 {
87115 for (info .columns [0.. info .count ]) | col | {
88116 if (col .pk_index == @as (c_int , @intCast (pk_order ))) {
89- return col .name [0.. col .name_len ];
117+ const result = col .name [0.. col .name_len ];
118+ // Debug: ensure we're returning a valid name
119+ if (result .len == 0 ) {
120+ setLastErrorFmt ("getPkColumnName found empty name for pk_order={}" , .{pk_order });
121+ }
122+ return result ;
90123 }
91124 }
125+ // Debug: record which pk_order we couldn't find
126+ setLastErrorFmt ("getPkColumnName: no column found with pk_index={} (have {} cols, {} pks)" , .{ pk_order , info .count , info .pk_count });
92127 return null ;
93128}
94129
@@ -124,33 +159,109 @@ fn getOrCreatePkKey(
124159 info : * const TableInfo ,
125160 pk_values : []* api.sqlite3_value ,
126161) ? i64 {
127- if (db == null ) return null ;
162+ if (db == null ) {
163+ setLastError ("db is null" );
164+ return null ;
165+ }
166+
167+ // Debug: log what we have
168+ if (info .pk_count == 0 ) {
169+ setLastError ("info.pk_count is 0" );
170+ return null ;
171+ }
172+
173+ // Build a debug string of all columns and fail immediately to see column info
174+ var debug_buf : [512 ]u8 = undefined ;
175+ var debug_len : usize = 0 ;
176+ for (info .columns [0.. info .count ]) | col | {
177+ const name_slice = col .name [0.. col .name_len ];
178+ const part = std .fmt .bufPrint (debug_buf [debug_len .. ], "{s}(pk={})," , .{ name_slice , col .pk_index }) catch break ;
179+ debug_len += part .len ;
180+ }
181+
182+ // Debug info logged (we know cols are correct now)
183+
128184 // SELECT __crsql_key FROM "{table}__crsql_pks" WHERE "pk1" IS ? AND "pk2" IS ? ...
129185 var select_buf : [MAX_SQL_BUF ]u8 = undefined ;
130186 var select_fbs = std .io .fixedBufferStream (& select_buf );
131187 const select_writer = select_fbs .writer ();
132188
133- select_writer .print ("SELECT __crsql_key FROM \" {s}__crsql_pks\" WHERE " , .{table_name }) catch return null ;
189+ select_writer .print ("SELECT __crsql_key FROM \" {s}__crsql_pks\" WHERE " , .{table_name }) catch {
190+ setLastError ("failed to format SELECT SQL" );
191+ return null ;
192+ };
134193
135194 var pk_order : usize = 1 ;
136195 var pk_written : usize = 0 ;
196+ var iteration : usize = 0 ;
137197 while (pk_written < info .pk_count ) : (pk_order += 1 ) {
138- const col_name = getPkColumnName (info , pk_order ) orelse return null ;
198+ iteration += 1 ;
199+ // DEBUG: Log each iteration
200+ if (iteration > 10 ) {
201+ setLastError ("infinite loop protection" );
202+ return null ;
203+ }
204+ const col_name = getPkColumnName (info , pk_order ) orelse {
205+ // Error already set by getPkColumnName - add iteration context
206+ var err_buf : [512 ]u8 = undefined ;
207+ const prev_err = last_error_buf [0.. last_error_len ];
208+ const msg = std .fmt .bufPrint (& err_buf , "iter pk_order={} pk_written={}: {s}" , .{ pk_order , pk_written , prev_err }) catch "getPkColumnName failed" ;
209+ setLastError (msg );
210+ return null ;
211+ };
212+ if (col_name .len == 0 ) {
213+ setLastErrorFmt ("empty col_name at pk_order={} pk_written={}" , .{ pk_order , pk_written });
214+ return null ;
215+ }
139216 if (pk_written > 0 ) {
140- select_writer .writeAll (" AND " ) catch return null ;
217+ select_writer .writeAll (" AND " ) catch {
218+ setLastErrorFmt ("failed AND at pos={} pk_order={}" , .{ select_fbs .pos , pk_order });
219+ return null ;
220+ };
221+ }
222+ const before_pos = select_fbs .pos ;
223+
224+ // DEBUG: Force error on second iteration to see col_name
225+ if (iteration == 2 ) {
226+ // Don't try to format col_name as string, just show length and first few bytes as hex
227+ var hex_buf : [256 ]u8 = undefined ;
228+ var hex_len : usize = 0 ;
229+ for (col_name , 0.. ) | byte , i | {
230+ if (hex_len + 5 >= hex_buf .len ) break ;
231+ const part = std .fmt .bufPrint (hex_buf [hex_len .. ], "{x:0>2} " , .{byte }) catch break ;
232+ hex_len += part .len ;
233+ if (i >= 10 ) break ; // Limit to first 10 bytes
234+ }
235+ setLastErrorFmt ("ITER2: len={} hex=[{s}] pos={} order={}" , .{ col_name .len , hex_buf [0.. hex_len ], before_pos , pk_order });
236+ return null ;
237+ }
238+
239+ select_writer .print ("\" {s}\" IS ?" , .{col_name }) catch | err | {
240+ setLastErrorFmt ("failed write pk_order={} col_name=[{s}] len={} pos={} err={}" , .{ pk_order , col_name , col_name .len , before_pos , err });
241+ return null ;
242+ };
243+ const after_pos = select_fbs .pos ;
244+ // Detect if write succeeded but didn't actually write
245+ if (after_pos == before_pos ) {
246+ setLastErrorFmt ("SILENT FAIL: pk_order={} col=[{s}] before_pos={} after_pos={}" , .{ pk_order , col_name , before_pos , after_pos });
247+ return null ;
141248 }
142- select_writer .print ("\" {s}\" IS ?" , .{col_name }) catch return null ;
143249 pk_written += 1 ;
144250 }
145251
146252 const select_len = select_fbs .pos ;
147253 if (select_len >= MAX_SQL_BUF ) return null ;
148254 select_buf [select_len ] = 0 ;
149255
150- var select_stmt : ? * api.sqlite3_stmt = null ;
151256 const select_sql : [* :0 ]const u8 = @ptrCast (& select_buf );
257+
258+ var select_stmt : ? * api.sqlite3_stmt = null ;
152259 var rc = api .prepare_v2 (db , select_sql , -1 , & select_stmt , null );
153- if (rc != api .SQLITE_OK ) return null ;
260+ if (rc != api .SQLITE_OK ) {
261+ const err = api .errmsg (db );
262+ setLastErrorFmt ("SELECT prepare failed (rc={}): {s} | SQL: {s}" , .{ rc , std .mem .span (err ), std .mem .span (select_sql ) });
263+ return null ;
264+ }
154265 defer _ = api .finalize (select_stmt );
155266
156267
@@ -197,19 +308,28 @@ fn getOrCreatePkKey(
197308 var insert_stmt : ? * api.sqlite3_stmt = null ;
198309 const insert_sql : [* :0 ]const u8 = @ptrCast (& insert_buf );
199310 rc = api .prepare_v2 (db , insert_sql , -1 , & insert_stmt , null );
200- if (rc != api .SQLITE_OK ) return null ;
311+ if (rc != api .SQLITE_OK ) {
312+ const err = api .errmsg (db );
313+ setLastErrorFmt ("INSERT prepare failed (rc={}): {s} | SQL: {s}" , .{ rc , std .mem .span (err ), std .mem .span (insert_sql ) });
314+ return null ;
315+ }
201316 defer _ = api .finalize (insert_stmt );
202317
203318 for (0.. info .pk_count ) | i | {
204319 rc = bindValue (insert_stmt , @intCast (i + 1 ), pk_values [i ]);
205- if (rc != api .SQLITE_OK ) return null ;
320+ if (rc != api .SQLITE_OK ) {
321+ setLastErrorFmt ("INSERT bind failed at index {} (rc={})" , .{ i + 1 , rc });
322+ return null ;
323+ }
206324 }
207325
208326 rc = api .step (insert_stmt );
209327 if (rc == api .SQLITE_ROW ) {
210328 return api .column_int64 (insert_stmt , 0 );
211329 }
212330
331+ const err = api .errmsg (db );
332+ setLastErrorFmt ("INSERT step failed (rc={}): {s}" , .{ rc , std .mem .span (err ) });
213333 return null ;
214334}
215335
@@ -427,10 +547,18 @@ fn crsqlAfterInsertFunc(pCtx: ?*api.sqlite3_context, argc: c_int, argv: [*c]?*ap
427547 const table_name = std .mem .span (table_name_ptr .? );
428548
429549 const info = getTableInfo (db , table_name ) orelse {
430- api .result_error (pCtx , "crsql_after_insert: failed to get table info" , -1 );
550+ var err_buf : [1024 ]u8 = undefined ;
551+ const err_msg = std .fmt .bufPrintZ (& err_buf , "crsql_after_insert getTableInfo failed: {s}" , .{last_error_buf [0.. last_error_len ]}) catch "crsql_after_insert: failed to get table info" ;
552+ api .result_error (pCtx , err_msg .ptr , @intCast (err_msg .len ));
431553 return ;
432554 };
433555
556+ // Validate we got the expected number of PK columns
557+ if (info .pk_count == 0 ) {
558+ api .result_error (pCtx , "crsql_after_insert: table has no PK columns" , -1 );
559+ return ;
560+ }
561+
434562 const pk_count : usize = @intCast (argc - 1 );
435563 if (pk_count != info .pk_count ) {
436564 api .result_error (pCtx , "crsql_after_insert: PK count mismatch" , -1 );
@@ -445,9 +573,10 @@ fn crsqlAfterInsertFunc(pCtx: ?*api.sqlite3_context, argc: c_int, argv: [*c]?*ap
445573 // Get or create the key in __crsql_pks.
446574 // Note: we intentionally do this before reading current clocks.
447575 const key_existing = getOrCreatePkKey (db , table_name , & info , pk_values [0.. pk_count ]) orelse {
448- // Emit debug info that helps pinpoint which SQL path failed.
449- // (Note: error message returned to SQLite)
450- api .result_error (pCtx , "crsql_after_insert: failed geteting or creating lookaside key (pks schema lookup/insert failed)" , -1 );
576+ // Return the captured error message with context
577+ var err_buf : [1024 ]u8 = undefined ;
578+ const err_msg = std .fmt .bufPrintZ (& err_buf , "crsql_after_insert failed: {s}" , .{last_error_buf [0.. last_error_len ]}) catch "crsql_after_insert: getOrCreatePkKey failed" ;
579+ api .result_error (pCtx , err_msg .ptr , @intCast (err_msg .len ));
451580 return ;
452581 };
453582
@@ -522,7 +651,9 @@ fn crsqlAfterUpdateFunc(pCtx: ?*api.sqlite3_context, argc: c_int, argv: [*c]?*ap
522651 const next_db_version = site_identity .nextDbVersion (null );
523652
524653 const new_key = getOrCreatePkKey (db , table_name , & info , pk_new_values [0.. pk_count ]) orelse {
525- api .result_error (pCtx , "crsql_after_update: failed geteting or creating lookaside key" , -1 );
654+ var err_buf : [1024 ]u8 = undefined ;
655+ const err_msg = std .fmt .bufPrintZ (& err_buf , "crsql_after_update (new_key) failed: {s}" , .{last_error_buf [0.. last_error_len ]}) catch "crsql_after_update: getOrCreatePkKey failed" ;
656+ api .result_error (pCtx , err_msg .ptr , @intCast (err_msg .len ));
526657 return ;
527658 };
528659
@@ -536,7 +667,9 @@ fn crsqlAfterUpdateFunc(pCtx: ?*api.sqlite3_context, argc: c_int, argv: [*c]?*ap
536667
537668 if (pk_changed ) {
538669 const old_key = getOrCreatePkKey (db , table_name , & info , pk_old_values [0.. pk_count ]) orelse {
539- api .result_error (pCtx , "crsql_after_update: failed geteting or creating lookaside key" , -1 );
670+ var err_buf : [1024 ]u8 = undefined ;
671+ const err_msg = std .fmt .bufPrintZ (& err_buf , "crsql_after_update (old_key) failed: {s}" , .{last_error_buf [0.. last_error_len ]}) catch "crsql_after_update: getOrCreatePkKey failed" ;
672+ api .result_error (pCtx , err_msg .ptr , @intCast (err_msg .len ));
540673 return ;
541674 };
542675 const seq_del = site_identity .getNextSeq ();
@@ -607,7 +740,9 @@ fn crsqlAfterDeleteFunc(pCtx: ?*api.sqlite3_context, argc: c_int, argv: [*c]?*ap
607740 const seq = site_identity .getNextSeq ();
608741
609742 const key = getOrCreatePkKey (db , table_name , & info , pk_values [0.. pk_count ]) orelse {
610- api .result_error (pCtx , "crsql_after_delete: failed geteting or creating lookaside key" , -1 );
743+ var err_buf : [1024 ]u8 = undefined ;
744+ const err_msg = std .fmt .bufPrintZ (& err_buf , "crsql_after_delete failed: {s}" , .{last_error_buf [0.. last_error_len ]}) catch "crsql_after_delete: getOrCreatePkKey failed" ;
745+ api .result_error (pCtx , err_msg .ptr , @intCast (err_msg .len ));
611746 return ;
612747 };
613748
0 commit comments