a95c8074f3
SQL identifiers are case-insensitive, so the engine resolves a table named in any capitalization — but our metadata tables (keyed by table_name / parent_table / child_table) and data/<table>.csv files use case-sensitive TEXT '=', so an operation naming a table in a different case than stored drifted: schema ops orphaned metadata rows, and a wrong-case insert/update/delete silently skipped the CSV write, losing the change on the next reload/rebuild. This contradicted ADR-0009's stated rule (case-insensitive resolution, case-preserving display). Add a canonical_table_name helper (resolve to the stored case via COLLATE NOCASE, excluding sqlite_* and __rdbms_* tables) and apply it at the entry of every table-naming executor — drop table, add/drop/rename column, change column type, add/drop constraint, add relationship, add index, rename table, insert/update/delete, and the advanced SQL DML — so the live schema, the metadata, and the CSV stay in step regardless of how the user capitalized the name. This also folds the internal-table guard into the same lookup (executors that previously lacked it now refuse __rdbms_*/sqlite_* as "no such table"). do_rename_table now accepts a case-variant source too. Column names remain matched case-sensitively (a wrong case is refused as "no such column" — strict, but never drifting), per the scope agreed with the user. Tests: tests/case_insensitive_names.rs — wrong-case rename-column, insert (survives a fresh rebuild — no data loss), add-column, drop-table, rename-table, and add-relationship, all with fresh-rebuild round-trips. Full suite 1909 passing / 0 failing / 1 ignored; clippy clean.
96 lines
3.7 KiB
Markdown
96 lines
3.7 KiB
Markdown
# ADR-0009: DSL command syntax conventions
|
|
|
|
## Status
|
|
|
|
Accepted
|
|
|
|
## Context
|
|
|
|
As the DSL grows, its commands need consistent surface
|
|
conventions. Without an explicit rule, every command would
|
|
invent its own way of expressing optional vs. required parts,
|
|
and the surface would drift toward an unreadable soup.
|
|
|
|
The decision is informed by experience from this iteration:
|
|
when we initially proposed `create table X --pk` the most
|
|
common form (a basic table with a primary key) required a `--`
|
|
flag, which is cosmetically wrong — `--` reads as "extra
|
|
option," and the most-used form should not look like one.
|
|
|
|
## Decision
|
|
|
|
The DSL surface follows three rules.
|
|
|
|
### 1. Required clauses use keyword grammar
|
|
|
|
Required parts of a command are written in plain words and
|
|
read like English. Examples:
|
|
|
|
- `create table <Name> with pk <name>(<type>)`
|
|
- `add column to table <Name>: <Name> (<Type>)`
|
|
- `drop table <Name>`
|
|
|
|
The `with` clause format is the canonical pattern for
|
|
attaching required structural information to an entity-creating
|
|
command, and is reusable: future iterations may add `with
|
|
index`, `with check`, etc. Multiple `with` clauses on the same
|
|
command are allowed in principle.
|
|
|
|
### 2. Optional flags use `--prefix`
|
|
|
|
Flags signal "I am asking for an extra capability or
|
|
non-default behaviour." Examples planned for later iterations:
|
|
|
|
- `add 1:n relationship on Customers.Id=Orders.CustId --create-fk`
|
|
(auto-creates the FK column instead of requiring it to exist)
|
|
- *(future)* `--rename-on-clash`, `--no-strict`, etc.
|
|
|
|
A user reading "with pk id:serial" sees only what's needed; a
|
|
user reading "...with pk id:serial --some-flag" sees that they
|
|
have asked for something beyond default. The visual distinction
|
|
is intentional.
|
|
|
|
### 3. One sigil only — `:` for the simple-mode advanced escape
|
|
|
|
Per ADR-0003, prefixing a single line with `:` in simple mode
|
|
treats that one submission as if it were entered in advanced
|
|
mode. This is the only sigil in the system. App-level commands,
|
|
DSL commands, and SQL all use plain words.
|
|
|
|
### Lexical rules
|
|
|
|
- **Keywords are case-insensitive.** `CREATE TABLE Customers
|
|
WITH PK email:TEXT` is equivalent to `create table Customers
|
|
with pk email:text`.
|
|
- **Identifiers are case-preserving.** `Customers` and
|
|
`customers` are different identifiers if a backend would
|
|
treat them as such (we follow SQLite's case-insensitive
|
|
identifier rules at the schema level but preserve the user's
|
|
written casing in display). Concretely, a command may name a
|
|
table in any capitalization (the engine resolves it
|
|
case-insensitively); every executor **canonicalizes a
|
|
user-supplied table name to its stored case** before touching
|
|
metadata or CSV (`canonical_table_name` in `db.rs`), so the
|
|
live schema, the metadata tables, and the `data/<table>.csv`
|
|
files stay in step regardless of how the user capitalized the
|
|
name. (Column names are matched case-sensitively and a wrong
|
|
case is refused as "no such column" — strict, but never
|
|
drifting.)
|
|
- **Whitespace is liberal.** Any amount of horizontal whitespace
|
|
between tokens is accepted, including around punctuation
|
|
(`,`, `:`, `(`, `)`).
|
|
|
|
## Consequences
|
|
|
|
- The basic, most-common form of any command remains readable
|
|
and free of cosmetic punctuation. New users see only words.
|
|
- Optional adornments are visually distinct, encouraging
|
|
discoverability of advanced features without forcing them on
|
|
beginners.
|
|
- New commands inherit a uniform shape: keyword-based clauses
|
|
for required parts, `--` flags for opt-ins. Drift is bounded
|
|
by this rule.
|
|
- The grammar implementation (`chumsky`) maps cleanly onto this
|
|
structure: a `with_clause` rule can be reused across
|
|
commands, and flag parsing has a single representation when
|
|
it lands. |