Skip to content

Commit 255e316

Browse files
fix(after_write): fix dangling pointer in getPkColumnName for compound PKs
PROBLEM: Compound PK inserts were failing with incomplete SQL generation. Second PK column name was corrupted (null byte instead of actual name). ROOT CAUSE: getPkColumnName was iterating by value, creating a local copy of each ColumnInfo, then returning a slice into that local copy. After the function returned, the copy was destroyed, leaving a dangling pointer. FIX: Changed from iterating by value to iterating by index with pointer dereference. This ensures the returned slice points into the actual TableInfo.columns array, not a temporary copy. BEFORE: for (info.columns[0..info.count]) |col| { // Copy! return col.name[0..col.name_len]; // Dangling! } AFTER: for (0..info.count) |i| { const col = &info.columns[i]; // Pointer to actual return col.name[0..col.name_len]; // Safe! } IMPACT: Compound PK tables now work correctly for local writes via triggers. TESTING: CREATE TABLE foo(a INT NOT NULL, b INT NOT NULL, c TEXT, PRIMARY KEY(a,b)); SELECT crsql_as_crr('foo'); INSERT INTO foo VALUES(4,5,'hello'), (10,20,'world'); -- Both rows inserted successfully, pks table populated correctly PART OF: TASK-147 (Rust/C pks schema compatibility) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent bba5977 commit 255e316

File tree

1 file changed

+4
-17
lines changed

1 file changed

+4
-17
lines changed

zig/src/local_writes/after_write.zig

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const TableInfo = struct {
5050

5151
fn getTableInfo(db: ?*api.sqlite3, table_name: []const u8) ?TableInfo {
5252
var info = TableInfo{
53-
.columns = undefined,
53+
.columns = [_]ColumnInfo{.{ .name = [_]u8{0} ** 128, .name_len = 0, .pk_index = 0 }} ** 64,
5454
.count = 0,
5555
.pk_count = 0,
5656
.non_pk_count = 0,
@@ -112,7 +112,9 @@ fn getTableInfo(db: ?*api.sqlite3, table_name: []const u8) ?TableInfo {
112112
}
113113

114114
fn getPkColumnName(info: *const TableInfo, pk_order: usize) ?[]const u8 {
115-
for (info.columns[0..info.count]) |col| {
115+
// IMPORTANT: Iterate by index, not by value, to avoid returning slice into local copy
116+
for (0..info.count) |i| {
117+
const col = &info.columns[i];
116118
if (col.pk_index == @as(c_int, @intCast(pk_order))) {
117119
const result = col.name[0..col.name_len];
118120
// Debug: ensure we're returning a valid name
@@ -221,21 +223,6 @@ fn getOrCreatePkKey(
221223
}
222224
const before_pos = select_fbs.pos;
223225

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-
239226
select_writer.print("\"{s}\" IS ?", .{col_name}) catch |err| {
240227
setLastErrorFmt("failed write pk_order={} col_name=[{s}] len={} pos={} err={}", .{ pk_order, col_name, col_name.len, before_pos, err });
241228
return null;

0 commit comments

Comments
 (0)