22bec61d11a946b96b959a38c24a5f901133e73b
96 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
22bec61d11 |
feat(ui): scroll the focused sidebar panel + refine the nav overlay (#21, ADR-0046 DC3 + DC2)
DC3 — navigation-mode scroll: the focused Tables / Relationships panel scrolls (Up/Down by a line, PageUp/PageDown by its visible-row count). Per-panel offsets are clamped to content at render time, and the renderer reports each panel's visible rows for paging — mirroring the output panel's scroll. render_items_panel / render_relationships_panel take &mut App, count their rows, and store+clamp the offset before building the borrowing lines. DC2 refinement: the expand-on-focus overlay now clears only the sidebar strip plus a one-column gutter, leaving the base output/input/hint visible (unchanged) to the right rather than blanking the whole area — truer to "underneath keeps its layout", with the gutter keeping the cut-off edge clean (chosen after eyeballing both variants). ADR DC2 and the overlay snapshot updated to match. Tests: line/page scroll move only the focused panel and clamp; the render clamps a past-the-end offset so the last row stays visible. |
||
|
|
c9da6ff785 |
feat(ui): Ctrl-O navigation mode — peek + expand the schema sidebar (#21, ADR-0046 DC1/DC2/DC4)
Ctrl-O enters a navigation mode orthogonal to the input mode, cycling
focus Input -> Tables -> Relationships -> Input (Esc exits). While a
sidebar panel is focused the sidebar is revealed (a peek, even when
width-hidden) and drawn as an expanded 45-column overlay over a cleared
main area, so the schema is browsable without the cramped 26-column
unfocused width. The focused panel gets an accent border.
Routing lives in the main key handler after the modal gate, so Ctrl-O
and nav keys are inert while a modal is open; in nav mode every
non-navigation key (printable/Enter/Tab/Backspace/...) is inert because
the input is occluded. Scroll keys (Up/Down, PageUp/PageDown) are
reserved for DC3 (next).
New App state: NavFocus { Input, SidebarTables, SidebarRelationships }.
Tests: the focus cycle, Esc exit, input-keys-inert, overlay reveal +
expansion, the accent-border style, and an overlay snapshot.
|
||
|
|
94825d0f36 |
feat(ui): relationships sidebar panel + schema data (#21, ADR-0046 DB2/DB4)
The left column now stacks a Tables panel over a Relationships panel.
Each relationship renders as three narrow lines — its name, then the
endpoints broken at the arrow (Customers.id -> / indented
Orders.customer_id) — ellipsized past the inner width. The panel is
content-sized within [5 rows ("(none)" when empty), half the column];
the Tables panel keeps the rest (>=3 rows). Phase C adds focus+scroll
for content beyond the cap (clipped for now).
Data path: a new worker Request::ReadAllRelationships +
Database::read_all_relationships returns full RelationshipSchema
records; the runtime posts them via a RelationshipsRefreshed event
alongside the schema-cache refresh, and the App holds them in a new
`relationships` field.
ADR deviation (recorded in ADR-0046 DB2 + index): DB2 specified this
data on SchemaCache; it lives on the App instead — SchemaCache is
walker/completion-facing and needs only relationship names (untouched),
while the full records are UI-only, so App is the cleaner home and it
avoids editing ~23 SchemaCache literals. No behavioural difference.
Tests: panel-height bounds, the three-line render, the empty "(none)"
case, a snapshot, read_all_relationships end-to-end (real DB via the
m:n junction), and the event->field handler.
|
||
|
|
e0b9470feb |
feat(ui): horizontal-scroll long input so the cursor stays visible (#23, ADR-0046 DA3)
A command longer than the input field used to clip silently at the right edge, hiding the cursor and the command tail. Now the single logical input line scrolls horizontally to keep the cursor in view, with muted `<` / `>` markers at the reserved edge columns signalling hidden content on either side. The offset is a pure function of (line length, cursor column, field width, previous offset) — input_scroll_offset — so the view only moves when the cursor would leave the window, and one column is held on each side for the markers so a marker never hides the cursor. The stored App::input_scroll_offset resets when the buffer is replaced wholesale (submit, history recall). The ADR-0027 6-column indicator reserve is preserved. Tests: pure-offset cases, tail-visible + head-visible render checks, and the reset-on-submit/history check. One layout snapshot now shows a long command's tail instead of its clipped head. |
||
|
|
8bd43ccadf |
feat: create m:n relationship convenience command (C4, ADR-0045)
`create m:n relationship from <T1> to <T2> [as <name>]` generates a
junction table with one FK column per parent PK column ({table}_{pkcol},
typed via fk_target_type), a compound PK over them, and two CASCADE 1:n
relationships -- all in one do_create_table call = one undo step.
Auto-named {T1}_{T2} (optional `as`), both modes, compound-parent PKs
supported (ADR-0043). Self-referential m:n / PK-less parent / internal
junction name / name collision all refused.
Wired across every surface: grammar (separate CREATE_M2N node), worker
executor, runtime dispatch, completion ("m:n" composite), hints,
highlighting, help + usage catalog + disambiguator, and the advanced-mode
DSL->SQL teaching echo (render_create_m2n, round-trips as valid SQL).
Generalized/fixed framework assumptions the build + two /runda passes
surfaced (all behaviour-preserving for existing commands):
- simple-mode dispatch committed simple.first() unconditionally -> tries
candidates, so `create table` no longer shadows `create m:n`.
- the completion continuation-merge was advanced-only -> runs in simple
mode too when an entry word has >1 DSL form (gated simple_count>1).
- do_create_table now rejects internal `__rdbms_*` names (closes a
pre-existing hole on the DSL create-table path too, not just m:n).
- usage disambiguator now recognizes the `m:n` opener.
Tests: 14 integration (tests/it/m2n.rs), 7 typing-surface matrix, echo /
highlight / usage / internal-name units. Closes C4.
2237 pass / 0 fail / 1 ignored. Clippy clean.
|
||
|
|
0a7612efe2 |
feat: comprehensive logging across parser, app, persistence, runtime (X1)
Completes the X1 full sweep started in
|
||
|
|
a0ee32393f |
feat: show table renders relationships as compact diagrams (ADR-0044)
show table <T> and add/drop relationship echoes now render the focal structure box plus a Relationships section of compact stacked connector diagrams (child-left/parent-right, n…1, actions); incidental DDL echoes keep the prose References:/Referenced by: form. Selected by command in handle_dsl_success via the "relationship-relevant" reach. - output_render.rs: render_structure refactored into section helpers (box/prose/index/constraint), byte-identical output; new render_structure_with_diagrams + compact-box rendering - app.rs: handle_dsl_success routes ShowTable/Add/DropRelationship to the diagram path, others to prose - fixes: eager widths[1] index on compact (1-col) boxes; body-cell padding under title-widening (name wider than columns) Tests: unit + snapshot + integration; add-relationship echo test updated to the diagram form. Full suite 2203 pass / 0 fail / 1 ignored; clippy clean. V1 still [/] (compound routing + self-ref remain). |
||
|
|
cad90ec4a5 |
feat: show relationship <name> renders a styled two-table diagram (ADR-0044)
The first wired slice of relationship visualization (V1). `show relationship <name>` now renders the relationship as two full structure boxes joined by a width-jogging connector (child-left / parent-right, n…1 cardinality, on delete/update actions), styled App-side, with a vertical-stack fallback for narrow terminals. - db.rs: RelationshipDiagramData + show_relationship worker path (structured data: the relationship + both endpoint TableDescriptions) - runtime.rs: named relationships route to the structured outcome (boxed); other show <kind> forms stay prose - app.rs/event.rs/ui.rs: DslShowRelationshipSucceeded rendered App-side; new diagram OutputStyleClass variants; App::last_output_width from ui.rs - output_render.rs: styled Seg layout engine (boxes, connector routing, side-by-side + vertical), composing the ADR-0016 box primitives Tests: 4 unit + 4 integration; full suite 2201 pass / 0 fail / 1 ignored; clippy nursery clean. requirements.md V1 stays [/] (show table diagrams, compound routing, DDL-echo wiring remain). |
||
|
|
b14f0199e9 |
refactor: relationship model to column lists for compound FK (ADR-0043)
Move the FK column fields String->Vec<String> through all six layers (AddRelationship/SqlForeignKey AST, RelationshipSchema, metadata, project.yaml, ReadForeignKey, RelationshipEnd). Metadata stores comma-joined lists in the existing TEXT cells; project.yaml endpoints now columns: [a, b] (house style). Executor logic is multi-column ready: resolve_fk_parent_columns (full-PK F-A + auto-expand F-D), per-pair type-compat, schema_to_ddl multi-column emission, pragma FK read grouped by id, auto-name + --create-fk per-column, multi-column teaching echo. Single-column behaviour preserved (one-element vecs); all 2181 tests green. The grammar to parse multi-column input lands next. |
||
|
|
1d898adf00 |
feat: V5a show relationship/index <name> detail views
Fold the singular per-item forms into Command::ShowList { kind,
name: Option<String> } (name: Some = one item). Two grammar
branches reuse the relationship/index completion sources; worker
do_show_one renders a labelled detail block or a friendly
"No ... named X." line, reusing the V5 render path. Help +
parse-usage entries, two ADR-0042 near-miss rows, 5 integration
tests. Mark V5a [x] — V5's [<name>] clause now complete.
|
||
|
|
757711f2bf |
feat: H3 help <command> per-command detail + general reference
HELP node takes an optional single-word topic (BarePath);
AppCommand::Help { topic }. note_help_topic renders the help
block(s) of every command sharing that entry word (so `help
create` covers both create forms), plus `help types` and a
friendly "no help for X" pointer for unknown topics. Full help
gains a detail-hint footer. Catalogued help.detail_hint /
help.unknown_topic; parse-error matrix updated (help now takes a
topic, so the near-miss is the multi-word case). 9 integration
tests in tests/it/help_command.rs. Mark H3 [x].
|
||
|
|
8dec784080 |
feat: V5 show tables / relationships / indexes list commands
Add the list-all show family as one Command::ShowList { kind }
variant. A read-only worker show_list formats count-headed lists
(reusing do_list_tables / read_all_relationships /
read_table_indexes, so it never drifts from the items panel);
internal __rdbms_* tables excluded. Help + parse-usage entries
added; 10 integration tests in tests/it/show_list.rs.
Mark V5 [x]. Split the singular show relationship/index <name>
detail forms (the [<name>] half) into a new tracked V5a [ ] item
rather than leaving them as an untracked footnote.
|
||
|
|
1d4923b15b |
fix: H1a G3 advanced usage shows all valid forms; complete near-miss matrix (ADR-0042)
The /runda DA pass found G3 over-corrected: advanced-mode `create`/`drop` showed SQL forms only, hiding the DSL fallback forms that are valid input in advanced mode (verified: `create table Foo with pk`, `drop column …` parse and dispatch). Per the user decision, the advanced usage block now shows every form valid in the mode, SQL-primary first, then the DSL fallback forms — a usage hint must never hide working input. Simple mode unchanged (DSL forms only). Matrix completion (closing the residual coverage tail): - arg-less app commands (help/rebuild/new/load/undo/redo/export/import) audited + locked — all reject trailing junk with "expected end of input" + usage. - committed multi-forms (add index/constraint/1:n relationship, drop index/constraint/relationship, show table, change column, create index, alter table add/drop) audited + locked in near_miss_matrix_committed_multiforms — each renders its own form-specific missing-keyword message + usage. Also from the DA pass: - G2 distinct+all detector empirically verified unique to projection start (no misfire at count( / union / union all / select distinct). - stale `chumsky` comment removed (app.rs import handler). - ADR-0042 Implementation-outcome section records G1–G4, the user-confirmed G3 decision, and the now-complete matrix coverage. Full suite green (lib 1578 / it 387 / typing_surface_matrix 192); clippy clean. |
||
|
|
649fdcb38e |
feat: H1a parse-error gaps G2–G4 + advanced near-miss matrix (ADR-0042)
Close the three remaining ADR-0042 triage gaps, each test-first, and lock the advanced-mode near-miss matrix. G2 — bare `select` dumped the 14-item expression first-set. Collapse it to "a projection: `*`, a column, or an expression" in the error message only (parser::format_walker_error), detected by the joint `distinct`+`all` quantifier signature unique to a projection start. Render-only: completion/hints still expand the full set (typing-surface matrix unchanged). G3 — the usage block was mode-blind: advanced `create table` showed the DSL `create table … with pk …` template. usage_key(s)_for_input gain mode-aware `_in_mode` variants selecting candidates by CommandCategory; render_usage_block and the typing-time ambient usage thread the submission mode. Advanced `create` now shows both SQL forms. A fallback covers shared SQL nodes (insert/update/delete) that declare no usage_ids of their own — without it they regressed to the available-commands fallback (caught by the new advanced matrix). G4 — `with` borrowed `select`'s usage template; give it its own parse.usage.with CTE template. Tests: new near_miss_matrix_advanced_mode (12 SQL-surface cases incl. the available-commands regression guard) + per-gap tests; removed the temporary baseline_dump. Full suite green (lib 1578 / it 386 / typing_surface_matrix 192); clippy clean. |
||
|
|
d0c8f9d5d2 |
feat: copy the output panel to the system clipboard (#11)
New app-level `copy` / `copy all` / `copy last` command (ADR-0041). Delivery is OSC 52 *and* a best-effort native write (arboard), always both — OSC 52 acceptance is undetectable, so a true fallback can't be built. Payload is the panel's plain text exactly as rendered (tags, ✓/✗, box-drawing), drift-locked to render_output_line. arboard added --no-default-features (X11-only; OSC 52 covers Wayland). Amends ADR-0003's command registry; requirements V6. |
||
|
|
4cd574b909 |
feat: persist & restore per-project input mode (#14)
The input mode always started in simple; a learner who quit in advanced had to re-toggle every launch. Store the mode per-project in project.yaml (project.mode:, optional, default simple) and restore it on every open. Mode is live UI state, not schema: the worker stamps the current mode into project.yaml on every write, so a later command rewrites the live value rather than clobbering it — no db round-trip needed. The mode is persisted on unload (quit + project switch) so the mode you leave a project in is always what reopens; the `mode` command also persists immediately. A switch saves the outgoing mode, then restores the incoming project's stored mode. New --mode simple|advanced CLI flag (precedence --mode > stored > simple; combines with --resume). A teacher can ship a project that opens in advanced mode and export it to students (the mode travels in the zip). ADR-0015 Amendment 1; ADR-0003 note; help banner; requirements L1b. |
||
|
|
8311de44a8 |
feat: replace the [ok] summary line with a ✓/✗ echo marker
An audit of the command surface found the `[ok] <verb> <subject>` summary line duplicated the echo line above it everywhere; its only unique signal was success-vs-error. Retire it: a command's echo line now resolves from `running: <input>` to `<input> ✓` / `<input> ✗` on completion, and the symmetric `"<verb> <subject>" failed:` prefix is dropped (only the reason remains). Content lines (row counts, structure, plan tree, teaching echo) are unchanged. Echo lines carry an EchoStatus; executed commands push Pending and resolve the oldest-pending echo on their result event (FIFO worker — correct under interleaving). Parse-time and pre-flight rejections are not executed and keep their running: + caret rendering. App-command [ok] lines (rebuild/export/replay) are payload-bearing and untouched. ADR-0040. |
||
|
|
10e5197c19 |
feat: bring simple-mode insert arity diagnostics to parity with advanced
A wrong-count simple-mode insert now shows the friendly per-column arity message at typing time (instead of a bare "expected `,`/`)`") and is blocked from dispatch at submit — unifying simple and advanced mode onto the one ADR-0027 model (structural parse + ERROR diagnostic), where they had diverged. Grammar: a simple-mode-only arity gate (dsl_insert_value_list) routes a wrong-count DSL insert tuple to the type-blind fallback so it matches structurally and the per-tuple arity diagnostic fires. The gate is gated to simple mode, so advanced behaviour is unchanged. count_tuple_values and the target-column selection (insert_target_columns) are now shared by both grammars. Diagnostic: dml_insert_arity_diagnostics is mode-aware — advanced Form B expects all columns; simple Form B/C expects the user-fillable columns (serial/shortid auto-fill). It counts the DSL Form A role and scans the keyword-less Form C tuple. New catalog keys name the fillable/auto split and the all-auto-table case. Submit: a wrong-count DSL insert now parses Ok + carries the ERROR diagnostic, so a unified Ok-arm pre-flight (dsl_insert_count_mismatch_notes) blocks dispatch and teaches; the previous Err-arm note retires. advanced_alternative_note's gate now reads the validity verdict so it still fires for the parse-Ok-with-error shape. Docs: ADR-0036 Amendment 2 (+ README index) and requirements.md H1a. |
||
|
|
c12ed1da9a |
fix: INSERT Form B value-count UX (ADR-0033 Amendment 5)
Three layered fixes for advanced/simple-mode positional INSERT
value-count mismatches (e.g. `insert into T values (...)` with the
wrong number of values for T's column count), plus ADR-0033
Amendment 5 recording the gate refinement.
Walker diagnostic (dml_insert_arity_diagnostics): the function's own
doc-comment recorded the no-column-list (Form B) case as deferred.
This commit closes that gap. Form B mismatches now emit a new
diagnostic.insert_arity_mismatch_form_b ERROR per offending tuple,
keyed off the target table's column count from the schema cache. The
[ERR] validity indicator (ADR-0027) lights up at typing time for the
reported scenario, no longer needing a submit.
Cross-mode pointer gate (advanced_alternative_note): refactored from
a hand-rolled Form B count check to a single input_verdict_in_mode(
input, schema, Mode::Advanced) call. The pointer fires only when the
verdict is None — the ADR-0027 sense of "valid". Any future static
check added to the verdict pipeline participates automatically; no
per-feature maintenance.
Teaching notes for the value-count cases users can hit before the
indicator turns red:
- simple-mode submit: insert.form_b_extra_values_note covers under-,
in-window, and over-supply against the Form B contract; suppressed
when the cross-mode pointer fires (to avoid parallel advice).
- advanced-mode dispatch pre-flight:
insert.form_b_positional_count_mismatch_note catches a submitted
mismatch with a teaching message before the engine produces its
raw NOT-NULL / type error.
The advanced_mode.also_valid_sql pointer wording was reworked to
"trying to write SQL? switch with `mode advanced`, or prefix `:` to
run once". One insta snapshot regenerated.
ADR-0033 Amendment 5 records the gate change: Amendment 3's "would
parse in advanced mode" now reads as "valid in advanced mode" in the
explicit verdict-is-None sense, with the precise definition spelled
out so future readers can't drift back to the syntactic-only reading.
ADR-0000 index entry updated; docs/requirements.md H1a citation added
listing the three new pedagogical strings.
Tests added (8): four walker arity tests (under-supply, over-supply,
match, unknown-table); two app-level teaching-note tests for the
sibling cases (under-supply, over-supply beyond total); one pointer-
gate unit test pinning the bug-case suppression; one gate-precedence
test ensuring only one advice line per error. Existing
simple_mode_submit_of_sql_construct_appends_advanced_pointer updated
to use a known schema (the new validity gate requires it).
Full suite: 2031 passed, 0 failed, 0 unexpected skips. Clippy clean.
|
||
|
|
6840b928f9 |
test: /runda round 2 — pin TeachingEcho kind + cat-3 prose wiring
Second Devil's-Advocate pass over the now-complete ADR-0038 closes
three minor test-coverage gaps the polish (
|
||
|
|
2aab457c44 |
feat: DSL→SQL teaching echo — §4 styled-runs polish (ADR-0038)
Lands the last open item on ADR-0038: the de-emphasised styled-runs rendering treatment for the echo + every category-3 prose line. The echoed SQL now reads as code — the dimmed `Executing SQL:` label plus the SQL portion lexed and coloured the same way the input echo treats user-typed input (ADR-0028 §5 styled-runs over input_render::lex_to_runs in advanced mode). Category-3 prose lines (the DontConvert caveat and the existing illuminating `client_side.*` notes — shortid auto-fill, type-conversion transforms) all render dimmed too, per §6's "de-emphasised prose line" wording, so every cat-3 line is visually consistent. * New `OutputKind::TeachingEcho` variant + a custom branch in `ui::render_output_line` mirroring the OutputKind::Echo input-echo path: strip the canonical `Executing SQL:` prefix, render it with `theme.muted`, then lex the rest in `Mode::Advanced` and emit one span per token. Tag stays `[system]` for visual consistency with other system output. * New `OutputStyleClass::Hint` styled-runs class, resolved to `theme.muted` in `output_span_style`. Used for the cat-3 prose lines (dont_convert caveat + the existing client_side notes). * New const `crate::echo::TEACHING_ECHO_LABEL = "Executing SQL: "` — the byte boundary the ui.rs branch needs is fixed (an i18n template can't provide that), so the label moves from i18n to a constant. The `echo.executing_sql` i18n key is retired (en-US.yaml + keys.rs); a comment in en-US.yaml points future locales at re-introducing it if needed. * App-side helpers: `push_teaching_echo(sql)` builds the TeachingEcho line; `push_category_three_prose(text)` builds a System line with a whole-text Hint span. `note_ok_summary` and `handle_dsl_change_column_success` / `handle_dsl_add_column_success` use these instead of plain `note_system` for the echo, the caveat, and the illuminating notes. Existing tests pass unchanged — text content is the same; only styling changes. New tests pin the polish: * `ui::tests::teaching_echo_line_renders_dim_prefix_and_lexed_sql` asserts the TeachingEcho rendering produces a dim prefix span + keyword-coloured SQL spans (confirming the lexer ran in advanced mode). * `ui::tests::category_three_prose_line_renders_all_dim` pins the whole-text Hint coverage. * `ui::tests::hint_class_resolves_to_muted_foreground` pins the theme resolution across both light and dark. * `app::tests::polished_echo_carries_teaching_echo_kind_and_caveat_a_hint_span` pins the App-side wiring (kind + styled_runs shape). Tests: 2019 passed / 0 failed / 1 ignored (pre-existing); clippy clean (`--all-targets -D warnings`, nursery). ADR-0038 is now feature-complete — every catalogue row implemented, round-tripped, AND polished per §4. |
||
|
|
e6ad1aec3d |
feat: DSL→SQL teaching echo — Phase 3 cat-3 caveat (ADR-0038)
Lands the only piece of category-3 prose not already covered by the
existing `client_side.*` notes infrastructure: the `change column …
--dont-convert` *caveat* (ADR-0038 §6, the only Bucket A caveat —
every other category-3 line is illuminating).
`--dont-convert` skips the client-side layer entirely, so the headline
SQL echo (`ALTER TABLE … SET DATA TYPE …`) is the nearest SQL but
*not* equivalent: running the line in advanced mode would convert the
stored values, but the playground left them as-is. The new caveat
states that divergence explicitly.
* New i18n key `client_side.dont_convert_caveat` (no placeholders) —
registered in keys::KEYS_AND_PLACEHOLDERS.
* New `dont_convert_caveat: bool` field on DslChangeColumnSucceeded,
set in the runtime when submission_mode is advanced *and* the
command is ChangeColumnType { mode: DontConvert, .. }. Gated on
advanced mode because the caveat references "the line above" — the
echo, which only fires in advanced mode.
* App's handle_dsl_change_column_success emits the caveat line
between the existing client-side notes and the structure render,
so it reads alongside the echo, not after the table view.
The other two category-3 lines from §6 (shortid generation,
type-conversion transforms) were already in place via
`client_side.auto_fill_*` / `client_side.transformed*` — those notes
already render after the echo via handle_dsl_add_column_success /
handle_dsl_change_column_success, in the right position per the ADR.
This commit just adds the missing caveat.
Tests: 2014 passed / 0 failed / 1 ignored (pre-existing); clippy
clean. An App-level test pins the rendering order (caveat sits
after the echo, before the structure) and the simple-mode gate
(no caveat without an echo to refer to).
The §4 de-emphasised styled-runs rendering polish remains —
the echo + caveat lines are still plain `[system]` lines.
|
||
|
|
275c726ad4 |
feat: DSL→SQL teaching echo — Bucket B renderer (ADR-0038 Phase 2)
Expands the renderer to Bucket B — resolved-name single-statement
echoes plus the two category-2 multi-statement forms. Every catalogue
row round-trips per line through the advanced-mode walker (the §1
copy-paste contract; §6 category 2 holds the contract per line):
add index [as N] on T (cols) → CREATE INDEX <name> ON T (cols)
drop index on T (cols) (positional) → DROP INDEX <name>
add 1:n relationship [as N] … → ALTER TABLE C ADD CONSTRAINT
<name> FOREIGN KEY (cc)
REFERENCES P (pc) [ON …]
drop relationship (endpoints or named) → ALTER TABLE C DROP CONSTRAINT
<name>
drop column T.c --cascade → DROP INDEX <ix1> ⏎ … ⏎
ALTER TABLE T DROP COLUMN c
add relationship … --create-fk → ALTER TABLE C ADD COLUMN cc <ty>
(child column newly created) ⏎ ALTER TABLE … ADD CONSTRAINT
(already existed) collapses to a single-line FK echo
Refactors the echo payload from Option<String> to Option<Vec<String>>
across the 7 success events + arms + render path — one entry per
statement; the Bucket A single-line echoes wrap as Some(vec![s]). Plain
rendering repeats `Executing SQL:` per line; the de-emphasised
styled-runs polish (ADR-0038 §4) will refine it later.
Adds the two echo build paths the handoff §5 ⚠️ gotcha foreshadowed:
* collect_echo_lookups (pre-execution, runtime): resolves names the
dropped thing or not-yet-created column would erase post-execution —
drop index (positional), drop relationship (both endpoints and named
selectors, the latter via a list_tables scan acceptable for teaching-
playground schemas), and the --create-fk pre-state (whether the child
column existed + the parent PK type to derive the new column type via
Type::fk_target_type).
* build_schema_echo (post-execution, runtime): subsumes the Bucket A
pure-Command schema cases and renders Bucket B from the description +
the lookups.
The DropColumn arm gains build_drop_column_cascade_echo, which reads
DropColumnResult.dropped_indexes to emit the multi-line cascade echo;
non-cascade falls through to the pre-execution Bucket A echo unchanged.
Tests: 2013 passed / 0 failed / 1 ignored (pre-existing); clippy clean
(`--all-targets -D warnings`, nursery). Two end-to-end runtime tests
exercise the resolved-name and multi-statement flows against a real
worker (auto-named index, both drop-relationship selector forms, both
--create-fk branches). One app-level test pins the multi-line rendering
(one Executing SQL: per statement, in order, beneath [ok]).
Phase 3 (category-3 prose — shortid generation, type-conversion
transforms, `change column --dont-convert` caveat) and the §4
de-emphasised styled-runs rendering polish remain per ADR-0038 §8
phasing.
|
||
|
|
90479cb879 |
feat: DSL→SQL teaching echo — Bucket A renderer (ADR-0038 Phase 1)
Expands the renderer skeleton from ADR-0038's first slice to the full single-statement catalogue. Every Bucket A row round-trips through the advanced-mode walker (the §1 copy-paste contract): add column / drop column (non-cascade) / rename column / change column (SET DATA TYPE) / add constraint (not null, default, unique, check) / drop constraint (not null, default) / show data [where] [limit] / delete --all-rows / update --all-rows Adds the Expr→SQL and Value→SQL-literal renderers (ADR-0038 §5) — bare identifiers, inlined literals, NULL uppercase, standard <> for inequality — and threads `echo: Option<String>` onto the six remaining success events (DslAddColumn/DropColumn/ChangeColumn/Data/Update/Delete Succeeded) with matching runtime construction and App stash arms. `show data` is the one Bucket A row whose echo needs schema info beyond the Command (the `ORDER BY <pk>` for a limited query): the pure renderer takes the primary key as a parameter, and the runtime sources it post-execution via describe_table — gated on advanced mode + limit present, mirroring the enrich_dsl_failure describe pattern. An end-to-end test pins the describe→PK→ORDER BY glue against a real worker; the simple-mode gate and unlimited-no-lookup paths are covered too. Also fixes a contract gap surfaced while completing the catalogue: the existing create-table echo silently dropped per-column DEFAULT / CHECK, which simple-mode `create table … with pk c(ty) check (…)` does parse (ADR-0029) — so the echo was non-equivalent. The render now emits the full ADR-0029 column-constraint suffix, sharing one append_constraints helper with `add column`. Phase 2 (Bucket B — resolved-name + multi-line echoes, including `add index`), Phase 3 (category-3 prose), and the de-emphasised styled-runs polish remain deferred per ADR-0038 §8 phasing. Tests: 2000 passed / 0 failed / 1 ignored (pre-existing); clippy clean (`--all-targets -D warnings`, nursery). |
||
|
|
04c8e4295f |
feat: DSL→SQL teaching echo — channel + create-table slice (ADR-0037 + ADR-0038)
Walking skeleton validating the whole echo architecture end to end; the Command→SQL renderer currently covers `create table`, with the rest of Bucket A / B / category-3 to follow (ADR-0038 §8). - Channel (ADR-0037): the three-way EffectiveMode (reusing the existing enum, not a new SubmissionMode — recorded in the ADR) rides on Action::ExecuteDsl to the runtime. `replay` bypasses the interactive spawn, so it never echoes (silent, for free). - Echo (ADR-0038): built at the runtime's ExecuteDsl dispatch — the worker gets decomposed calls, not the Command, so ADR §4's "worker builds it" was corrected to the dispatch layer. Gated by echo_for (advanced effective mode + DSL-form). Carried on DslSucceeded; rendered by note_ok_summary as `Executing SQL: …` immediately beneath `[ok]`. New src/echo.rs renderer; echo.executing_sql i18n key. - command_to_sql: `create table` → `CREATE TABLE T (id serial PRIMARY KEY)` (single inline / compound table-level PK), playground type vocabulary, round-trip-verified against the advanced walker (the §1 contract). Tests: echo.rs (render, round-trip contract, mode gate, Sql*-not-echoed); app.rs (submit carries the 3-way mode; echo renders beneath [ok]). Suite 1970/0/1; clippy clean. |
||
|
|
338dc8a4cf |
feat: advanced ALTER COLUMN SET/DROP NOT NULL & DEFAULT, SET DATA TYPE (ADR-0035 Am2)
The standard-first ALTER COLUMN constraint gap-fill advanced mode lacked: - ALTER COLUMN <c> SET DATA TYPE <ty> — ISO canonical synonym for the PostgreSQL TYPE shorthand (same AlterColumnType action + executor). - SET NOT NULL / DROP NOT NULL — reuse the ADR-0029 do_add_constraint / do_drop_constraint executors (dry-run + internal-table guards free). - SET DEFAULT <expr> / DROP DEFAULT — SET DEFAULT uses a dedicated raw-SQL executor (do_set_column_default); sql_expr yields no typed Value, so it can't go through do_add_constraint. DROP DEFAULT reuses do_drop_constraint. Grammar: AT_ALTER_COLUMN gains a tail Choice (type / set / drop), reusing SQL_TYPE and the CREATE TABLE DEFAULT_NODES; builder dispatch routes the new column-attribute forms; runtime decomposes to the executors. ADR-0035 Am2 corrected in-place: SET DEFAULT decomposes to do_set_column_default, not do_add_constraint (Value-based) — found during build. Tests (test-first): 6 parse + 7 Tier-3 execution via run_replay. Suite 1962/0/1; clippy clean. |
||
|
|
8c3b13b313 |
feat: ADR-0036 Phase 2 — validate advanced-mode UPDATE SET literals + retain the value
Mirror Phase 1's capture-at-parse technique on the UPDATE SET assignment list. build_sql_update calls the new capture_set_literals (data.rs), which walks the matched tokens (no reparse, no grammar change) and classifies each top-level `SET col = <rhs>` as a literal (Some, incl. signed numbers) or an expression (None), using paren depth so a comma inside a function call or a `where` inside a scalar subquery is not mistaken for a boundary, and the trailing top-level WHERE is excluded. Command::SqlUpdate gains set_literals; do_sql_update validates the literals against their column types via the shared impl_value_for before the still verbatim update; user_value_for_column reads them so a constraint error names the offending value. WHERE stays unvalidated; execution and command identity are unchanged. Also corrects the stale data.rs header comment (DSL typed slots are wired, not "deferred") and flips ADR-0036 + README to Phases 1–2 implemented. Tests: 1934 passing (+4), 0 failed, 0 skipped, 1 ignored; clippy clean. |
||
|
|
f8a91f41c9 |
feat: ADR-0035 Amendment 1 follow-up — enrich replay errors + close message gaps
- F2-broad: replay failures now render with real schema context instead of
a contextless friendly_message(). Extract App::build_translate_context into
the shared App::translate_context_for(command, facts, verbosity); run_replay
enriches via enrich_dsl_failure + that builder. ctx_* fallbacks degrade to
neutral prose so the rare non-replay contextless callsites can't leak raw
{name} either. (SQL INSERT/UPDATE values aren't retained — ADR-0033 verbatim
— so those show real table/column + neutral "that value".)
- Gap C: SQL ALTER … ADD FOREIGN KEY on a missing child column refuses with an
SQL-appropriate "add it first", not the DSL-only --create-fk flag.
- Gap B: dropping a single-column-UNIQUE column refuses with a pointer to
`drop constraint unique from T.col` (was an opaque generic refusal).
- Gap D: 4e drop/rename CHECK-guard + 4f change-type FK-guard refusals reworded
to explain why; static_refusal reasons left as-is.
Tests: +4, 3 strengthened. 1926 pass / 0 fail / 0 skip; clippy clean.
|
||
|
|
22e5bf5d6a |
feat: ADR-0035 4i(a,b) — CREATE TABLE help/usage + describe table constraints; Phase 4 complete
(b) describe shows table-level constraints: TableDescription gains unique_constraints + check_constraints (populated by do_describe_table from read_schema), rendered in a new "Table constraints:" section — composite UNIQUE and table-level CHECK (named + unnamed). The per-column Constraints column already covered single-column NOT NULL/UNIQUE/PK/CHECK. (a) CREATE TABLE help/usage skeleton refreshed for the column DEFAULT/ CHECK/REFERENCES, table-level composite UNIQUE, table CHECK, and table-level FOREIGN KEY forms (4a.2/4a.3/4b) — engine-neutral, vocab-audit clean. With 4i's (c)/(d)/(e) already shipped, this completes sub-phase 4i — the verification sweep — and therefore ADR-0035 Phase 4 (4a–4i). ADR-0035 Status, §13 4i, the ADR index, and requirements.md Q1 updated to "Phase 4 complete". Tests: render_structure table-level-constraints unit test + e2e_describe_shows_table_level_constraints. Full suite 1917 passing / 0 failing / 1 ignored; clippy clean. |
||
|
|
f7e77a86f8 |
feat: ADR-0035 4h — ALTER TABLE … RENAME TO
The one genuinely new low-level op in Phase 4: a native engine RENAME TO
plus one-transaction reconciliation (commit-db-last) of everything the
engine does not track —
- every metadata row naming the table: __rdbms_playground_columns, both
ends of __rdbms_playground_relationships (FK parent, child, and
self-referential), and __rdbms_playground_table_checks;
- the CSV file, via the existing persistence rewrite+delete path
(rewritten_tables=[new], deleted_tables=[old]) — no new method;
- CHECK text that qualifies a column with the old table name
(T.age → U.age, column- and table-level): the engine rewrites the live
CHECK but the stored text would drift and break a fresh rebuild (a
planning-/runda finding); rewrite_check_table_qualifier keeps them in
step. Bounded — a CHECK references only its own table.
Grammar: a fifth AlterTableAction (RenameTable { new }), added by
splitting the `rename` verb into one branch with an inner Choice on a
distinct second keyword (column vs to); the new-name slot mirrors the
CREATE TABLE name slot (NewName + reject_internal_table validator).
Refusals are engine-neutral and case-insensitive (the engine matches
names that way): same-name, case-only, existing-target, __rdbms_*, and
non-existent source. Auto-named indexes and relationships keep their
stale names (only table-name columns update — §6 scope). One undo step;
advanced-mode only; closes the rename half of C1.
Tests: 8 Tier-3 e2e + rewrite-helper unit tests + parse-dispatch tests.
Full suite 1903 passing / 0 failing / 1 ignored; clippy clean.
|
||
|
|
6ff97f6e20 |
feat: ADR-0035 4g — ALTER TABLE add/drop constraint + add FK
ALTER TABLE <T> ADD [CONSTRAINT <name>] (CHECK | UNIQUE | FOREIGN KEY)
and DROP CONSTRAINT <name>. ADD = table-CHECK + composite UNIQUE + FK
(ADD PRIMARY KEY and a named UNIQUE refused — composite UNIQUE is
anonymous in our model). Each ADD reuses a low-level path with a dry-run
guard (table-CHECK/UNIQUE rebuild; FK -> add_relationship, bare
REFERENCES -> parent single PK). DROP CONSTRAINT resolves the name to a
named table-CHECK then a child-side FK, else refuses. One undo step each.
Named table-CHECKs round-trip: a nullable `name` column on
__rdbms_playground_table_checks (rebuild-only arrival; a named add on a
pre-4g project is refused with a "rebuild first" hint) plus a project.yaml
check_constraints {expr, name} extension (bare-string form still reads).
The internal-__rdbms_* guard was folded into do_add_constraint /
do_add_relationship, completing that guard class.
Grammar: the action Choice keeps one branch per verb (add/drop/rename/
alter) with an inner Choice fanning out on the distinct second keyword,
since the walker's Choice does not backtrack between same-led branches.
Tests: 7 Tier-1 parse + 2 yaml round-trip + 1 internal-guard + 9 Tier-3
e2e. Help/usage refreshed; ADR-0035 §13 4g + README + requirements.md in
lockstep.
|
||
|
|
5b76315d1e |
feat: ADR-0035 4f — ALTER TABLE … ALTER COLUMN TYPE
Fourth AlterTableAction (AlterColumnType), runtime-decomposed to the existing change_column_type executor with ForceConversion — which IS the §7 advanced policy: lossy converts with a note (no force flag), incompatible + the ADR-0017 static refusals (↔blob, same-type, date↔datetime, non-int→serial) still refuse, while int→serial is allowed (auto-fills nulls + UNIQUE, ADR-0018 §8). No new mode/note/persistence; undo is the advanced safety net. Grammar adds a fourth action branch leading on `alter`, discriminated in the builder by the `type` keyword (unique — ADD COLUMN's type is an ident); the type slot reuses SQL_TYPE. The internal-__rdbms_* guard was folded into do_change_column_type (user-confirmed), closing the simple `change column` exposure. Tests: 7 Tier-3 e2e via run_replay + 4 Tier-1 parse (incl. a column-named- `type` discriminator probe) + the simple-surface guard. Help/usage refreshed; ADR-0035 §13 4f + README + requirements.md in lockstep. |
||
|
|
bbc2e34b33 |
feat: ADR-0035 4e — ALTER TABLE add/drop/rename column
Advanced-only `alter` entry word; ALTER TABLE <T> ADD COLUMN <col> <type> [constraints] | DROP COLUMN <col> | RENAME COLUMN <old> TO <new> -> SqlAlterTable, runtime-decomposed to the existing column executors (do_add_column / do_drop_column / do_rename_column) — one undo step each, no new worker layer. The COLUMN keyword is required (reserves bare RENAME TO for 4h, ADD CONSTRAINT for 4g). - ADD COLUMN takes NOT NULL / UNIQUE / DEFAULT / CHECK (no PK / inline REFERENCES). do_add_column extended to consume the SQL raw-text default_sql / check_sql (sql_expr is validate-only, the 4a.2 mechanism), reaching parity with CREATE TABLE's column constraints. - Drop/rename column refuse a column any CHECK references — table-level AND column-level (incl. a column's own self-check on rename) — the 4a.3 deferral, detected up-front by tokenizing the raw CHECK text (skipping string literals). In the shared executors, so it guards both the simple and SQL surfaces and fixes a latent rename-drift bug that desynced the stored CHECK text and broke rebuild. - SQL DROP COLUMN refuses an index-covered column (no --cascade SQL spelling — matches SQLite + the simple default). - The column executors and do_add_index gained an internal-__rdbms_* guard (refuse as "no such table"), closing a pre-existing exposure on both surfaces. (do_change_column_type / do_add_constraint / do_add_relationship are a tracked follow-up.) - `alter` is advanced-only; AlterTableAction::AddColumn is boxed (clippy::large_enum_variant). Docs: ADR-0035 status + §13 4e; ADR README; requirements.md Q1. Plan: docs/plans/20260525-adr-0035-sql-ddl-4e.md. Tests: 1854 passing / 0 failing / 0 skipped / 1 ignored; clippy clean. |
||
|
|
701217d29f |
feat: ADR-0035 4d — CREATE [UNIQUE] INDEX / DROP INDEX
Advanced-mode SQL CREATE [UNIQUE] INDEX [IF NOT EXISTS] [<name>] ON <T> (cols) -> SqlCreateIndex and DROP INDEX [IF EXISTS] <name> -> SqlDropIndex, both reusing the ADR-0025 executors (do_add_index / do_drop_index), like 4c reused do_drop_table. - CREATE UNIQUE INDEX admitted in advanced mode (ADR-0025 Amendment 1): ADR-0025 deferred UNIQUE indexes for the simple-mode DSL, but advanced mode trusts the user like SQL does. Adds an additive IndexSchema.unique flag (project.yaml, serde-default, version stays 1); rebuild re-emits CREATE UNIQUE INDEX; the redundant-set guard keys on (columns, unique). Simple-mode `add unique index` stays deferred. - IF [NOT] EXISTS on both forms reuses the 4c no-op-with-note skip (journalled, not snapshotted) via CreateIndexOutcome / DropIndexOutcome. - Unnamed CREATE INDEX auto-named (ADR-0025 convention); the [UNIQUE] prefix is a concrete-keyword Choice and the optional name an on-led-first selector (the drop-index selector precedent) — trap-safe. - create/drop each gain a second advanced node; the existing all-candidates dispatch handles it (locked by parse tests). - Unique indexes marked [unique] in the structure view and items panel. - do_add_index refuses internal __rdbms_* tables as "no such table", closing a latent exposure on both the simple `add index` and the new SQL CREATE INDEX surfaces (ADR-0025 Amendment 1). Docs: ADR-0035 status + §13 4d + 4i; ADR-0025 Amendment 1; ADR README; requirements.md Q1/C3. Plan: docs/plans/20260525-adr-0035-sql-ddl-4d.md. Tests: 1834 passing / 0 failing / 0 skipped / 1 ignored; clippy clean. |
||
|
|
e52e90c45b |
feat: ADR-0035 4c — DROP TABLE [IF EXISTS]
Add advanced-mode SQL `DROP TABLE [IF EXISTS] <name>` -> SqlDropTable, executing through the existing do_drop_table (cascade / inbound- relationship refusal / metadata cleanup) — full parity with the simple `drop table`. The only new behaviour is `IF EXISTS` as a no-op-with-note: a new DropOutcome::Skipped mirroring CreateOutcome::Skipped (journalled, no snapshot), rendered via a new ddl.drop_skipped_absent note + DslDropSkipped event. - Grammar: SQL_DROP_TABLE node (entry `drop`, shape `table [if exists] <name> [;]`), registered Advanced. SQL-first dispatch: `drop table T` -> SqlDropTable in advanced; `drop column`/`relationship`/`index`/ `constraint` fall back to the simple `drop` node (and still execute). - Worker: Request::SqlDropTable + db.sql_drop_table; the if-exists-and- absent arm journals + replies Skipped without a snapshot, else snapshot_then(do_drop_table) -> Dropped. - Completion: advanced `drop ` now surfaces the SQL `table` (the shared-entry-word behaviour from `create`); test split into simple (full DSL list) + advanced (SQL surface). Known shared-entry-word completion unevenness (advanced `drop ` offers only `table`; partial `drop rel` returns an empty list) deferred to 4i (merge candidate sets for shared entry words) along with a flagged user request to visually distinguish simple- vs advanced-mode completions in the hint UI — tracked in ADR §13 4i (d)/(e), the 4c plan, and the completion test. The DSL drops still parse + execute via fallback. 10 new tests (parse/builder + Tier-3: drop existing + one-undo-step + restore, IF EXISTS skip + journal, plain-absent error, inbound refusal). Docs: ADR-0035 Status/§13, README, requirements.md Q1. Tests: 1805 passing, 0 failing, 1 ignored. Clippy clean. |
||
|
|
631074ff9c |
feat: ADR-0035 4a — SQL CREATE TABLE command, worker, and exit gate
Command + builder + worker for advanced-mode SQL CREATE TABLE (sub-phase 4a), executed structurally through do_create_table: - Command::SqlCreateTable + build_sql_create_table (ddl.rs): aliases via from_sql_name (incl. double precision), column- and table-level PRIMARY KEY, redundant-flag de-dup off a sole PK, IF NOT EXISTS. Advanced REGISTRY entry on the shared `create` word (SQL-first, DSL fallback); no-PK tables allowed (user-confirmed). - Worker (db.rs): Request::SqlCreateTable + CreateOutcome + snapshot_then (one undo step); IF NOT EXISTS no-op (no snapshot, but journalled, like read-only commands). do_create_table inline-PK rule aligned with the rebuild generator schema_to_ddl — no round-trip DDL drift; serial autoincrement is independent of inline-PK (verified by round-trip tests). - Runtime/App: dispatch + CommandOutcome::SchemaSkipped + AppEvent::DslCreateSkipped (structure + "already exists — skipped" note). Friendly catalog keys added (engine-neutral). DEFAULT/CHECK/table-level UNIQUE are absent from the 4a grammar (parse error with usage skeleton; friendly message + support land in the 4a.2 constraint slice) — user-confirmed. Tests: type resolver, grammar shape, builder (incl. the PK detection bug they caught), and tests/sql_create_table.rs (worker round-trip, serial autoincrement first/non-first across rebuild, IF NOT EXISTS no-op + journalling, no-PK table, one undo step) + a replay-as- write test. 1739 pass / 0 fail / 1 ignored; clippy clean. Exit gate: ADR-0035 Proposed -> Accepted (validated end-to-end by 4a); README + requirements.md Q1 updated. |
||
|
|
25800e3eb5 |
feat: ADR-0006 §8 steps 4-5 — undo/redo commands + confirm-modal flow
Commands & grammar (step 4):
- AppCommand::Undo/Redo, grammar nodes + REGISTRY entries, catalog
help/usage + keys; parse tests
- replay skips undo/redo (is_app_lifecycle_entry_word) + completion
entry-keyword lockstep; replay-skip test extended
Wiring (step 5):
- Action::{PrepareUndo,PrepareRedo,Undo,Redo} + AppEvent::{UndoPrepared,
UndoUnavailable,UndoSucceeded,UndoFailed}
- App: undo_enabled flag, Modal::UndoConfirm, dispatch + event handling
+ confirm-key handler (Y confirms / N/Esc cancels); "turned off" when
--no-undo; "nothing to undo/redo" when empty
- ui::render_undo_confirm names the command + snapshot time
- runtime: opens with undo enabled (!--no-undo), threads it through the
project-switch path, spawn_prepare_undo/spawn_undo (peek->modal,
restore->refresh tables + schema cache)
- 9 Tier-1 app tests + 3 parse tests
1692 passed / 0 failed / 1 ignored; clippy clean.
|
||
|
|
e4f2f5fa15 |
feat: ADR-0034 — history journal records err + replay parses/filters the journal
Replay (§3): run_replay parses <ts>|<status>|<source> journal records — runs ok, skips non-ok — while still accepting bare .commands scripts (prefix-detected so a | inside a bare command isn't misread). Fixes replay history.log, which died on line 1. Journal failures (§1/§2): failed commands are recorded err via a new Action::JournalFailure, emitted by the pure-sync App for both parse failures and worker-execution failures (runtime appends best-effort, never fatal). Hydration reads all records so typo'd/rejected commands are recallable across sessions. Amendment 1 — replay filters app-lifecycle commands: a working replay history.log exposed that the journal also records save as/load/new/export/import/rebuild/mode (which would panic the worker dispatch or abort replay). Replay now re-applies only schema/data writes and skips every app-lifecycle command + nested replay, classified by entry word so modal/incomplete forms (save as, bare mode) and quit skip uniformly rather than aborting. All skips continue (reversing the nested-replay refusal); import and nested replay warn. replay.error_nested removed; replay.skipped_import/_replay added; ReplayCompleted carries warnings. requirements.md U3/U4 updated; app-command runtime-failure journalling tracked as a follow-up. 1659 passing / 0 failing / 0 skipped / 1 ignored. Clippy clean. |
||
|
|
d5c7f63513 |
grammar+walker: 3j — shared insert/update/delete entry words (ADR-0033 §2 / Amendments 1 & 3)
Wire `insert`/`update`/`delete` as shared DSL/SQL entry words through the
category-grouped dispatcher (ADR-0033 Amendment 1): the Advanced SQL nodes
move off the dev words (`sqlinsert`/`sql_update`/`sql_delete`) to the real
keywords, registered alongside the Simple DSL nodes. Remove the dev-word
scaffold; collapse build_sql_{insert,update,delete} to source.trim();
de-duplicate the two REGISTRY entry-word listing sites.
Dispatch model (ADR-0033 Amendment 3, written this round):
- A command is the mode-rooted grammar-path outcome; identity is intrinsic.
Advanced mode tries SQL first, falling back to the Simple DSL command when
no SQL branch matches a token (`delete … --all-rows` falls back;
`update … --all-rows` does not — the SET expression absorbs it, harmless
since the engine treats `--all-rows` as a comment).
- Simple mode commits the DSL candidate for a shared word, surfacing the real
DSL error; bare "this is SQL" is reserved for SQL-only entry words
(`select`/`with`). A content rejection on the SQL candidate (internal
table) is committed, never masked by the DSL fallback.
Combined DSL-error + advanced-SQL pointer (ADR-0033 Amendment 3): a Simple-mode
definite DSL error that would run as SQL in advanced mode gains the
`advanced_mode.also_valid_sql` suffix — in the live hint (ambient_hint_in_mode)
and on submit (dispatch_dsl), via the shared advanced_alternative_note — so the
actionable DSL fix and the mode pointer coexist (submit covers constructs that
surface only on submit, e.g. `delete … returning`).
Internal-table rejection symmetrised (/runda finding B, ADR-0030 §6): the DSL
data-command target slots (insert/update/delete/show data/show table) gained
reject_internal_table, so `__rdbms_*` tables are refused in Simple mode too —
previously only the advanced SQL grammar rejected them.
Mode-awareness: classify_input_with_schema_in_mode and
invalid_ident_at_cursor_in_mode stop leaking the advanced SQL view into
simple-mode hints for shared words.
Tests: dev-word inputs migrated to the real words (advanced); DSL grammar /
completion / phase-D / db tests parse in Simple mode (the DSL surface); replay
keeps its advanced-mode model (one stale assertion fixed); dispatcher routing,
combined-pointer, and internal-table tests added. Suite 1626 pass / 0 fail /
1 ignored; clippy --all-targets -D warnings clean.
Defer M4 (execution-time mode side-channel; tracked in requirements.md) to its
own ADR.
|
||
|
|
2d1112d0f3 |
grammar+db: 3i — not_null_missing diagnostic + TableColumn constraints (ADR-0033 §8.3)
Extend SchemaCache TableColumn with not_null + has_default (with a TableColumn::new constructor for the common no-constraint case), populated in build_schema_cache from ColumnDescription (a PK column counts as not-null). New dml_not_null_missing_diagnostics pass: a WARNING when a SQL INSERT's explicit column list omits a column that is NOT NULL with no DEFAULT — advisory (the engine enforces it). serial/shortid (auto-filled) and defaulted columns are excluded. Anchored on the target-table ident (no token for the omitted column). Catalog key diagnostic.not_null_missing (engine-neutral). Tests (+4): fires on omitted required column; silent when included, when defaulted, and for auto-gen serial/shortid. ~24 TableColumn literal sites updated for the two new fields (build clean). 1591 pass / 0 fail / 1 ignored. Clippy clean. All three ADR-0033 §8 DML diagnostics now implemented. Remaining 3i: cross-cut verification + #12 UPSERT DO UPDATE validation. |
||
|
|
fd8b74ba5e |
grammar+db: 3g — RETURNING on INSERT/UPDATE/DELETE (ADR-0033 §5)
Shared RETURNING_CLAUSE (reuses Phase-2 PROJECTION_LIST, now pub(crate)) as an optional tail on all three SQL DML shapes. `returning: bool` on the Command variants, set by the ast-builders and threaded to the worker. run_returning collects the returned rows as a DataResult (RETURNING mutates + yields in one pass), reusing resolve_select_column_types for bare-column type recovery; computed projections stay typeless. DeleteResult gains a `data` field rendered alongside the cascade summary. Follow-set fix: `returning` is added to the table-source and projection bare-alias follow-sets so an INSERT … SELECT row source stops before RETURNING instead of reading it as a table alias. Auto-fill × RETURNING: build_sql_insert stops row_source before the RETURNING token (keeping it preparable for shortid materialisation), and plan_shortid_autofill re-appends the RETURNING tail so generated shortids surface in RETURNING *. Tests (+17): grammar accept on all three; INSERT/UPDATE/DELETE RETURNING incl. *, aliases, multi-row, type recovery + computed- typeless; auto-fill × RETURNING (single + multi-row distinct ids); INSERT…SELECT…RETURNING execution; UPDATE…RETURNING zero-match; DELETE…RETURNING cascade+rows; app-level render of both. Dev sql_insert/sql_update/sql_delete entry words still removed in 3j. 1562 pass / 0 fail / 1 ignored. Clippy clean. |
||
|
|
62f09bebc5 |
db: fix self-referential cascade over-count + SQL-delete render test
A self-referential ON DELETE CASCADE FK (e.g. T.ParentId -> T.id) is returned by read_relationships_inbound as a child whose table IS the delete target. The before/after row-count diff then includes the directly-deleted rows (already in rows_affected), so deleting a chain root reported 3 cascaded rows when only 2 were removed via the self-reference. Fix in both do_delete (DSL) and do_sql_delete (SQL): when the child table equals the target, subtract rows_affected from the diff and guard on the corrected count (a leaf delete no longer reports a phantom 0-row self-cascade); the target's CSV is already queued, so a self-ref child is not re-added to rewritten_tables. Pre-existing in do_delete; surfaced by the 3f DA pass, fixed in both paths to keep DSL/SQL parity. Behaviour: report only the rows removed via the self-reference (user-confirmed). Also adds an app-level render test for the SQL DELETE path (handle_dsl_delete_success via CommandOutcome::Delete) — the shared renderer's ok-summary + per-relationship cascade line were exercised only through the DSL path before. Test-first: self_referential_cascade_counts_only_cascaded_rows added for both paths (asserted 2, failed at 3 before the fix). 1545 pass / 0 fail / 1 ignored. Clippy clean. |
||
|
|
2c86a1313e |
grammar+db: 3f — SQL DELETE + cascade summary (ADR-0033 §1/§7)
New src/dsl/grammar/sql_delete.rs (FROM <table> [WHERE] [;]), Command::SqlDelete, Request::RunSqlDelete, do_sql_delete worker. do_sql_delete mirrors the DSL do_delete: detect FK cascade by before/after child row-count diffing, re-persist target + every cascade-affected child, history-on-success inside the tx. Reuses CommandOutcome::Delete -> handle_dsl_delete_success, so the per-relationship cascade summary formatter is shared, not duplicated. ADR-0033 Amendment 2: supersedes §7's WHERE-injected pre-count. Its premise (DSL handler builds pre-counts from the typed Expr) was wrong — do_delete uses count-diff. The pre-count would also have broken the §2 parity promise by reporting SET NULL the DSL path doesn't. Count- diff gives exact parity, no WHERE-byte extraction, and withdraws R2. SET NULL reporting deferred for both paths (user-confirmed). Tests: +6 grammar unit, +12 integration (cascade parity with DSL, both R2 subquery cases, before-execute order, no-WHERE, FK-rejection rollback, childless-parent, two-child cascade). 1542 pass / 0 fail / 1 ignored. Clippy clean. Dev sql_delete entry word removed in 3j. |
||
|
|
53808ed9d7 |
grammar+db: 3e — SQL UPDATE grammar + execution (ADR-0033 §2)
New src/dsl/grammar/sql_update.rs: SQL_UPDATE_SHAPE =
<table> SET col = sql_expr (',' …)* [WHERE sql_expr] [';'], the
__rdbms_* target rejection, and the shared sql_expr on both the
assignment RHS and the predicate. No --all-rows rail — a SQL
UPDATE without WHERE runs as written (ADR-0030 §12). Reuses
sql_select::WHERE_CLAUSE (now pub(crate)) so the predicate
diagnostics are identical. The target uses the shared `table_name`
ident role (not a bespoke one) so the Phase-2 schema-existence and
predicate-warning passes collect it as a scope binding and check
the SET / WHERE columns for free — a bespoke role left them
unchecked (the cross-cut tests caught this).
Command::SqlUpdate { sql, target_table }; Request::RunSqlUpdate +
do_sql_update (execute validated SQL via execute_with_fk_enrichment,
re-persist the target CSV, append history.log). 3e surfaces the
affected-row count only; precise row output is RETURNING (3g), so
the update-success render skips a column-less data set rather than
showing a misleading "(no rows)" band. Behind the dev `sql_update`
entry word until 3j.
Tests: grammar accept/reject; integration (single/multi-col,
no-WHERE all-rows, sql_expr in SET, scalar subquery in SET,
zero-match success, history); walker cross-cut (unknown SET column
→ unknown_column, `= NULL` in WHERE → eq_null warning); app-level
render-guard both ways (column-less → count only; with columns →
table renders). 1524 green, clippy clean.
|
||
|
|
c87363168f |
grammar+db: 3b — SQL INSERT grammar + minimal execution (ADR-0033 §1)
SQL_INSERT_SHAPE (INTO <table> [(cols)] VALUES tuple(s)) with __rdbms_*
target rejection; Command::SqlInsert{sql,target_table}; Request::RunSqlInsert
+ do_sql_insert worker (tx-guarded: execute, then finalize_persistence for
CSV + history before commit, so failures roll back and don't re-persist).
Auto-show is best-effort via last_insert_rowid range.
Isolated behind a dev `sqlinsert` entry word (Advanced) so the SQL path is
testable without making `insert` a shared word yet (that's 3j, after 3d
auto-fill parity). Command::SqlInsert carries only sql+target_table; the
plan's listed_columns/returning land in 3d/3g where they're read.
6 grammar accept/reject tests + 8 integration tests (single/multi-row,
column-list, full-arity, history, rollback-on-failure, multi-row atomicity,
parse-path reconstruction, internal-table rejection). 1452 baseline green.
|
||
|
|
4e16d97fe0 |
walker: 3a — category-grouped mode-aware dispatch (ADR-0033 Amendment 1)
Replaces ADR-0033 §2's original Node::Guard + Choice(SQL,DSL) mechanism,
which was found during 3a to be unworkable: any guard-in-Choice approach
forces a walk_choice change (walk_choice falls through only on NoMatch, so
simple-mode valid-DSL would wrongly surface "this is SQL"), and walk_seq
treats a NoMatch past idx 0 as a hard Failed, breaking advanced-mode DSL
fall-through.
Mechanism (Amendment 1): each REGISTRY entry is tagged
CommandCategory::{Simple, Advanced}, generalising the whole-command
is_advanced_only gate. walk() becomes a thin dispatcher over decide()
(mode-aware candidate selection: simple commits the DSL node or emits the
"this is SQL" hint; advanced tries SQL first, DSL as a full-line fallback)
and an extracted walk_one_command(); speculative match-testing runs on a
scratch WalkContext so the caller's context is only touched by the
committed walk. No Node::Guard, no walk_choice/walk_seq change.
6 dispatch smoke tests on a shared-entry-word smoke registry; 1446 baseline
green; clippy clean.
|
||
|
|
05884bd13a |
2g rework: address DA findings on type recovery + engine routing + UI
Three DA critiques from the Phase-2 verification flagged real gaps; this commit closes them. 1. Type recovery row-independence (critique #1). The all-10-types test left col_blob NULL because the DSL Value enum has no Blob variant. The DA flagged this as a potential row-dependence gap. Added `database_run_select_type_recovery_works_on_empty_table` that proves column-origin metadata works on Text AND Blob columns with zero rows, pinning the invariant. The all-types test now carries an explicit comment referencing it. 2. Engine.* pattern matching against real SQLite output (critique #2). The pre-rework tests fed `translate_generic` hand-coded strings; never verified that the pinned SQLite version actually produces those wordings. Added three engine-routing tests in `tests/sql_select.rs` that produce real engine errors via `run_select` and assert catalog routing. Aggregate-in-WHERE confirms end-to-end. GROUP-BY-required and scalar-subquery are SQLite-permissive (no real error on the natural triggers), so those tests verify the matcher doesn't false-positive on benign queries + that synthetic messages route correctly. 3. Manual TUI verification (critique #3) surfaced an additional gap: `App::input_validity_verdict()` was hard-coded silent in Advanced mode, so SQL predicate warnings emitted but never reached the [WRN] indicator. Wired the verdict through to the active effective mode; updated two pre-existing tests that pinned the now-superseded "silent in Advanced" behavior; added one new test confirming a SQL `LIKE`-on-numeric warning fires the indicator. Launched the TUI, typed a representative warning-triggering SELECT, confirmed SELECT/FROM/WHERE/LIKE highlight as keyword colour AND the [WRN] indicator appears. Test totals: 1441 → 1446 passing (+5). Clippy clean. |
||
|
|
83e0ddc2ff |
app: mode-threaded completion, overlay, and validity indicator
The dispatch-layer mode gate (previous commit) made the submit behaviour correct — `select` runs in advanced mode and shows the SQL hint in simple mode. This commit extends that gating to the ambient assistance layer so simple-mode users do not see SQL leak through Tab completion, the live error overlay, or the `[ERR]`/`[WRN]` validity indicator either. `_in_mode` walker variants -------------------------- - `completion_probe_in_mode`, `expected_at_input_in_mode`, `input_verdict_in_mode`. Each sets `ctx.mode` before walking. The empty-input / unknown-entry fallback in `completion_probe` and `expected_at_input` filters the `REGISTRY` listing by `is_advanced_only` so Tab does not offer `select` in simple mode. Old signatures keep delegating to `Mode::Advanced` (back-compat for tests + other callers). `_in_mode` completion variants ------------------------------ - `candidates_at_cursor_in_mode`, `candidates_at_cursor_with_in_mode`. Internally they route the `parse_command` completeness probe through `parse_command_in_mode(input, mode)`, the `completion_probe` call through `completion_probe_in_mode`, and the `expected_at` fallback through `expected_at_input_in_mode`. Old signatures default to `Mode::Advanced`. `EffectiveMode::as_mode` ------------------------ - Collapses the persistent / one-shot distinction the UI cares about into the plain `Mode` the walker reads from `WalkContext::mode`. App-level call sites that thread mode into the walker chain use this. App / input-render wiring ------------------------- - `App::input_validity_verdict` runs only when effective mode is plain `Simple` (per ADR-0027), so it hardcodes `Mode::Simple` into the new `input_verdict_in_mode` call rather than threading. - `App::start_or_complete_at` / `_last` (the Tab handlers) pass `self.effective_mode().as_mode()` into `candidates_at_cursor_in_mode`, so a `:` one-shot or persistent advanced gives full SQL completion, persistent simple does not offer SQL. - `input_render::render_input_runs` and `ambient_hint` are invoked from `ui.rs` only when effective mode is plain `Simple` (advanced rendering uses `plain_input_spans` and skips ambient hinting per ADR-0022 §12). Their internal `classify_input_with_schema` / `candidates_at_cursor` / `parse_command` calls now go through the mode-aware variants with `Mode::Simple` hardcoded — a SQL form in simple mode surfaces as a definite-error overlay and the hint panel does not offer it. After this commit a simple-mode user typing `select` or `sel<Tab>` sees nothing SQL-shaped: no live highlight, no Tab completion candidate, the `[ERR]` indicator lit, and the on- submit hint that names the recovery paths. An advanced-mode user or a `:` one-shot sees the full SQL surface. |
||
|
|
6369066fe4 |
grammar: SQL SELECT end-to-end (ADR-0030 Phase 1)
The first cut of advanced-mode SQL: a `select` line in advanced
mode parses, runs against the database, and renders its rows
through the existing data-table renderer; the same line in
simple mode lights up the precise "this is SQL" hint instead of
running.
Walker mode gate (ADR-0030 §2)
------------------------------
- `WalkContext` gains a `mode: Mode` field; `Mode` derives
`Default` (= `Simple`, matching the app's startup mode).
- `grammar::is_advanced_only` keys an advanced-only entry-word
set (Phase 1: just `select`). When the walker matches an
advanced-only entry word with `ctx.mode == Simple`, it
short-circuits to a `WalkOutcome::ValidationFailed` carrying
the `advanced_mode.sql_in_simple` catalog key — the input
highlights as a keyword, the validity indicator goes ERROR,
and the parse-error layer renders the "switch with `mode
advanced`, or prefix the line with `:`" hint.
- `parser::parse_command_with_schema_in_mode` (and the
schemaless `parse_command_in_mode`) threads the mode into
`WalkContext`; existing `parse_command*` entry points default
to `Mode::Advanced` (most permissive) so back-compat callers
see the full grammar.
- `App::submit` is unified: both modes route through
`dispatch_dsl(&effective_input, effective_mode)`, which now
parses with the line's effective mode. The placeholder
advanced-mode echo branch is gone.
Builder signature sweep (ADR-0031 §2)
-------------------------------------
- `CommandNode.ast_builder` gains a `source: &str` parameter,
forwarded by the walker. `build_select` reads it to put the
validated SQL text into `Command::Select`; the 21 existing
builders accept it as `_source`.
SQL `SELECT` (ADR-0030 §6, ADR-0031)
-------------------------------------
- New `Command::Select { sql: String }` variant. Every
exhaustive `match Command` updated (`verb`, `target_table`,
`build_translate_context`, `execute_command_typed`,
`typing_surface`'s label).
- `grammar::data::SELECT` `CommandNode`: projection (`*` or
`expr [as alias]` list), optional `FROM <table>`, optional
`WHERE`/`ORDER BY`/`LIMIT`, optional trailing `;`. The
expression slots reference the ADR-0031 fragment through
`Subgrammar(&sql_expr::SQL_OR_EXPR)`. The `FROM` table-name
slot carries a `reject_internal_table` validator that
refuses `__rdbms_*` references at parse time.
- The `FROM` clause is optional — `select 1`, `select upper('x')`
(zero-table constant/function-call SELECTs) work alongside
the single-table form. Standard SQL admits them and they are
the canonical learner probe.
- Implicit projection aliasing (`select a x`) is deliberately
unsupported — `from` is a keyword, the bare alias would be
ambiguous; only `select a as x` is admitted.
Worker / runtime
----------------
- `Request::RunSelect { sql, source, reply }` + a new
`Database::run_select` method. `do_run_select_request` runs
the prepared statement, collects rows into a `DataResult`
with `column_types: Vec<None>` (Phase-1 SELECT result columns
carry no playground type per ADR-0030 §6), and appends the
literal source line to `history.log` so replay re-runs it
(ADR-0030 §11).
- `runtime::execute_command_typed` gains a `Command::Select`
arm that calls `database.run_select(sql, src)` and maps to
`CommandOutcome::Query`, which flows into the existing
`AppEvent::DslDataSucceeded` → `render_data_table` path.
Catalog (ADR-0019)
------------------
- `advanced_mode.sql_in_simple` — the walker's gate message.
- `select.internal_table` — the `__rdbms_*` rejection.
- `parse.usage.select` — the parse-error usage template.
Tests
-----
Two `app::tests` cases that pinned the pre-ADR-0030 placeholder
echo are updated to pin the new dispatch contract — both verify
that the advanced-mode `select` (one persistent, one via the
`:` one-shot) produces `ExecuteDsl(Command::Select)` with the
submission's effective mode tagged on the echo. The matching
walking-skeleton test is updated likewise.
A separate follow-up commit lands the ambient mode-threading
(completion / live overlay / validity indicator) so simple-mode
users do not see SQL surfaced through Tab or the live error
overlay either — the dispatch-layer gate landed here is the
behavioural foundation that follow-up builds on. Integration
tests for the full end-to-end land in a third commit.
|
||
|
|
5e97f6ac6a |
constraints: CHECK-violation friendly error + typing-surface matrix (ADR-0029 §10)
Completes ADR-0029's implementation: the friendly-error layer now names the rule a CHECK violation broke, and the typing-surface matrix covers the whole constraint grammar. CHECK-violation friendly error (ADR-0029 §10): - enrich_dsl_failure gains a CHECK branch — it reads the column from the engine's `CHECK constraint failed: <column>` message, then resolves the table, the offending value, and the column's compiled CHECK expression. - FailureContext / TranslateContext carry the resolved check_rule; translate_check renders "the value <v> breaks the rule `<rule>`" when it is known, falling back to the plain hint otherwise. Typing-surface matrix: a new `constraints` submodule, 14 cells covering the create-table / add-column constraint suffix and the add-constraint / drop-constraint commands (174 → 188). 16 tests added (1 translate unit, 1 enrichment integration, 14 matrix cells). |