diff --git a/src/main.zig b/src/main.zig
index b3d9650..93f22e9 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,21 +1,206 @@
const std = @import("std");
const cobalt = @import("root.zig");
+const Value = cobalt.row.Value;
+const Field = cobalt.row.Field;
+
const usage =
\\cobalt - LSM key/value store
\\
\\usage: cobalt
[args]
\\
\\commands:
- \\ put store a value
- \\ get print a value or (nil)
+ \\ put [--ttl ]
+ \\ store a value, optionally set to expire
+ \\ get print a value or (nil); a key past its ttl
+ \\ reads as (nil) and is deleted lazily on this read
\\ del delete a key
+ \\ expire set/replace a key's ttl; error if the key does
+ \\ not currently exist (nonexistent keys have no ttl to set)
+ \\ persist remove a key's ttl, making it immortal again
+ \\ ttl print seconds left, -1 if no ttl, -2 if no such key
+ \\ expire-sweep actively delete every key whose ttl is due,
+ \\ not just the next one someone happens to read;
+ \\ prints how many were reaped
\\ scan [start] [end] forward range scan, start inclusive, end exclusive
\\ rscan [start] [end] reverse range scan
\\ prefix scan every key under a prefix
\\ compact run full leveled compaction
- \\ stats print database statistics
+ \\ stats [--prom] print database statistics (optionally as Prometheus text)
\\ verify write n keys, reopen, check durability and scan order
+ \\ serve [host:port] [--rate-limit ]
+ \\ [--per-conn-rate-limit ] [--auth]
+ \\ [--session-ttl ] [--session-sweep-interval ]
+ \\ [--ttl-sweep-interval ] [--coll-ttl-sweep-interval ]
+ \\ run a TCP server (default 127.0.0.1:6380),
+ \\ optionally capping requests with a shared
+ \\ token bucket and/or a private one per
+ \\ connection (both may be given together);
+ \\ --auth requires an AUTH before any op but
+ \\ ping, refuses to start if no admin user
+ \\ exists yet (see `user add`); --session-ttl
+ \\ (requires --auth) makes AUTH return a
+ \\ token valid for that many seconds - a new
+ \\ connection can present it via AUTH_TOKEN
+ \\ instead of the password, and the role it
+ \\ grants stops working once the ttl elapses
+ \\ even on a connection that never disconnects;
+ \\ --session-sweep-interval (requires
+ \\ --session-ttl) periodically drops sessions
+ \\ that expired but nobody ever presented again;
+ \\ --ttl-sweep-interval periodically reaps every
+ \\ key whose ttl is due, independent of --auth
+ \\ (like local `expire-sweep`, but automatic);
+ \\ --coll-ttl-sweep-interval does the same for
+ \\ whole collections (like local
+ \\ `coll expire-sweep`), independent of
+ \\ --ttl-sweep-interval - either, both, or
+ \\ neither may be given
+ \\
+ \\cobalt remote-select [expr...] [--limit N] [--offset N]
+ \\ [--order-by field[:desc][,field[:desc]...]]
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table select` there (same expr grammar as
+ \\ local `table select`; empty expr selects
+ \\ every row) - no local Db is opened;
+ \\ --user/--pass authenticate first, needed
+ \\ only if the server was started with --auth;
+ \\ --token authenticates with a previously
+ \\ issued session token instead of a password
+ \\ (requires the server to have --session-ttl)
+ \\
+ \\cobalt remote-explain [expr...]
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table explain` there (same expr grammar as
+ \\ local `table select`) - no local Db is opened;
+ \\ --user/--pass or --token authenticate first,
+ \\ same as `remote-select`
+ \\
+ \\cobalt remote-update --set field=value[,field=value...] [expr...]
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table update` there (same --set grammar and
+ \\ expr grammar as local `table update`; empty
+ \\ expr updates every row) - no local Db is
+ \\ opened; prints the number of rows updated;
+ \\ --user/--pass or --token authenticate first,
+ \\ same as `remote-select`
+ \\
+ \\cobalt remote-delete [expr...]
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table delete` there (same expr grammar as
+ \\ local `table delete`; empty expr deletes
+ \\ every row) - no local Db is opened; prints
+ \\ the number of rows deleted; --user/--pass or
+ \\ --token authenticate first, same as
+ \\ `remote-select`
+ \\
+ \\cobalt remote-export csv|json
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table export` there (same output shape as
+ \\ local `table export`), writing the result to
+ \\ on THIS machine - no local Db is
+ \\ opened; prints the row count written;
+ \\ --user/--pass or --token authenticate first,
+ \\ same as `remote-select`
+ \\
+ \\cobalt remote-import csv|json
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table import` there, reading on THIS
+ \\ machine and sending its bytes in one frame -
+ \\ no local Db is opened; prints the row count
+ \\ imported; --user/--pass or --token
+ \\ authenticate first, same as `remote-select`
+ \\
+ \\cobalt remote-join [--left|--right|--full]
+ \\ [--left-expr E] [--right-expr E]
+ \\ [--limit N] [--offset N]
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table join` there (same 4 modes as local
+ \\ `table join`; --left-expr/--right-expr are
+ \\ the same expr grammar as `table select`,
+ \\ filtering each side before the join runs;
+ \\ --limit/--offset window the already-joined
+ \\ result, same convention as `table select`) -
+ \\ no local Db is opened; --user/--pass or
+ \\ --token authenticate first, same as
+ \\ `remote-select`
+ \\
+ \\cobalt remote-aggregate [,...] [expr...]
+ \\ [having ] [order [,field...]]
+ \\ [--limit N] [--offset N]
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table aggregate` there (same spec grammar
+ \\ as local `table aggregate`: count |
+ \\ count:field | sum:field | avg:field |
+ \\ min:field | max:field | distinct:field,
+ \\ comma-separated;
+ \\ `*` as groupField means no GROUP BY, always
+ \\ at most one result row and --limit/--offset
+ \\ are ignored; expr filters rows before
+ \\ aggregating, same grammar as `table
+ \\ select`; an optional `having ` after
+ \\ expr filters the result groups after
+ \\ aggregation, same as local `table
+ \\ aggregate`; an optional `order
+ \\ [,field...]` after having
+ \\ sorts the result groups server-side before
+ \\ --limit/--offset window them, same as
+ \\ local `table aggregate`; --limit/--offset
+ \\ window the result groups, same convention
+ \\ as `table select`) - no local Db is opened;
+ \\ --user/--pass or --token authenticate
+ \\ first, same as `remote-select`
+ \\
+ \\cobalt remote-window [,...] [expr...]
+ \\ [partition [,field...]]
+ \\ [order [,field...]]
+ \\ [--limit N] [--offset N] [--range]
+ \\ [--user NAME --pass PASS] [--token TOKEN]
+ \\ connect to a running `cobalt serve` and run a
+ \\ `table window` there (same spec-list
+ \\ grammar as local `table window`: row_number |
+ \\ rank | dense_rank | count | count:field |
+ \\ count_distinct:field | sum:field | avg:field |
+ \\ min:field | max:field | first_value:field |
+ \\ last_value:field | nth_value:field:n[:default] |
+ \\ lag:field[:offset[:default]] |
+ \\ lead:field[:offset[:default]],
+ \\ comma-separated for multiple
+ \\ annotation columns sharing one partition/
+ \\ order/frame; lag/lead read the field's value
+ \\ `offset` rows before/after the current row
+ \\ within the partition's order sequence
+ \\ (default offset 1, default value SQL NULL
+ \\ unless an `i:`/`f:`/`b:`/`s:`-prefixed
+ \\ `default` literal is given); first_value/
+ \\ last_value/nth_value read the current
+ \\ frame's first/last/n-th row (n is 1-based,
+ \\ required, no SQL default) - UNLIKE lag/lead,
+ \\ these respect --range - see local `table
+ \\ window`'s help for the full frame-end
+ \\ semantics; expr
+ \\ filters rows before annotation, same
+ \\ grammar as `table select`;
+ \\ an optional `partition [,field...]`
+ \\ after expr splits rows into independent
+ \\ groups, same as local `table window`; an
+ \\ optional `order [,field...]`
+ \\ after partition orders rows within each
+ \\ partition for ranking/running-aggregate
+ \\ purposes; every matching row comes back
+ \\ annotated (never collapsed, unlike `table
+ \\ aggregate`); --limit/--offset window WHICH
+ \\ rows are returned, walked in the same
+ \\ partition-then-order sequence used to
+ \\ compute them, not raw scan order; --range
\\
;