Skip to content

feat: real table and column names, and a DuckDB session that opens no file - #20

Merged
YoanSallami merged 5 commits into
mainfrom
feat/real-table-and-column-names
Oct 14, 2026
Merged

YoanSallami merged 5 commits into
mainfrom
feat/real-table-and-column-names

Conversation

@YoanSallami

@YoanSallami YoanSallami commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

A table and its columns keep the names the database gives them; the predicate declared over them is what rules use.

OrderItems(id:, unit_price:) :- `public.Order Items`(Id: id, `Prix unitaire (€)`: unit_price);
  • Quoted table names take any letter, spaces and -'&#@+%:() next to digits and _. Each part is quoted for the engine, so a sign in it is part of the name.
  • Still refused in a table name: what could end the quoting (", a backtick, \), make a path or a URL (/, *, ?) or name a variable ($), plus ;, ,, control characters and leading or trailing spaces.
  • Column names with an uppercase letter are quoted. PostgreSQL folds unquoted names, so createdAt was not found.
  • A backticked field names the column between the backticks when a table is read. `Order Note` used to be written with its backticks.
  • The DuckDB session of execute is locked once its files are loaded: no external access, no extension installed or loaded on demand, no Python variable taken for a table, configuration locked.

Why the DuckDB lock

DuckDB opens the file a table name designates. On 1.5.3, with a secret.<ext> file in the working directory, SELECT * FROM "secret"."csv" returns its rows; the same holds for .tsv, .json, .jsonl, .ndjson, .parquet, .db and .csv.gz. .xlsx and .avro trigger an extension download. A name matching a Python variable of the caller is scanned too. This was reachable before this change through a bare secret.csv(x:).

Behaviour changes to review

  • The SQL of any program naming a column with an uppercase letter now quotes that column, on every engine.
  • `(SELECT 1 AS x)` and `a'b` are now table names (quoted identifiers the engine does not find) instead of verifier errors. Three tests were changed for this: injection/table_name_subquery.l, verifier2/error_table_name_quote.l, verifier2/error_table_name_sql.l, and the unit test sql_as_a_table_name_is_refused.
  • A program run through execute on DuckDB can no longer read a file other than the loads given to it.

Tests

  • 16 new attempts in tests/programs/injection, with a new # Expect: run-error <Pred> <text> kind that runs one on DuckDB and SQLite.
  • tests/cli/test_file_access.py: 46 cases over the file formats, bare and quoted names, a caller's variable, and a control showing plain DuckDB does read the file.
  • cargo test --lib: 861 passed. Golden compiler, parser, verifier and search suites pass (run with --no-default-features; the integration tests do not link with the Python feature on macOS here).
  • pytest tests/cli: 5961 passed, 34 skipped.
  • tests/e2e on live engines: the whole suite on SQLite, DuckDB and PostgreSQL (40,982 passed); the injection programs on Trino, Presto and Spark (all passed); a 1-in-25 sample of the rest on Trino and Presto (1,256 passed) and on Spark (628 passed). The full suite on Trino, Presto and Spark was not run (about five hours here), nor anything on BigQuery or a real Databricks.

Databricks: a table name reads no file

Spark reads format.path as a file when no table has that name. On Spark 3.5.3, text.secret (bare or in backticks) returned the lines of a file in the server's working directory, and csv.secret opened it as CSV; this was reachable before this change. On the databricks dialect, a two-part name whose first part is a file format (text, csv, json, parquet, orc, avro, xml, binaryfile, delta, jdbc, ...) is now refused at compile time, with a message asking to name the table with its catalog. A schema that is really called text or csv is therefore only reachable as catalog.schema.table.

Not covered

  • SQL compiled with compile and run on a caller's own DuckDB connection is not protected by the session lock; Lemma locks its own connections in SynaLinks/lemma#3.
  • The Databricks refusal is checked by a unit test and by hand against Spark, not by a program in tests/programs (those run on DuckDB and SQLite).

🤖 Generated with Claude Code

… file

A table and its columns keep the names the database gives them; the
predicate declared over them is what rules use:

    OrderItems(id:, unit_price:) :- `public.Order Items`(Id: id, `Prix unitaire (€)`: unit_price);

- A quoted table name takes any letter, spaces and -'&#@+%:() next to
  digits and '_'. Each part is quoted for the engine, so a sign in it is
  part of the name. What could end the quoting (", `, \), make a path or
  a URL (/, *, ?) or name a variable ($) stays refused.
- A column name with an uppercase letter is quoted: an engine that folds
  unquoted names (PostgreSQL) did not find `createdAt`.
- A field in backticks names the column between them when a table is
  read: `Order Note` was written with its backticks.
- The DuckDB session of `execute` is locked once its files are loaded:
  no external access, no extension installed or loaded on demand, no
  Python variable taken for a table, configuration locked. DuckDB
  otherwise opens the file a table name designates (`secret.csv`,
  "secret"."parquet", also .tsv, .json, .jsonl, .ndjson, .db, .csv.gz).

Tests: 16 more attempts in tests/programs/injection (a new
`# Expect: run-error` runs one on every engine), tests/cli/test_file_access.py
for the file formats, and the unit tests of the names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yoan and others added 4 commits October 9, 2026 00:02
Spark reads `format.path` as a file when no table has that name:
`text.secret` (bare or in backticks) returned the lines of a file of the
server's working directory, `csv.secret` opened it as CSV. On the
databricks dialect, a two-part name whose first part is a file format
(text, csv, json, parquet, orc, avro, xml, binaryfile, delta, jdbc, ...)
is refused: such a table is named with its catalog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mark goes from 86.5% to 76% of its square canvas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Synalog fences are named `synalog` and left to
javascripts/synalog-highlight.js: Synalemma's tokenizer and colours, and
lines wider than 80 characters formatted along the code. The other
languages take the same colours through the theme's variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
axioms.ttl, axioms.l and axioms.log presented a synalog import command
that no longer exists; no page linked them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@YoanSallami
YoanSallami merged commit 3721258 into main Oct 14, 2026
29 of 44 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