Skip to content

Commit 5fe9618

Browse files
committed
Tighter probe + force temp_store=MEMORY under Turso
Two changes in one CI iteration to maximize use of the build: - Tighten the correlated-subquery probe: the minimal repro passed every workaround variant on Turso, so the bug isn't fundamental. Replace it with a probe that mirrors the *exact* shape the driver emits — info_schema-style _ist/_isc/sqlseq tables, the multi-table LEFT JOIN inside the correlated subquery, and COALESCE — then run the failing-test query patterns (`AI > 3`, `AI IS NULL`, plain SELECT) and print the actual rows so we can see what Turso returns. - Patch the SQLite connection's __construct to issue `PRAGMA temp_store = MEMORY` after journal_mode setup. Turso's default disk-backed temp pager has a state-dependent `short read on page N` bug (mid-suite, doesn't reproduce in fresh PDO). Forcing temp_store=MEMORY routes temp tables through MemoryIO, which is the same code path the probe exercises and which works. This is the candidate fix for testCreateTemporaryTable + testTemporaryTableHasPriorityOverStandardTable.
1 parent b5a2d2c commit 5fe9618

1 file changed

Lines changed: 73 additions & 26 deletions

File tree

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

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,40 +1014,53 @@ jobs:
10141014
run: |
10151015
php <<'PHP'
10161016
<?php
1017-
// Minimal repro of testInformationSchemaTablesFilterByAutoIncrement.
1018-
// Verify whether Turso correctly evaluates a correlated subquery
1019-
// alias inside a derived-table SELECT list when the outer query
1020-
// applies WHERE on the alias.
1017+
// Reproduce testInformationSchemaTablesFilterByAutoIncrement's exact
1018+
// shape — info_schema-style tables and the *exact* translate_table_ref
1019+
// correlated subquery (LEFT JOIN inside subquery + COALESCE).
10211020
$pdo = new PDO('sqlite::memory:');
10221021
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
1023-
$pdo->exec('CREATE TABLE outer_t (id INT, name TEXT)');
1024-
$pdo->exec('CREATE TABLE inner_t (parent_name TEXT, value INT)');
1025-
$pdo->exec("INSERT INTO outer_t VALUES (1,'a'), (2,'b'), (3,'c')");
1026-
$pdo->exec("INSERT INTO inner_t VALUES ('a',10), ('b',20), ('c',30)");
1022+
// Mirror the driver's information schema layout and seed it.
1023+
$pdo->exec("CREATE TABLE _ist (table_schema TEXT, table_name TEXT, engine TEXT)");
1024+
$pdo->exec("CREATE TABLE _isc (table_schema TEXT, table_name TEXT, column_name TEXT, extra TEXT)");
1025+
$pdo->exec("INSERT INTO _ist VALUES ('main','low','InnoDB'), ('main','high','InnoDB'), ('main','plain','InnoDB')");
1026+
$pdo->exec("INSERT INTO _isc VALUES ('main','low','id','auto_increment'), ('main','high','id','auto_increment'), ('main','plain','id','')");
1027+
// sqlite_sequence is auto-managed; emulate with a regular table called sqlseq.
1028+
$pdo->exec("CREATE TABLE sqlseq (name TEXT, seq INT)");
1029+
$pdo->exec("INSERT INTO sqlseq VALUES ('low',1), ('high',5)");
1030+
1031+
// Driver's actual correlated subquery shape:
1032+
$corr = "(SELECT COALESCE(s.seq + 1, 1)
1033+
FROM _isc AS c
1034+
LEFT JOIN sqlseq AS s ON s.name = c.table_name
1035+
WHERE c.extra = 'auto_increment'
1036+
AND c.table_schema = t.table_schema
1037+
AND c.table_name = t.table_name)";
1038+
1039+
$derived = "(SELECT table_name AS NAME, $corr AS AI FROM _ist AS t)";
10271040
10281041
$cases = [
1029-
'baseline: correlated subq, no derived-table wrap' =>
1030-
"SELECT id, (SELECT value FROM inner_t WHERE parent_name = outer_t.name) AS v FROM outer_t WHERE (SELECT value FROM inner_t WHERE parent_name = outer_t.name) > 15",
1031-
'derived-table wrap + WHERE on alias' =>
1032-
"SELECT id FROM (SELECT id, name, (SELECT value FROM inner_t WHERE parent_name = outer_t.name) AS v FROM outer_t) WHERE v > 15",
1033-
'derived-table + WHERE on alias + outer col aliased in inner WHERE (length trick)' =>
1034-
"SELECT id FROM (SELECT id, name, (SELECT value FROM inner_t WHERE parent_name = outer_t.name AND length(outer_t.name) > 0) AS v FROM outer_t) WHERE v > 15",
1035-
'derived-table + WHERE on alias + outer col added in inner SELECT (zero arith)' =>
1036-
"SELECT id FROM (SELECT id, name, (SELECT value + (length(outer_t.name) - length(outer_t.name)) FROM inner_t WHERE parent_name = outer_t.name) AS v FROM outer_t) WHERE v > 15",
1037-
'derived-table + WHERE on alias + outer col combined into expression' =>
1038-
"SELECT id FROM (SELECT id, name, (SELECT value || '|' || outer_t.name FROM inner_t WHERE parent_name = outer_t.name) AS combined, (SELECT value FROM inner_t WHERE parent_name = outer_t.name) AS v FROM outer_t) WHERE v > 15",
1039-
'derived-table + WHERE on alias + recreate as JOIN form' =>
1040-
"SELECT id FROM (SELECT outer_t.id, outer_t.name, inner_t.value AS v FROM outer_t LEFT JOIN inner_t ON inner_t.parent_name = outer_t.name) WHERE v > 15",
1042+
'inline correlated subq value' =>
1043+
"SELECT table_name, $corr AS AI FROM _ist AS t",
1044+
'derived-table + WHERE AI > 3 (the bug)' =>
1045+
"SELECT NAME FROM $derived WHERE AI > 3",
1046+
'derived-table + WHERE AI IS NULL' =>
1047+
"SELECT NAME FROM $derived WHERE AI IS NULL",
1048+
'derived-table + plain SELECT' =>
1049+
"SELECT NAME, AI FROM $derived",
1050+
'derived-table + WHERE on AI with length(t.name) > 0 forced' =>
1051+
("SELECT NAME FROM (SELECT table_name AS NAME, "
1052+
. "(SELECT COALESCE(s.seq + 1, 1) FROM _isc AS c "
1053+
. "LEFT JOIN sqlseq AS s ON s.name = c.table_name "
1054+
. "WHERE c.extra='auto_increment' "
1055+
. "AND c.table_schema = t.table_schema "
1056+
. "AND c.table_name = t.table_name "
1057+
. "AND length(t.table_name) > 0) AS AI FROM _ist AS t) WHERE AI > 3"),
10411058
];
10421059
foreach ($cases as $label => $sql) {
10431060
try {
10441061
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_NUM);
1045-
$ids = array_column($rows, 0);
1046-
sort($ids);
1047-
$got = json_encode($ids);
1048-
$expected = '[2,3]';
1049-
$tag = $got === $expected ? 'OK ' : 'BUG ';
1050-
echo "$tag $label got=$got expected=$expected\n";
1062+
$rows_pretty = array_map(fn($r) => '['.implode(',', array_map(fn($v) => $v === null ? 'NULL' : $v, $r)).']', $rows);
1063+
echo "ROW $label => " . implode(' ', $rows_pretty) . "\n";
10511064
} catch (Throwable $e) {
10521065
echo "FAIL $label -> " . $e->getMessage() . "\n";
10531066
}
@@ -1882,6 +1895,40 @@ jobs:
18821895
src = src.replace(old_c, new_c, 1)
18831896
open(path, 'w').write(src)
18841897
print('patched testCreateTableWithDefaultExpressions PRAGMA expectations for Turso')
1898+
1899+
# 20. testCreateTemporaryTable / testTemporaryTableHasPriorityOverStandardTable
1900+
# fail mid-suite under Turso with `I/O error: short read on
1901+
# page N` from the pager. With the default temp_store
1902+
# setting Turso creates a per-connection temp database on
1903+
# disk (via `tempfile::tempdir()` in core/connection.rs).
1904+
# The CI probe of the same SQL on a fresh PDO passes because
1905+
# the test order/state in the suite hits a code path the
1906+
# probe doesn't. Force `temp_store = MEMORY` at connection
1907+
# setup so temp tables go through MemoryIO instead, bypassing
1908+
# the buggy disk-temp pager path.
1909+
path = 'src/sqlite/class-wp-sqlite-connection.php'
1910+
src = open(path).read()
1911+
old_jm = (
1912+
"\t\tif ( $journal_mode && in_array( $journal_mode, self::SQLITE_JOURNAL_MODES, true ) ) {\n"
1913+
"\t\t\t$this->query( 'PRAGMA journal_mode = ' . $journal_mode );\n"
1914+
"\t\t}\n"
1915+
"\t}"
1916+
)
1917+
new_jm = (
1918+
"\t\tif ( $journal_mode && in_array( $journal_mode, self::SQLITE_JOURNAL_MODES, true ) ) {\n"
1919+
"\t\t\t$this->query( 'PRAGMA journal_mode = ' . $journal_mode );\n"
1920+
"\t\t}\n"
1921+
"\n"
1922+
"\t\t// Force in-memory temp store under Turso. The default disk-backed\n"
1923+
"\t\t// temp pager has a state-dependent `short read on page N` bug that\n"
1924+
"\t\t// breaks testCreateTemporaryTable mid-suite.\n"
1925+
"\t\t$this->query( 'PRAGMA temp_store = MEMORY' );\n"
1926+
"\t}"
1927+
)
1928+
assert old_jm in src, 'connection __construct journal_mode block not found'
1929+
src = src.replace(old_jm, new_jm, 1)
1930+
open(path, 'w').write(src)
1931+
print('patched connection setup to set PRAGMA temp_store = MEMORY')
18851932
PY
18861933
18871934
- name: Run PHPUnit tests against Turso DB

0 commit comments

Comments
 (0)