Skip to content

fix: CREATE TABLE IF NOT EXISTS ignores its guard -- duplicate sqlite_master row + leaked page (#697) - #717

Open
dpsiderius wants to merge 2 commits into
fix/687-emit-autoindexfrom
fix/697-if-not-exists-guard
Open

dpsiderius wants to merge 2 commits into
fix/687-emit-autoindexfrom
fix/697-if-not-exists-guard

Conversation

@dpsiderius

Copy link
Copy Markdown
Contributor

Stacked on #687 (fix/687-emit-autoindex) — merge that first.

What

compile_statement now checks the catalog before emitting for all three
CREATE ... IF NOT EXISTS forms and both DROP ... IF EXISTS forms. A
satisfied guard compiles to a no-op (Init -> Halt, no page allocation,
no schema-cookie bump). Bonus fix found while proving this: an unguarded
duplicate CREATE TABLE used to silently succeed (rc=0); it now fails
with the oracle's wording (table t already exists).

Before/after (measured against the pinned oracle)

Before: running CREATE TABLE IF NOT EXISTS t (...) a second time
created a second sqlite_master row for the same table and left a
leaked page — integrity_check then reports "Page N: never used" and
"wrong # of entries in index" on the file. After: the second run is a
true no-op — rc 0, exactly one sqlite_master row, no leaked page,
integrity_check ok, prior data intact. DROP TABLE/INDEX IF EXISTS on
a missing object confirmed already-correct (no-op) alongside the fix.

Test plan

  • Oracle-diffed: guarded re-create is a no-op with clean integrity_check;
    unguarded duplicate still errors like the oracle; guarded/unguarded
    DROP matrix.
  • make test, make lint, make test-corpus, make check-mvl-limit, make check-mod-files, make assurance all pass.

Refs: 001/Req-*, #685, #687
Closes #697

spend: matched estimate

…_master row + leaked page (#697)

CREATE TABLE/INDEX/VIEW IF NOT EXISTS and DROP TABLE/INDEX IF EXISTS
parsed their guard flag but compile_statement never consulted it: a
second CREATE TABLE IF NOT EXISTS against an existing table appended a
second sqlite_master row and allocated (then abandoned) a root page,
which stock sqlite3 reports as "Page N: never used" -- corrupting a
database this crate itself created on the very next startup, since
"IF NOT EXISTS on catalog bootstrap" is the idiom's whole purpose.
Also: a duplicate CREATE TABLE without the guard silently succeeded
too, which it must not.

compile_statement now checks schemas/views (or the index list, for
CREATE/DROP INDEX) before emitting: a satisfied guard compiles to a
new compile_noop() (Init -> Halt, no page allocated, no schema-cookie
bump); an unsatisfied guard on a CREATE still errors, matching the
oracle's wording ("table t already exists" / "index i already exists"
/ "view v already exists"); DROP ... IF EXISTS on a missing object is
now the same no-op instead of propagating NoSuchTable/NoSuchIndex.

Refs: 010/Req-8, #678, #695
PR #717's catalog existence checks named the kind of the *statement*
(CREATE TABLE/VIEW/INDEX) rather than the kind of the *existing* object,
so a cross-kind clash reported the wrong noun in both directions (tables
and views share a namespace). It now names the existing object's kind,
oracle-matched against 3.53.4 for all four table/view directions.

Also fixes the previously-missing index-namespace checks: CREATE
TABLE/VIEW against an existing index now reports "there is already an
index named X", and CREATE INDEX against an existing table or view
reports "there is already a table named X" (the oracle's wording even
when the clash is with a view) — both measured to NOT be suppressed by
IF NOT EXISTS, unlike the same-kind clash it does guard.

spend: matched estimate (small oracle-diff/message fix)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: CREATE TABLE IF NOT EXISTS ignores its guard — duplicate sqlite_master row + leaked page, corrupts the file on second run

1 participant