Skip to content

Commit d69d148

Browse files
committed
Try Turso pager patch: treat 0-byte page read as empty (SQLite-compat)
The previous Turso patch (effective_temp_store always Memory) also crashed testReconstructTable, so even compile-time changes to temp table routing trip Turso's pager hypersensitivity. Try a more targeted Turso patch: in `sqlite3_ondisk::begin_read_page`, match SQLite's behaviour and treat a 0-byte read as a zero-filled page instead of returning ShortRead. This is the root cause of the "short read on page N: expected 4096 bytes, got 0" error that breaks the temp-table tests; the existing code already has an empty-buffer fallback path for when `allow_empty_read` is true — we just route the bytes_read==0 case there unconditionally. Truncated reads (1..buf_len-1) are still rejected.
1 parent ac53791 commit d69d148

1 file changed

Lines changed: 32 additions & 37 deletions

File tree

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

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -595,49 +595,44 @@ jobs:
595595
print('patched CreateTrigger to preserve original SQL text')
596596
PY_TRIGGER_SQL
597597
598-
# Force temp tables to use MemoryIO under all conditions, to
599-
# bypass the disk-temp pager's state-dependent
600-
# `short read on page N` bug that breaks
601-
# testCreateTemporaryTable + testTemporaryTableHasPriorityOverStandardTable.
602-
#
603-
# Setting this via runtime PRAGMA temp_store = MEMORY crashes
604-
# testReconstructTable in the pager (memory:
605-
# project_turso_testreconstructtable_fragile). Instead, patch
606-
# `effective_temp_store` so it returns `TempStore::Memory`
607-
# unconditionally — a compile-time change that doesn't go
608-
# through `set_temp_store` / `bump_prepare_context_generation`,
609-
# so testReconstructTable's prepared-statement cache is
610-
# untouched.
611-
python3 - <<'PY_TEMP_MEMORY'
612-
p = 'core/connection.rs'
598+
# Treat 0-byte page reads as empty pages instead of erroring.
599+
# SQLite's pager treats reads past EOF as zero-filled; Turso
600+
# bails with `short read on page N: expected 4096 bytes, got 0`,
601+
# which surfaces as a state-dependent failure mid-suite for
602+
# testCreateTemporaryTable and testTemporaryTableHasPriorityOverStandardTable.
603+
# Drop the !allow_empty_read special case for the bytes_read==0
604+
# branch so the existing empty-buffer path runs instead — match
605+
# SQLite's "zeros past EOF" behaviour.
606+
python3 - <<'PY_PAGER_EMPTY_READ'
607+
p = 'core/storage/sqlite3_ondisk.rs'
613608
s = open(p).read()
614609
old = (
615-
" fn effective_temp_store(&self) -> crate::TempStore {\n"
616-
" let temp_store = self.get_temp_store();\n"
617-
" #[cfg(feature = \"fs\")]\n"
618-
" {\n"
619-
" temp_store\n"
620-
" }\n"
621-
" #[cfg(not(feature = \"fs\"))]\n"
622-
" {\n"
623-
" let _ = temp_store;\n"
624-
" crate::TempStore::Memory\n"
625-
" }\n"
626-
" }\n"
610+
" // Handle truncated database files: if we read fewer bytes than expected\n"
611+
" // (and it's not an intentional empty read), return a ShortRead error.\n"
612+
" if bytes_read == 0 {\n"
613+
" if !allow_empty_read {\n"
614+
" tracing::error!(\"short read on page {page_idx}: expected {buf_len} bytes, got 0\");\n"
615+
" page.clear_locked();\n"
616+
" return Some(CompletionError::ShortRead {\n"
617+
" page_idx,\n"
618+
" expected: buf_len,\n"
619+
" actual: 0,\n"
620+
" });\n"
621+
" }\n"
622+
" } else if bytes_read != buf_len as i32 {\n"
627623
)
628624
new = (
629-
" fn effective_temp_store(&self) -> crate::TempStore {\n"
630-
" // Always use MemoryIO for temp tables: avoids a state-dependent\n"
631-
" // `short read on page N` bug in the disk-temp pager that breaks\n"
632-
" // testCreateTemporaryTable mid-suite under PHPUnit.\n"
633-
" let _ = self.get_temp_store();\n"
634-
" crate::TempStore::Memory\n"
635-
" }\n"
625+
" // Match SQLite: a read of 0 bytes (page beyond EOF / not yet\n"
626+
" // written) is treated as a zero-filled page instead of an error.\n"
627+
" // Truncated reads (1..buf_len-1 bytes) are still rejected.\n"
628+
" if bytes_read == 0 {\n"
629+
" // fall through to the empty-buffer path below\n"
630+
" } else if bytes_read != buf_len as i32 {\n"
636631
)
637-
assert old in s, 'effective_temp_store block not found'
632+
assert old in s, 'sqlite3_ondisk short-read block not found'
638633
open(p, 'w').write(s.replace(old, new, 1))
639-
print('patched effective_temp_store to always return TempStore::Memory')
640-
PY_TEMP_MEMORY
634+
print('patched sqlite3_ondisk to treat 0-byte page read as empty (SQLite-compatible)')
635+
PY_PAGER_EMPTY_READ
641636
642637
echo '--- Patched stub! macro ---'
643638
sed -n '/macro_rules! stub/,/^}$/p' sqlite3/src/lib.rs

0 commit comments

Comments
 (0)