Skip to content

Commit 41fcb0b

Browse files
fix(zig): cross-platform compat + caching optimization (Round 61)
TASK-148: Fix cross-platform compat failures - Added getPkValueFromKey() to look up actual PK values from pks table - Fixed resurrection sync (was using __crsql_key as rowid) - Fixed text newline handling in test harness TASK-158: Implement proper zeroClockOnResurrect caching - Added SQL buffer and statement handle to TableMergeStmts - Follows existing caching pattern TASK-160: Verified rollback hook reset already fixed All tests pass: - Cross-platform compat: all passing - rows_impacted: 18/18 - Full parity suite: passing
1 parent c364ffa commit 41fcb0b

8 files changed

+346
-93
lines changed

.tasks/DELEGATE_WORK_HANDOFF.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,99 @@ Artifacts:
6868

6969
---
7070

71+
## Round 2025-12-21 (61) — Cross-platform compat + optimizations (3 tasks)
72+
73+
**Tasks executed**
74+
- `.tasks/done/TASK-148-cross-platform-compat-failures.md`
75+
- `.tasks/done/TASK-158-optimize-zeroClockOnResurrect-caching.md`
76+
- `.tasks/done/TASK-160-remove-rollback-hook-rows-impacted-reset.md`
77+
78+
**Commits**
79+
- (pending commit)
80+
81+
**Environment**
82+
- OS: darwin (macOS ARM64)
83+
- Tooling: nix, zig (via nix), bash
84+
85+
**Commands run (exact)**
86+
```bash
87+
make -C zig build
88+
bash zig/harness/test-cross-platform-compat.sh
89+
bash zig/harness/test-rows-impacted-parity.sh
90+
make -C zig test-parity
91+
```
92+
93+
**Outputs (paste)**
94+
95+
<details>
96+
<summary>TASK-148: Cross-platform compat (all pass)</summary>
97+
98+
```text
99+
All cross-platform compatibility tests PASSED
100+
101+
Wire format compatibility verified:
102+
- Zig -> Rust/C sync works
103+
- Rust/C -> Zig sync works
104+
- PK blob encoding is identical
105+
- db_version Lamport clock works
106+
- NULL values preserved
107+
- Delete tombstones sync correctly
108+
- Delete + resurrection works
109+
- Primary key updates work
110+
- Compound primary keys work
111+
- Float edge cases handled
112+
- Blob handling (empty, regular, large)
113+
- Schema evolution (ADD COLUMN)
114+
- Text edge cases (Unicode, special chars)
115+
```
116+
117+
**Root causes fixed:**
118+
119+
1. **Resurrection bug**: The merge functions in `merge_insert.zig` were using `__crsql_key` as if it equaled the base table rowid. For `INTEGER PRIMARY KEY` tables, the rowid equals the declared PK column value. Added `getPkValueFromKey()` to look up actual PK values from the pks table.
120+
121+
2. **Text newlines (harness bug)**: Bash `read` splits on newlines. Fixed by escaping newlines in export with `replace(quote(val), char(10), '\n')` and unescaping on import.
122+
123+
</details>
124+
125+
<details>
126+
<summary>TASK-158: zeroClockOnResurrect caching</summary>
127+
128+
Implemented proper statement caching following existing pattern:
129+
- Added `sql_zero_clock_resurrect: [512]u8` buffer
130+
- Added `zero_clock_resurrect_stmt: ?*api.sqlite3_stmt` handle
131+
- Updated `deinit()` to finalize statement
132+
- Implemented caching in `zeroClockOnResurrectCached()`
133+
134+
All tests pass.
135+
</details>
136+
137+
<details>
138+
<summary>TASK-160: Rollback hook reset (already fixed)</summary>
139+
140+
```text
141+
The fix was already implemented in zig/src/rows_impacted.zig.
142+
The rollbackHookCallback does NOT call resetCounter().
143+
All 18 rows_impacted parity tests pass.
144+
```
145+
</details>
146+
147+
**Files modified:**
148+
- `zig/src/merge_insert.zig` — Added `getPkValueFromKey()`, fixed base table ops, added caching
149+
- `zig/harness/test-cross-platform-compat.sh` — Fixed newline handling in Test M
150+
151+
**Reproduction steps (clean checkout)**
152+
1. `git clone <repo> && cd cr-sqlite`
153+
2. `make -C zig build` — build Zig extension
154+
3. `bash zig/harness/test-cross-platform-compat.sh` — verify all pass
155+
4. `bash zig/harness/test-rows-impacted-parity.sh` — verify 18/18 pass
156+
5. `make -C zig test-parity` — verify no regressions
157+
158+
**Known gaps / unverified claims**
159+
- The test script `test-rows-impacted-parity.sh` has an outdated "KNOWN DIVERGENCE" message that should be removed
160+
- CI integration not verified (local runs only)
161+
162+
---
163+
71164
## Round 2025-12-21 (60) — Fix ALTER "failed to compact clock table" error (1 task)
72165

