Skip to content

Commit 9c0b24d

Browse files
committed
Patch sqlite3_column_* to return defaults when no row
1 parent 3bae638 commit 9c0b24d

1 file changed

Lines changed: 52 additions & 8 deletions

File tree

.github/workflows/phpunit-tests-turso.yml

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)