@@ -44,17 +44,61 @@ jobs:
4444 - name : Clone Turso source
4545 run : git clone --depth 1 --branch '${{ steps.turso.outputs.tag }}' https://github.com/tursodatabase/turso.git
4646
47- # Turso's stub!() macro expands to a `todo!()` that panics across the FFI
48- # boundary and aborts the PHP process. That blocks any call into a not-yet-
49- # implemented sqlite3_* function — e.g. sqlite3_set_authorizer, which
50- # PHP's pdo_sqlite calls during PDO construction. Rewrite the macro so
51- # stubbed functions return a zeroed value of their return type instead
52- # (0 / SQLITE_OK for ints, NULL for pointers). This lets the driver reach
53- # functions Turso does implement, so PHPUnit can produce a real report.
54- - name : Patch Turso stub macro to not abort
47+ # Turso's C API shim aborts the PHP process via Rust panics in several
48+ # places. These patches neutralise the ones pdo_sqlite trips over:
49+ #
50+ # 1. `stub!()` expands to `todo!()`; pdo_sqlite hits it during PDO
51+ # construction (sqlite3_set_authorizer). Rewrite to return a zeroed
52+ # value of the function's return type (0 / SQLITE_OK for ints, NULL
53+ # for pointers) instead.
54+ #
55+ # 2. The sqlite3_column_* functions `.expect()` that a row is present,
56+ # but pdo_sqlite legitimately calls them on statements that have not
57+ # yet stepped to SQLITE_ROW (e.g. for column metadata). Replace the
58+ # expect with an early return of the type's "null" value, matching
59+ # SQLite's actual behaviour.
60+ - name : Patch Turso to not abort on recoverable conditions
5561 working-directory : turso
5662 run : |
5763 sed -i 's|todo!("{} is not implemented", stringify!($fn));|return unsafe { std::mem::zeroed() };|' sqlite3/src/lib.rs
64+
65+ python3 - <<'PY'
66+ import re
67+
68+ path = 'sqlite3/src/lib.rs'
69+ defaults = {
70+ 'sqlite3_column_type': 'SQLITE_NULL',
71+ 'sqlite3_column_int': '0',
72+ 'sqlite3_column_int64': '0',
73+ 'sqlite3_column_double': '0.0',
74+ 'sqlite3_column_blob': 'std::ptr::null()',
75+ 'sqlite3_column_bytes': '0',
76+ 'sqlite3_column_text': 'std::ptr::null()',
77+ }
78+
79+ pattern = re.compile(
80+ r'(pub unsafe extern "C" fn (sqlite3_column_\w+)\([^)]*\)[^{]*\{)'
81+ r'((?:[^{}]|\{[^{}]*\})*?)'
82+ r'(let row = stmt\s*\.stmt\s*\.row\(\)\s*'
83+ r'\.expect\("Function should only be called after `SQLITE_ROW`"\);)',
84+ re.DOTALL,
85+ )
86+
87+ def repl(m):
88+ header, name, body, _ = m.group(1), m.group(2), m.group(3), m.group(4)
89+ default = defaults.get(name, '0')
90+ guarded = (
91+ f'let row = match stmt.stmt.row() {{ '
92+ f'Some(r) => r, None => return {default} }};'
93+ )
94+ return header + body + guarded
95+
96+ src = open(path).read()
97+ src, n = pattern.subn(repl, src)
98+ open(path, 'w').write(src)
99+ print(f'patched {n} sqlite3_column_* functions')
100+ PY
101+
58102 echo '--- Patched stub! macro ---'
59103 sed -n '/macro_rules! stub/,/^}$/p' sqlite3/src/lib.rs
60104
0 commit comments