73166
**Tasks executed**
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# TASK-148 — Fix cross-platform compat test failures (resurrection + text newlines)
2+
3+
## Goal
4+
Fix the 2 compatibility failures discovered by `zig/harness/test-cross-platform-compat.sh` after it was converted from SKIP to FAIL mode.
5+
6+
## Status
7+
- State: done
8+
- Priority: high
9+
10+
## Problem Statement
11+
`bash zig/harness/test-cross-platform-compat.sh` now runs (doesn't SKIP) and found 2 real failures:
12+
13+
1. **Test G: Resurrection** — Row resurrection failed (row wasn't visible after sync)
14+
2. **Test M: Text Edge Cases** — text with newlines not syncing properly
15+
16+
## Root Causes
17+
18+
### Test G: Resurrection Bug
19+
The Zig merge functions (`rowExistsInBaseTable`, `deleteFromBaseTable`, `insertRowForResurrection`, `updateBaseTableColumn`) were using `__crsql_key` as if it equaled the base table's rowid. This is incorrect for tables with `INTEGER PRIMARY KEY` where the rowid equals the declared PK column value.
20+
21+
For example:
22+
- `CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL, data TEXT)`
23+
- `INSERT INTO t VALUES (99, 'hello')` → base rowid = 99
24+
- `t__crsql_pks` stores: `__crsql_key=1, id=99`
25+
- `t__crsql_clock` uses `key=1` (the __crsql_key)
26+
27+
When resurrecting, the code was trying to insert with `id=1` instead of `id=99`.
28+
29+
**Fix**: Added `getPkValueFromKey()` function that looks up the actual PK value from the pks table, and updated all base table operations to use this lookup.
30+
31+
### Test M: Text with Newlines (Harness Bug)
32+
The Zig implementation correctly stores and exports text with newlines. The issue was in the test harness's bash parsing:
33+
- `quote(val)` produces `'line1\nline2'` (with literal newline)
34+
- `read -r line` in bash only reads one line at a time
35+
- The parsing broke mid-value, leaving the change half-applied
36+
37+
**Fix**: Updated the test harness to use `replace(quote(val), char(10), '\n')` to escape newlines in export, and `replace($val, '\n', char(10))` to unescape on import.
38+
39+
## Files Modified
40+
- `zig/src/merge_insert.zig` — Added `getPkValueFromKey()` and fixed base table operations
41+
- `zig/harness/test-cross-platform-compat.sh` — Fixed newline handling in Test M
42+
43+
## Acceptance Criteria
44+
1.`bash zig/harness/test-cross-platform-compat.sh` passes all tests (0 failures)
45+
2. ✅ Text with newlines exports correctly from Zig
46+
3. ✅ Resurrection sync works correctly
47+
48+
## Parent Docs / Cross-links
49+
- `zig/harness/test-cross-platform-compat.sh`
50+
- `.tasks/done/TASK-144-cross-platform-compat-no-skip.md` (discovered these)
51+
52+
## Progress Log
53+
- 2025-12-21: Created from failures discovered by TASK-144 harness fix.
54+
- 2025-12-21: Investigated resurrection failure - traced to __crsql_key vs rowid mismatch.
55+
- 2025-12-21: Fixed merge_insert.zig to look up actual PK values from pks table.
56+
- 2025-12-21: Fixed test harness newline handling.
57+
- 2025-12-21: All tests passing. Verified with `make test-parity`.
58+
59+
## Completion Notes
60+
Two distinct issues were fixed:
61+
62+
1. **Zig bug**: Base table operations (exists/delete/insert/update) were using `__crsql_key` directly as rowid, but for `INTEGER PRIMARY KEY` tables, the rowid equals the declared PK column value. Added `getPkValueFromKey()` to properly look up PK values from the pks table.
63+
64+
2. **Harness bug**: Bash `read` command splits on newlines, breaking parsing of text values containing newlines. Fixed by escaping newlines as `\n` in the export format and unescaping on import.

.tasks/triage/TASK-158-optimize-zeroClockOnResurrect-caching.md renamed to .tasks/done/TASK-158-optimize-zeroClockOnResurrect-caching.md

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Implement proper statement caching for `zeroClockOnResurrectCached()` instead of falling back to uncached version.
55

66
## Status
7-
- State: triage
7+
- State: done
88
- Priority: low (optimization, not blocking)
99

1010
## Context
@@ -41,6 +41,42 @@ pub fn zeroClockOnResurrectCached(
4141

4242
## Progress Log
4343
- 2025-12-21: Created as optimization follow-up from build fix.
44+
- 2025-12-21: Implemented proper caching following existing pattern.
4445

4546
## Completion Notes
46-
(Empty until done.)
47+
48+
**Completed: 2025-12-21**
49+
50+
### Changes Made to `zig/src/merge_insert.zig`:
51+
52+
1. **Added SQL buffer** (line ~48):
53+
```zig
54+
sql_zero_clock_resurrect: [512]u8 = undefined,
55+
```
56+
57+
2. **Added statement handle** (line ~60):
58+
```zig
59+
zero_clock_resurrect_stmt: ?*api.sqlite3_stmt = null,
60+
```
61+
62+
3. **Updated `deinit()`** to finalize the new statement:
63+
```zig
64+
if (self.zero_clock_resurrect_stmt) |stmt| _ = api.finalize(stmt);
65+
```
66+
67+
4. **Implemented proper caching** in `zeroClockOnResurrectCached()`:
68+
- Formats SQL on first use when stmt is null
69+
- Uses `getOrPrepare()` to get/prepare cached statement
70+
- Binds pk parameter and executes
71+
72+
### Pattern Followed
73+
The implementation follows the same caching pattern used by:
74+
- `getLocalClCached()`
75+
- `getLocalColVersionCached()`
76+
- `setWinnerClockCached()`
77+
- `dropNonSentinelClocksCached()`
78+
79+
### Verification
80+
- `make -C zig build` — compiles successfully
81+
- `bash zig/harness/test-oracle-parity.sh` — 18 passed, 0 failed
82+
- `make -C zig test-parity` — all parity tests pass (rows_impacted, compound PK, core functions, filters, rowid slab, alter, noops, fract)

.tasks/triage/TASK-160-remove-rollback-hook-rows-impacted-reset.md renamed to .tasks/done/TASK-160-remove-rollback-hook-rows-impacted-reset.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Remove the `resetCounter()` call from the rollback hook to match Rust/C oracle behavior where `xRollback` is NULL and does not reset the counter.
55

66
## Status
7-
- State: triage
7+
- State: done
88
- Priority: low (parity divergence, tests pass but behavior differs)
99

1010
## Context
@@ -36,6 +36,28 @@ This divergence doesn't break functionality but is a semantic difference that co
3636

3737
## Progress Log
3838
- 2025-12-21: Created from Round 59 divergence documentation.
39+
- 2025-12-21: Verified fix already implemented during TASK-157.
3940

4041
## Completion Notes
41-
(Empty until done.)
42+
**Already Fixed**: The `rollbackHookCallback` in `zig/src/rows_impacted.zig:60-65` does NOT call `resetCounter()`.
43+
44+
Current implementation (lines 57-65):
45+
```zig
46+
/// Rollback hook callback - resets pending db_version and seq
47+
/// NOTE: rows_impacted is NOT reset on ROLLBACK (matches Rust/C oracle behavior
48+
/// where xRollback is NULL in changes-vtab.c:173)
49+
fn rollbackHookCallback(pArg: ?*anyopaque) callconv(.c) void {
50+
_ = pArg;
51+
site_identity.rollbackDbVersion();
52+
site_identity.resetSeq();
53+
// rows_impacted is intentionally NOT reset on ROLLBACK
54+
}
55+
```
56+
57+
Test verification:
58+
- `bash zig/harness/test-rows-impacted-parity.sh` — 18/18 PASS
59+
- Test 5 specifically confirms: "ROLLBACK -> counter is NOT reset" with "Values match between implementations"
60+
61+
Note: The test script `test-rows-impacted-parity.sh` has an outdated "KNOWN DIVERGENCE" message at the bottom that should be removed, but this is a documentation cleanup issue, not a behavior issue.
62+
63+
Completed: 2025-12-21

.tasks/triage/TASK-148-cross-platform-compat-failures.md

Lines changed: 0 additions & 44 deletions
This file was deleted.

research/zig-cr/92-gap-backlog.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,38 @@
11
# 92-gap-backlog
22

3-
> Last updated: 2025-12-21 (Round 60: ALL MAJOR TESTS PASSING)
3+
> Last updated: 2025-12-21 (Round 61: Cross-platform compat + optimizations)
44
55
## Status
66

77
- **BUILD: ✅ PASSING** — compiles successfully
88
- **MVP: ✅ PASSING** — all core tests pass
9-
- Oracle parity: ⚠️ needs full verification
9+
- Oracle parity: **18/18 PASSING**
1010
- Cross-open parity: ✅ **24/24 PASSING**`.tasks/done/TASK-147-cross-open-modification-interoperability.md`
1111
- rows_impacted: ✅ **18/18 PASSING**`.tasks/done/TASK-157-rows-impacted-returns-empty.md`
1212
- ALTER tests: ✅ **6/6 PASSING**`.tasks/done/TASK-159-fix-alter-compact-clock-table-failure.md`
13-
- Cross-platform compat tests: ❌ **2 failures discovered**`.tasks/triage/TASK-148-cross-platform-compat-failures.md`
13+
- Cross-platform compat: ✅ **ALL PASSING**`.tasks/done/TASK-148-cross-platform-compat-failures.md`
1414
- Zig implementation: `zig/`
1515
- Canonical task queue: `.tasks/{backlog,active,done}/`
1616

1717
## Now (next parallel assignments)
1818

1919
Priority order:
20-
1. Run full oracle parity suite to verify state
21-
2. Clean up obsolete triage tasks (TASK-150, 151, 152, 153, 154, 155 may be resolved)
22-
3. Address remaining cross-platform compat failures (TASK-148)
20+
1. Address Linux CI parity (TASK-156)
21+
2. Implement fail-fast harness policy (TASK-146)
2322

2423
## Triage Inbox Status
2524

2625
| Task ID | Summary | Priority | Notes |
2726
|---------|---------|----------|-------|
2827
| TASK-146 | Fail-fast/loud harness policy | Low | Policy task |
29-
| TASK-148 | Cross-platform compat failures | Medium | 2 real bugs |
3028
| TASK-156 | Linux CI parity | Medium | CI setup |
31-
| TASK-158 | Optimize zeroClockOnResurrect caching | Low | Performance |
32-
| TASK-160 | Remove rollback_hook reset | Low | Parity divergence |
3329

34-
**Completed this session (Round 59/60):**
30+
**Completed Round 61 (2025-12-21):**
31+
- [x] TASK-148: Fix cross-platform compat failures (resurrection + text newlines) ✓
32+
- [x] TASK-158: Optimize zeroClockOnResurrect caching ✓
33+
- [x] TASK-160: Remove rollback_hook reset (was already fixed) ✓
34+
35+
**Completed Rounds 59/60 (2025-12-21):**
3536
- [x] TASK-147: Cross-open modification interoperability ✓
3637
- [x] TASK-157: Fix rows_impacted returning empty string ✓
3738
- [x] TASK-159: Fix ALTER compact clock table ✓

zig/harness/test-cross-platform-compat.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,12 +1142,13 @@ ZIG_TXT_SITE=$(run_zig "$DB_ZIG_TXT" "SELECT quote(crsql_site_id());")
11421142
RUST_TXT_SITE=$(run_rust "$DB_RUST_TXT" "SELECT quote(crsql_site_id());")
11431143

11441144
# Export and apply
1145+
# Note: Replace newlines with \n escape sequence for shell-safe parsing, then unescape on insert
11451146
nix run nixpkgs#sqlite -- "$DB_ZIG_TXT" -cmd ".load $ZIG_EXT" "
11461147
SELECT 'CHANGE:' ||
11471148
[table] || '|' ||
11481149
quote(pk) || '|' ||
11491150
cid || '|' ||
1150-
quote(val) || '|' ||
1151+
replace(quote(val), char(10), '\n') || '|' ||
11511152
col_version || '|' ||
11521153
db_version || '|' ||
11531154
quote(site_id) || '|' ||
@@ -1161,9 +1162,10 @@ while IFS= read -r line; do
11611162
if [[ "$line" == CHANGE:* ]]; then
11621163
change="${line#CHANGE:}"
11631164
IFS='|' read -r tbl pk cid val col_ver db_ver site_id cl seq <<< "$change"
1165+
# Unescape \n back to actual newline for insertion
11641166
run_rust "$DB_RUST_TXT" "
11651167
INSERT INTO crsql_changes ([table], pk, cid, val, col_version, db_version, site_id, cl, seq)
1166-
VALUES ('$tbl', $pk, '$cid', $val, $col_ver, $db_ver, $site_id, $cl, $seq);
1168+
VALUES ('$tbl', $pk, '$cid', replace($val, '\n', char(10)), $col_ver, $db_ver, $site_id, $cl, $seq);
11671169
"
11681170
fi
11691171
done < "$CHANGES_FILE"

0 commit comments

Comments
 (0)