# 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.