feat: build out main

This commit is contained in:
dualfroz committed 2026-09-06 19:37:12 +02:00
1 parent 1459f7fe37
commit 862de87af6
1 file changed
+182
+182
View File
@@ -374,6 +374,188 @@ const usage =
\\ matches a row where the field was never set OR set to an explicit
\\ none, e.g. city is_null and age gte i:18 (never uses an index, a
\\ sparse row's absence is not something a B-tree entry can represent)
\\ (empty expr selects every row; --limit/--offset page the result,
\\ limit 0 or omitted means unlimited; --order-by sorts by one or more
\\ comma-separated fields, applied left to right (later fields only
\\ break ties left by earlier ones), ascending by default or ":desc"
\\ per field for descending - rows missing a sort field sort first
\\ on it)
\\ table explain <table> [expr...]
\\ reports which strategy `table select` would use for the same
\\ <table>/expr WITHOUT running the query: full scan, a single-field
\\ index (equality or range), or a composite index (equality or range,
\\ naming how many of its leading columns the expr actually pinned down)
\\ table update <table> --set field=value[,field=value...] [expr...]
\\ applies the --set field list to every row matched by expr (empty expr
\\ updates every row); each set field overrides an existing field of the
\\ same name or is added if the row does not have it; unmentioned fields
\\ are left as-is; secondary/composite indexes on a changed field are
\\ kept in sync exactly like `table put` would; prints the number of
\\ rows updated
\\ table delete <table> [expr...]
\\ removes every row matched by expr (empty expr deletes every row, same
\\ convention as `table select`/`table update`); secondary/composite
\\ indexes on the deleted rows are kept in sync exactly like `table del`
\\ would for a single row; prints the number of rows deleted
\\ table index create|drop <table> <field...> (multiple fields = composite index)
\\ create --unique <table> <field>: single-field index that also enforces the
\\ field holds a distinct value across every row (SQL UNIQUE); refuses to
\\ install over data that already has a duplicate; dropping the index drops
\\ the constraint too. Not available for composite (multi-field) indexes.
\\ table fk create <table> <field> <refTable> <refField> [restrict|cascade]
\\ installs a FOREIGN KEY: every <table> row's (non-null) <field> value must
\\ name an existing <refField> on <refTable> (SQL FOREIGN KEY REFERENCES);
\\ <refField> must be either the special primary-key name "__pk" or a field
\\ already carrying its own --unique index on <refTable>, or the create is
\\ refused; refuses to install over a <table> row whose current value has
\\ no matching target; rows missing <field> entirely are exempt (nullable FK).
\\ Last argument picks ON DELETE behavior for a `table delete`/`table_delete`
\\ against <refTable>, default "restrict": restrict refuses the delete while
\\ a <table> row still points at it, cascade removes those rows too (and
\\ anything cascading from them, recursively). A `txn <script>` delete
\\ always restricts regardless of this setting - cascade only fires through
\\ the plain (non-transactional) delete path
\\ table fk drop <table> <field>
\\ removes the foreign key constraint on <field>, if any
\\ table check create <table> <name> <expr...>
\\ installs a named CHECK constraint (SQL CHECK): every row put into <table>
\\ from now on must evaluate <expr> (same predicate language as `table select`)
\\ to true, or the put is refused with CheckViolation; refuses to install over
\\ existing rows that already fail <expr>; a malformed <expr> is refused with
\\ InvalidCheckExpression before anything is persisted
\\ table check drop <table> <name>
\\ removes the named CHECK constraint, if any
\\ table join [--left|--right|--full] <leftTable> <leftField> <rightTable> <rightField>
\\ inner equi-join by default: prints "L <row>" / "R <row>" for every matched pair;
\\ --left makes it a left outer join: every left row still prints once, with
\\ "R (nil)" when it has no match on the right instead of being dropped;
\\ --right is the mirror image: every right row prints once, "L (nil)" when unmatched;
\\ --full is the union of both: every row on either side prints at least once;
\\ uses an index on <rightField> (or <leftField> for --right) when one exists,
\\ hash-joins otherwise; [--limit N] [--offset N] window the already-joined
\\ result, same convention as `table select` (limit 0 or omitted = unlimited)
\\ values/keys are typed: i:<int> f:<float> b:true|false s:<text>, bare text defaults to s:
\\ table aggregate <table> <groupField|*> <spec>[,<spec>...] [expr...] [having <expr>]
\\ [order <field[:desc]>[,field...]] [--limit N] [--offset N]
\\ spec is count | count:<field> | sum:<field> | avg:<field> | min:<field> | max:<field> |
\\ distinct:<field> (COUNT DISTINCT), comma-separated for more than one in the same
\\ pass; groupField "*" means no
\\ grouping (a single result row over every row matching expr, same predicate
\\ grammar as `table select`, and having/order/--limit/--offset are all ignored);
\\ rows missing groupField are excluded from every group, rows missing a spec's
\\ field are excluded from just that spec; --limit/--offset window the result
\\ groups, same convention as `table select`;
\\ an optional literal `having <expr>` after the row-filtering expr filters the
\\ RESULT groups after aggregation (same expr grammar, evaluated before
\\ order/--limit/--offset); its fields are the printed result labels (bare kind
\\ name for count, else "<kind>_<field>" e.g. sum_score) plus groupField itself,
\\ so e.g. `table aggregate orders city sum:total having sum_total gt i:1000`;
\\ an optional literal `order <field[:desc]>[,field...]` after having (or right
\\ after the row-filtering expr if there is no having) sorts the result groups
\\ before --limit/--offset windows them, one or more comma-separated columns
\\ applied left to right same as `table select`'s --order-by, each field naming
\\ either groupField or a result label (same two names having's expr can use),
\\ e.g. `table aggregate orders city sum:total order sum_total:desc --limit 3`
\\ (top 3 cities by total, without needing having at all); without groupField
\\ among the sort fields, ties on every named field keep first-seen order
\\ (stable sort)
\\ table window <table> <kind[:field]>[,<kind[:field]>...] [expr...] [partition <field>[,field...]]
\\ [order <field[:desc]>[,field...]] [--limit N] [--offset N] [--range]
\\ annotates every matching row with one or more computed window-function
\\ columns instead of collapsing rows like `table aggregate` does; kind is
\\ row_number | rank | dense_rank (no :field) | sum:<field> | avg:<field> |
\\ count | count:<field> | count_distinct:<field> | min:<field> | max:<field> |
\\ first_value:<field> | last_value:<field> | nth_value:<field>:<n>[:<default>] |
\\ lag:<field>[:<offset>[:<default>]] | lead:<field>[:<offset>[:<default>]];
\\ a comma-separated list (e.g. `sum:total,avg:total,rank`) computes every
\\ column over the SAME partition/order/frame, printed in the same order
\\ after the row's own fields;
\\ an optional literal `partition <field>[,field...]` splits rows into
\\ independent groups (like GROUP BY, first-seen order; omitted = one
\\ partition covering every matching row) and an optional literal `order
\\ <field[:desc]>[,field...]` (same syntax as `table select`'s
\\ --order-by) decides the ranking/running-total walk within each
\\ partition - partition must come before order if both are given;
\\ without order, rank/dense_rank treat the whole partition as tied
\\ (every row gets 1) and sum/avg/count/count_distinct/min/max give every
\\ row the same whole-partition total; with order, sum/avg/count/
\\ count_distinct/min/max become a running total walked in that order
\\ instead, by default one physical
\\ ROW at a time - even a row tied with its predecessor on `order` gets
\\ its OWN individual running value; --range switches this to SQL's
\\ actual default RANGE frame instead, where rows tied on every `order`
\\ column share the LAST tied row's combined value rather than their own
\\ (e.g. two rows both on day 1 both show day 1's combined total, not a
\\ half/full split); --range has no effect without `order` (both frames
\\ already agree there) and is rejected outright together with
\\ row_number/rank/dense_rank (neither takes a frame clause in standard
\\ SQL either); --limit/--offset window the printed rows in the SAME
\\ partition-then-order walk the window computation itself uses (every
\\ partition's rows in `order`, first-seen partition order), not raw
\\ scan order - e.g. `table window orders row_number partition city
\\ order amount:desc --limit 3` prints (up to) the first 3 rows of the
\\ first-seen partition in that order, not an arbitrary scan-order slice;
\\ lag/lead read <field>'s value <offset> rows before/after the current
\\ row within the partition's `order` sequence (first-seen order if no
\\ `order` given), always as a DISTINCT physical position even when tied
\\ with a neighbor on `order` (unlike the RANGE running-aggregate frame,
\\ which collapses ties); a row whose target falls outside the partition
\\ gets <default> (an `i:`/`f:`/`b:`/`s:`-prefixed literal, same grammar
\\ as put's field=value) or SQL NULL if omitted; <offset> defaults to 1
\\ if omitted; --range is rejected outright with lag/lead, same as with
\\ row_number/rank/dense_rank (a frame clause has no meaning for either);
\\ first_value/last_value/nth_value read <field>'s value from the current
\\ frame's first/last/n-th row - UNLIKE lag/lead, these DO respect --range:
\\ first_value is always the partition's first row (same in both frames),
\\ but last_value's frame end follows the running-aggregate split - without
\\ --range (ROWS, ending at CURRENT ROW) last_value degenerates to the
\\ row's own field (SQL's well-known gotcha), with --range it is instead
\\ the LAST row of the current tie group on `order`; without `order` at
\\ all, both give every row the whole partition's first/last value;
\\ nth_value shares first_value/last_value's frame exactly (same start,
\\ same ROWS/RANGE/no-order end) but reads the <n>-th row (1-based,
\\ required - unlike lag/lead's <offset>, SQL's NTH_VALUE has no default
\\ position) counted from the frame's START; a row whose current frame is
\\ still smaller than <n> gets <default> (same grammar as lag/lead's, SQL
\\ NULL if omitted) - e.g. under ROWS, nth_value:total:2 is NULL on the
\\ first row of every partition (frame size 1) and 2nd-row's value from
\\ then on, once the frame has grown enough to contain it
\\ table export <table> csv|json <path>
\\ writes every row to <path>; the column/key set is the union of every
\\ field name across every row (primary key first, "__pk", then
\\ first-seen order), a row missing a column just omits that cell/key
\\ rather than writing empty/null; every value is written as a typed
\\ string (i:/f:/b:/s:/x:/n:, same prefixes as put's field=value, plus
\\ x: for raw bytes hex and n: for none) so re-importing reproduces the
\\ exact original type, including floats (encoded as exact bits, not a
\\ decimal that could round-trip lossy); prints the row count written
\\ table import <table> csv|json <path>
\\ reads rows written by `table export` (or any file in the same shape)
\\ back into <table> via `table put`, so existing rows sharing a
\\ primary key are overwritten and secondary/composite indexes stay in
\\ sync; the "__pk" column/key is required; prints the row count read
\\
\\ txn <script-file> apply a script of `put|del <table> <pk> [field=value ...]`
\\ lines as one atomic transaction (all-or-nothing, error.Conflict
\\ aborts with nothing applied); blank lines and #comments skipped
\\
\\ repl [--script <file>] interactive shell over this database: put/get/del/
\\ scan/stats/flush/compact plus table put/get/del and
\\ coll <kind> <op> [args...] (counter/hash/set/list/
\\ zset), with begin/commit/rollback bracketing an
\\ interactive transaction (get mid-transaction can
\\ steer the next put, unlike a static `txn` script).
\\ Reads stdin with a prompt by default, or a file
\\ non-interactively with --script.
\\
\\ replicate <replica-dir> [--once] [--interval-ms <n>]
\\ tail this database's WAL and apply new writes onto
\\ <replica-dir> (opened/created fresh). Runs a bootstrap
\\ fullResync first, then loops calling syncOnce every
\\ interval-ms (default 200) until interrupted, or once
\\ and exit if --once is given. Recovers automatically
\\ from a fallen-behind tailer (primary flushed before
\\ catching up) via another fullResync.
\\
;