Skip to content

fix: refuse a CREATE on a name a table already holds - #479

Merged
robinskil merged 5 commits into
mainfrom
fix/default-table-survives-restart
Sep 9, 2026
Merged

robinskil merged 5 commits into
mainfrom
fix/default-table-survives-restart

Conversation

@robinskil

@robinskil robinskil commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two defects, both about the name a table holds.

CREATE EXTERNAL TABLE and CREATE VIEW registered over whatever held the name. A typo repointed a table or swapped a view with no warning. CREATE TABLE and CREATE MATERIALIZED VIEW refused the same name, so one typo destroyed a table and another failed, by statement. The admin API documented the behavior it did not have: the if_not_exists field of POST /api/admin/external-tables skips "instead of erroring", and nothing errored.

Beacon also registers an empty stand-in table at startup, so a JSON query without a from field reports no missing table. The stand-in ignored sql.default_table and always took the literal name default. A server with BEACON_DEFAULT_TABLE=observations therefore held a table called default and left observations missing.

Change

Every CREATE refuses a name that a table or a view holds. The modifiers the SQL reference already documents do the work:

Statement On a taken name
CREATE [EXTERNAL] TABLE Error
CREATE VIEW, CREATE MATERIALIZED VIEW Error
CREATE EXTERNAL TABLE IF NOT EXISTS Keeps the table, reports success
CREATE OR REPLACE EXTERNAL TABLE, CREATE OR REPLACE VIEW Overwrites

The paths that replace a provider on purpose register directly and do not change: a materialized-view REFRESH, an ALTER TABLE, and a crawler that re-registers a table it owns.

Startup registers the stand-in under the configured name, and only when the name is free. Drop it to take the name:

DROP TABLE "default";
CREATE EXTERNAL TABLE "default" STORED AS PARQUET LOCATION 'obs/';

That table then survives a restart. The error on the stand-in names it and tells you to drop it, because a table nobody made is a confusing thing to collide with.

Breaking

A script that repoints a table with a bare re-CREATE needs OR REPLACE, or a DROP TABLE in front.

Tests

7 tests in tests/default_table.rs, 5 in tests/external_tables.rs, 3 unit tests, 1 HTTP test.

The startup stand-in for the default table blocked the name. CREATE TABLE
and CREATE MATERIALIZED VIEW answered "already exists". The stand-in now
yields to the first real table, and startup skips it when a loaded table
holds the name.

The stand-in also ignored sql.default_table and always registered
"default", so BEACON_DEFAULT_TABLE=observations left that table missing.
@robinskil robinskil self-assigned this Sep 7, 2026
@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.29630% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.44%. Comparing base (b27e545) to head (7bf8f96).

Files with missing lines Patch % Lines
...eacon-core/src/schema_persistence/default_table.rs 88.57% 4 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #479      +/-   ##
==========================================
+ Coverage   83.41%   83.44%   +0.02%     
==========================================
  Files         374      375       +1     
  Lines       63237    63334      +97     
==========================================
+ Hits        52749    52848      +99     
+ Misses      10488    10486       -2     
Files with missing lines Coverage Δ
...eacon-db/beacon-core/src/schema_persistence/mod.rs 92.85% <100.00%> (+0.26%) ⬆️
...-db/beacon-core/src/schema_persistence/provider.rs 96.78% <100.00%> (+0.43%) ⬆️
...eacon-db/beacon-core/src/statement_plan/actions.rs 80.32% <100.00%> (+1.06%) ⬆️
...eacon-core/src/statement_plan/materialized_view.rs 92.30% <100.00%> (+1.00%) ⬆️
...acon-db/beacon-core/src/statement_plan/physical.rs 85.68% <100.00%> (+0.06%) ⬆️
...db/beacon-core/src/statement_plan/query_planner.rs 84.21% <ø> (ø)
...eacon-core/src/schema_persistence/default_table.rs 88.57% <88.57%> (ø)

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The admin external-table endpoint builds the CREATE statement itself, so
the stand-in has to yield there too.
The stand-in is an ordinary table again: a CREATE on its name fails, and
you drop it first. Only the startup rule changes, so the stand-in takes
the configured sql.default_table instead of the literal "default".

The error names the stand-in, because a table nobody made is a confusing
thing to collide with.
@robinskil robinskil changed the title fix: let a real table take the default table name fix: register the default table under the configured name Sep 7, 2026
Both registered over whatever held the name, so a typo repointed a table
or swapped a view with no warning, while CREATE TABLE refused the same
name. The admin API documented the erroring behaviour it never had.

IF NOT EXISTS and OR REPLACE now do the work the SQL reference already
describes. REFRESH, ALTER TABLE and the crawler register directly and
keep replacing on purpose.
@robinskil robinskil changed the title fix: register the default table under the configured name fix: refuse a CREATE on a name a table already holds Sep 7, 2026
@robinskil
robinskil merged commit 2922c47 into main Sep 9, 2026
7 checks passed
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.

1 participant