@@ -2047,77 +2047,105 @@ fn changesUpdate(
20472047 // Get remote value from argv[5]
20482048 const remote_value = toApiValue (argv [5 ]);
20492049
2050- // Fetch local value for comparison
2051- var local_value_buf : [1024 ]u8 = undefined ;
2052- if (std .fmt .bufPrintZ (& local_value_buf , "SELECT \" {s}\" FROM \" {s}\" WHERE rowid = ?" , .{ cid_slice , table_slice })) | local_value_sql | {
2053- var local_stmt : ? * api.sqlite3_stmt = null ;
2054- if (api .prepare_v2 (api_db , local_value_sql , -1 , & local_stmt , null ) == api .SQLITE_OK ) {
2055- defer _ = api .finalize (local_stmt );
2056- _ = api .bind_int64 (local_stmt , 1 , pk_rowid );
2057-
2058- if (api .step (local_stmt ) == api .SQLITE_ROW ) {
2059- // Compare using compare_values module
2060- const local_sqlite_value = api .column_value (local_stmt , 0 );
2061- const cmp = compare_values .compareSqliteValues (remote_value , local_sqlite_value );
2062- if (cmp > 0 ) {
2063- // Remote value is larger, remote wins
2064- remote_wins = true ;
2065- } else if (cmp == 0 ) {
2066- // Values are equal - check merge-equal-values config
2067- // If enabled (default), tie-break on site_id (larger site_id wins)
2068- const merge_equal = config .getMergeEqualValues (api_db );
2069- if (merge_equal == 1 ) {
2070- // Get local site_id from clock table for this column
2071- var sid_buf : [512 ]u8 = undefined ;
2072- if (std .fmt .bufPrintZ (& sid_buf , "SELECT site_id FROM \" {s}__crsql_clock\" WHERE key = ? AND col_name = ?" , .{table_slice })) | sid_sql | {
2073- var sid_stmt : ? * api.sqlite3_stmt = null ;
2074- if (api .prepare_v2 (api_db , sid_sql , -1 , & sid_stmt , null ) == api .SQLITE_OK ) {
2075- defer _ = api .finalize (sid_stmt );
2076- _ = api .bind_int64 (sid_stmt , 1 , pk_rowid );
2077- _ = api .bind_text (sid_stmt , 2 , cid_slice .ptr , @intCast (cid_slice .len ), api .getTransientDestructor ());
2078-
2079- if (api .step (sid_stmt ) == api .SQLITE_ROW ) {
2080- const local_site_id_blob = api .column_blob (sid_stmt , 0 );
2081- const local_site_id_len = api .column_bytes (sid_stmt , 0 );
2082-
2083- // Compare site_ids (larger wins)
2084- // Remote site_id is in site_id_blob[0..site_id_len]
2085- if (site_id_blob != null and local_site_id_blob != null ) {
2086- const local_sid : [* ]const u8 = @ptrCast (local_site_id_blob );
2087- const remote_sid : [* ]const u8 = @ptrCast (site_id_blob );
2088- const local_slice = local_sid [0.. @intCast (local_site_id_len )];
2089- const remote_len : usize = @intCast (site_id_len );
2090- const remote_slice = remote_sid [0.. remote_len ];
2091-
2092- // Use lexicographic comparison (larger site_id wins)
2093- const sid_cmp = std .mem .order (u8 , remote_slice , local_slice );
2094- if (sid_cmp == .gt ) {
2095- // Remote site_id is larger, remote wins
2096- remote_wins = true ;
2050+ // Fetch local value for comparison using subquery with pks table
2051+ // This properly handles TEXT and BLOB primary keys
2052+ var pk_col_buf : [256 ]u8 = undefined ;
2053+ const pk_col_sql = std .fmt .bufPrintZ (& pk_col_buf , "PRAGMA table_info(\" {s}\" )" , .{table_slice }) catch {
2054+ // Buffer overflow - local wins (conservative)
2055+ // Fall through to end
2056+ pRowid .* = 0 ;
2057+ return vtab .SQLITE_OK ;
2058+ };
2059+
2060+ var pk_col_stmt : ? * api.sqlite3_stmt = null ;
2061+ if (api .prepare_v2 (api_db , pk_col_sql , -1 , & pk_col_stmt , null ) == api .SQLITE_OK ) {
2062+ defer _ = api .finalize (pk_col_stmt );
2063+
2064+ // Find the PK column name
2065+ var pk_col_name : ? [128 ]u8 = null ;
2066+ while (api .step (pk_col_stmt ) == api .SQLITE_ROW ) {
2067+ const pk_pos = api .column_int64 (pk_col_stmt , 5 );
2068+ if (pk_pos > 0 ) {
2069+ const name_ptr = api .column_text (pk_col_stmt , 1 );
2070+ if (name_ptr ) | name | {
2071+ var temp : [128 ]u8 = undefined ;
2072+ const name_slice = std .mem .span (name );
2073+ const len = @min (name_slice .len , 127 );
2074+ @memcpy (temp [0.. len ], name_slice [0.. len ]);
2075+ temp [len ] = 0 ;
2076+ pk_col_name = temp ;
2077+ }
2078+ break ;
2079+ }
2080+ }
2081+
2082+ // Now query local value using the PK column
2083+ if (pk_col_name ) | pk_col | {
2084+ const pk_col_len2 = std .mem .indexOfScalar (u8 , & pk_col , 0 ) orelse pk_col .len ;
2085+ const pk_slice = pk_col [0.. pk_col_len2 ];
2086+
2087+ var local_value_buf : [1024 ]u8 = undefined ;
2088+ // Build: SELECT "col" FROM "table" WHERE "pk_col" = (SELECT "pk_col" FROM "table__crsql_pks" WHERE __crsql_key = ?)
2089+ if (std .fmt .bufPrintZ (& local_value_buf , "SELECT \" {s}\" FROM \" {s}\" WHERE \" {s}\" = (SELECT \" {s}\" FROM \" {s}__crsql_pks\" WHERE __crsql_key = ?)" , .{ cid_slice , table_slice , pk_slice , pk_slice , table_slice })) | local_value_sql | {
2090+ var local_stmt : ? * api.sqlite3_stmt = null ;
2091+ if (api .prepare_v2 (api_db , local_value_sql , -1 , & local_stmt , null ) == api .SQLITE_OK ) {
2092+ defer _ = api .finalize (local_stmt );
2093+ _ = api .bind_int64 (local_stmt , 1 , pk_rowid );
2094+
2095+ if (api .step (local_stmt ) == api .SQLITE_ROW ) {
2096+ // Compare using compare_values module
2097+ const local_sqlite_value = api .column_value (local_stmt , 0 );
2098+ const cmp = compare_values .compareSqliteValues (remote_value , local_sqlite_value );
2099+ if (cmp > 0 ) {
2100+ // Remote value is larger, remote wins
2101+ remote_wins = true ;
2102+ } else if (cmp == 0 ) {
2103+ // Values are equal - check merge-equal-values config
2104+ // If enabled (default), tie-break on site_id (larger site_id wins)
2105+ const merge_equal = config .getMergeEqualValues (api_db );
2106+ if (merge_equal == 1 ) {
2107+ // Get local site_id from clock table for this column
2108+ var sid_buf : [512 ]u8 = undefined ;
2109+ if (std .fmt .bufPrintZ (& sid_buf , "SELECT site_id FROM \" {s}__crsql_clock\" WHERE key = ? AND col_name = ?" , .{table_slice })) | sid_sql | {
2110+ var sid_stmt : ? * api.sqlite3_stmt = null ;
2111+ if (api .prepare_v2 (api_db , sid_sql , -1 , & sid_stmt , null ) == api .SQLITE_OK ) {
2112+ defer _ = api .finalize (sid_stmt );
2113+ _ = api .bind_int64 (sid_stmt , 1 , pk_rowid );
2114+ _ = api .bind_text (sid_stmt , 2 , cid_slice .ptr , @intCast (cid_slice .len ), api .getTransientDestructor ());
2115+
2116+ if (api .step (sid_stmt ) == api .SQLITE_ROW ) {
2117+ const local_site_id_blob = api .column_blob (sid_stmt , 0 );
2118+ const local_site_id_len = api .column_bytes (sid_stmt , 0 );
2119+
2120+ // Compare site_ids (larger wins)
2121+ if (site_id_blob != null and local_site_id_blob != null ) {
2122+ const local_sid : [* ]const u8 = @ptrCast (local_site_id_blob );
2123+ const remote_sid : [* ]const u8 = @ptrCast (site_id_blob );
2124+ const local_slice_sid = local_sid [0.. @intCast (local_site_id_len )];
2125+ const remote_len_sid : usize = @intCast (site_id_len );
2126+ const remote_slice_sid = remote_sid [0.. remote_len_sid ];
2127+
2128+ // Use lexicographic comparison (larger site_id wins)
2129+ const sid_cmp = std .mem .order (u8 , remote_slice_sid , local_slice_sid );
2130+ if (sid_cmp == .gt ) {
2131+ remote_wins = true ;
2132+ }
2133+ } else if (site_id_blob != null and local_site_id_blob == null ) {
2134+ remote_wins = true ;
2135+ }
20972136 }
2098- } else if (site_id_blob != null and local_site_id_blob == null ) {
2099- // Remote has site_id, local doesn't - remote wins
2100- remote_wins = true ;
21012137 }
2102- // If local has site_id but remote doesn't, local wins
2103- }
2104- // If no clock entry found, local wins (conservative)
2138+ } else | _ | {}
21052139 }
2106- } else | _ | {
2107- // Buffer overflow, local wins (conservative)
21082140 }
2141+ // If cmp < 0, local wins (remote_wins stays false)
2142+ } else {
2143+ // No local row, remote wins
2144+ remote_wins = true ;
21092145 }
2110- // If merge_equal == 0, values equal means no-op (local wins)
21112146 }
2112- // If cmp < 0, local wins (remote_wins stays false)
2113- } else {
2114- // No local row, remote wins
2115- remote_wins = true ;
2116- }
2147+ } else | _ | {}
21172148 }
2118- // If prepare failed, local wins (conservative)
2119- } else | _ | {
2120- // Buffer overflow, local wins (conservative)
21212149 }
21222150 }
21232151 // If col_version < local_col_version, local wins (remote_wins stays false)
0 commit comments