1use super::verbs;
16use super::{Command, Kind, Param, Writes, DB, FORMAT, LIMIT};
17
18const QUERY_PARAMS: &[Param] = &[
20 Param {
21 name: "sql",
22 kind: Kind::Text,
23 required: true,
24 positional: true,
25 description: "The SQL to run. One statement. Use ?1, ?2 ... for values you pass in \
26 'params' rather than pasting them into the text.",
27 },
28 Param {
29 name: "params",
30 kind: Kind::Values,
31 required: false,
32 positional: false,
33 description: "The values for ?1, ?2 ... in order, as a JSON array of strings, numbers, \
34 booleans or nulls. An array of numbers is a vector and \
35 {\"blob\":\"<hex>\"} is bytes. Binding is how you avoid quoting mistakes \
36 and SQL injection.",
37 },
38 Param {
39 name: "params-file",
40 kind: Kind::Text,
41 required: false,
42 positional: false,
43 description: "A file holding the JSON array for 'params', or - for standard input. What \
44 a wrapper that spawns this binary uses: a command line has a length \
45 ceiling, about 32 KB on Windows, and a parameter past it fails outright.",
46 },
47 LIMIT,
48 DB,
49 FORMAT,
50];
51
52const EXEC_PARAMS: &[Param] = &[
54 Param {
55 name: "sql",
56 kind: Kind::Text,
57 required: true,
58 positional: true,
59 description: "One statement to run for its effect: INSERT, UPDATE, DELETE, CREATE, DROP, \
60 ALTER or a PRAGMA that sets something.",
61 },
62 Param {
63 name: "params",
64 kind: Kind::Values,
65 required: false,
66 positional: false,
67 description: "The values for ?1, ?2 ... in order, as a JSON array. An array of numbers \
68 is a vector and {\"blob\":\"<hex>\"} is bytes.",
69 },
70 Param {
71 name: "params-file",
72 kind: Kind::Text,
73 required: false,
74 positional: false,
75 description: "A file holding the JSON array for 'params', or - for standard input. What \
76 a wrapper that spawns this binary uses: a command line has a length \
77 ceiling, about 32 KB on Windows, and a parameter past it fails outright.",
78 },
79 DB,
80 FORMAT,
81];
82
83const BATCH_PARAMS: &[Param] = &[
85 Param {
86 name: "sql",
87 kind: Kind::Text,
88 required: true,
89 positional: true,
90 description: "Several statements separated by semicolons. They run as one transaction: \
91 either all of them take effect or none of them do.",
92 },
93 DB,
94 FORMAT,
95];
96
97const RUN_PARAMS: &[Param] = &[
99 Param {
100 name: "input",
101 kind: Kind::Text,
102 required: true,
103 positional: true,
104 description: "Shell input, exactly as you would type it: SQL statements ending in a \
105 semicolon, and dot commands such as '.schema' or '.mode box' on their own \
106 lines. Everything the interactive shell can do is reachable here.",
107 },
108 DB,
109];
110
111const CREATE_PARAMS: &[Param] = &[
113 Param {
114 name: "path",
115 kind: Kind::Text,
116 required: true,
117 positional: true,
118 description: "Where to make the new database file. It refuses a path that already \
119 exists rather than overwriting it.",
120 },
121 FORMAT,
122];
123
124const PATTERN_PARAMS: &[Param] = &[
126 Param {
127 name: "pattern",
128 kind: Kind::Text,
129 required: false,
130 positional: true,
131 description: "A LIKE pattern to filter the names by, such as 'user%'. Omit it for all \
132 of them.",
133 },
134 DB,
135 FORMAT,
136];
137
138const SCHEMA_PARAMS: &[Param] = &[
140 Param {
141 name: "pattern",
142 kind: Kind::Text,
143 required: false,
144 positional: true,
145 description: "A LIKE pattern naming which objects to show. Omit it for the whole schema.",
146 },
147 Param {
148 name: "indent",
149 kind: Kind::Boolean,
150 required: false,
151 positional: false,
152 description: "Pretty-print each CREATE statement over several lines instead of one.",
153 },
154 DB,
155 FORMAT,
156];
157
158const DESCRIBE_PARAMS: &[Param] = &[
160 Param {
161 name: "table",
162 kind: Kind::Text,
163 required: true,
164 positional: true,
165 description: "The table or view to describe, by its exact name. Use 'tables' first if \
166 you are not sure what it is called.",
167 },
168 DB,
169 FORMAT,
170];
171
172const DB_ONLY: &[Param] = &[DB, FORMAT];
174
175const EXPLAIN_PARAMS: &[Param] = &[
177 Param {
178 name: "sql",
179 kind: Kind::Text,
180 required: true,
181 positional: true,
182 description: "The statement whose plan you want. It is not run - this only shows which \
183 indexes and scans the planner chose.",
184 },
185 DB,
186 FORMAT,
187];
188
189const IMPORT_PARAMS: &[Param] = &[
191 Param {
192 name: "file",
193 kind: Kind::Text,
194 required: true,
195 positional: true,
196 description: "The delimited file to read.",
197 },
198 Param {
199 name: "table",
200 kind: Kind::Text,
201 required: true,
202 positional: false,
203 description: "The table to load into. If it does not exist it is created, taking its \
204 column names from the file's first row.",
205 },
206 Param {
207 name: "format",
208 kind: Kind::Text,
209 required: false,
210 positional: false,
211 description: "'csv' (the default, RFC 4180 quoting), 'tabs', or 'ascii' for \\037 and \
212 \\036 separated input.",
213 },
214 Param {
215 name: "skip",
216 kind: Kind::Integer,
217 required: false,
218 positional: false,
219 description: "How many leading rows to ignore, for a file with a preamble above its \
220 header.",
221 },
222 DB,
223];
224
225const EMBED_PARAMS: &[Param] = &[
227 Param {
228 name: "table",
229 kind: Kind::Text,
230 required: true,
231 positional: false,
232 description:
233 "The table to fill. An ordinary table with a VECTOR(768) or BLOB column, or an \
234 inillucent_search table, whose vector column can be updated by rowid.",
235 },
236 Param {
237 name: "text",
238 kind: Kind::Text,
239 required: true,
240 positional: false,
241 description: "The column holding the text to embed. A row whose text is NULL or empty is \
242 skipped and counted.",
243 },
244 Param {
245 name: "vector",
246 kind: Kind::Text,
247 required: true,
248 positional: false,
249 description:
250 "The column to write the vectors into. Only rows where it IS NULL are embedded, \
251 so a stopped run continues where it ended.",
252 },
253 Param {
254 name: "prefix",
255 kind: Kind::Text,
256 required: false,
257 positional: false,
258 description: "Text put in front of every value before it is embedded, exactly as \
259 embed('search_document: ' || body) does. For nomic-embed-text-v1.5 use \
260 'search_document: '. The default is no prefix.",
261 },
262 Param {
263 name: "device",
264 kind: Kind::Text,
265 required: false,
266 positional: false,
267 description:
268 "The processor: 'cpu', 'cuda' or 'cuda:N' for card N. Without it the machine's \
269 setting applies (INILLUCENT_EMBED_DEVICE, then what setup-embeddings \
270 recorded, then the processor). A cuda device that will not start is an error \
271 naming 'inillucent setup-embeddings runtime --gpu', and never runs on the \
272 processor.",
273 },
274 Param {
275 name: "threads",
276 kind: Kind::Integer,
277 required: false,
278 positional: false,
279 description: "Threads ONNX Runtime uses inside one operator, per session. Without it the \
280 machine's setting applies.",
281 },
282 Param {
283 name: "sessions",
284 kind: Kind::Integer,
285 required: false,
286 positional: false,
287 description: "How many model sessions to open on the device and run at once. The default \
288 is 1. Two sessions on one card were measured 2.1 times faster than one.",
289 },
290 Param {
291 name: "batch-size",
292 kind: Kind::Integer,
293 required: false,
294 positional: false,
295 description: "Most texts in one call to the model. The default is 16. A batch is also \
296 limited by a memory ceiling that shrinks it when the texts are long.",
297 },
298 Param {
299 name: "commit-every",
300 kind: Kind::Integer,
301 required: false,
302 positional: false,
303 description: "How many rows to embed and write in one transaction. The default is 1024. A \
304 crash loses at most one transaction.",
305 },
306 Param {
307 name: "all",
308 kind: Kind::Boolean,
309 required: false,
310 positional: false,
311 description: "Embed every row again, including rows that already have a vector.",
312 },
313 DB,
314 FORMAT,
315];
316
317const EXPORT_PARAMS: &[Param] = &[
319 Param {
320 name: "table",
321 kind: Kind::Text,
322 required: false,
323 positional: true,
324 description: "The table to write out whole. Give this or 'sql', not both.",
325 },
326 Param {
327 name: "sql",
328 kind: Kind::Text,
329 required: false,
330 positional: false,
331 description: "A query whose rows to write out, when you want less than a whole table.",
332 },
333 Param {
334 name: "format",
335 kind: Kind::Text,
336 required: false,
337 positional: false,
338 description: "csv, json, tabs, markdown, insert, quote, line or html. Defaults to csv.",
339 },
340 Param {
341 name: "out",
342 kind: Kind::Text,
343 required: false,
344 positional: false,
345 description: "A file to write to. Omitted, the rows come back in the result instead.",
346 },
347 DB,
348];
349
350const DUMP_PARAMS: &[Param] = &[
352 Param {
353 name: "objects",
354 kind: Kind::Text,
355 required: false,
356 positional: true,
357 description: "A LIKE pattern for the tables, indexes, triggers or views to dump. Omit \
358 it for the whole database.",
359 },
360 Param {
361 name: "data_only",
362 kind: Kind::Boolean,
363 required: false,
364 positional: false,
365 description: "Write only the INSERT statements, leaving out the CREATE statements.",
366 },
367 DB,
368];
369
370const BACKUP_PARAMS: &[Param] = &[
372 Param {
373 name: "file",
374 kind: Kind::Text,
375 required: true,
376 positional: true,
377 description: "Where to write the copy.",
378 },
379 DB,
380];
381
382const COPY_PARAMS: &[Param] = &[
384 Param {
385 name: "file",
386 kind: Kind::Text,
387 required: true,
388 positional: true,
389 description: "Where to write the copy. A file already there is refused, not replaced.",
390 },
391 DB,
392];
393
394const REKEY_PARAMS: &[Param] = &[
396 Param {
397 name: "new-key-file",
398 kind: Kind::Text,
399 required: false,
400 positional: false,
401 description: "A file holding the new key. Without it the new key is read from the INILLUCENT_NEW_KEY environment variable. A key is never taken as a word on the command line, where every user of the machine can read it.",
402 },
403 DB,
404];
405
406const RESTORE_PARAMS: &[Param] = &[
408 Param {
409 name: "file",
410 kind: Kind::Text,
411 required: true,
412 positional: true,
413 description: "The copy to read back. It replaces what is in the open database.",
414 },
415 DB,
416];
417
418const ANALYZE_PARAMS: &[Param] = &[
420 Param {
421 name: "table",
422 kind: Kind::Text,
423 required: false,
424 positional: true,
425 description: "One table to gather statistics for. Omit it for every table.",
426 },
427 DB,
428 FORMAT,
429];
430
431const MIGRATE_PARAMS: &[Param] = &[
433 Param {
434 name: "source",
435 kind: Kind::Text,
436 required: false,
437 positional: true,
438 description: "The SQLite database file to read, or a postgres:// or mysql:// connection \
439 URL. The source is never written to. A connection URL holds a password, \
440 and an argument is visible in the process list for the whole run - so it \
441 may be left out and given in INILLUCENT_SOURCE_URL instead, or written as \
442 '-' to read one line from standard input.",
443 },
444 Param {
445 name: "destination",
446 kind: Kind::Text,
447 required: true,
448 positional: false,
449 description: "The .rdb file to build. It refuses to overwrite an existing file.",
450 },
451 Param {
452 name: "kind",
453 kind: Kind::Text,
454 required: false,
455 positional: false,
456 description: "'sqlite' (the default for a path), 'postgres' or 'mysql' (the default for \
457 a URL written with that scheme), or 'index' for a legacy retrieval index.",
458 },
459 Param {
460 name: "batch",
461 kind: Kind::Integer,
462 required: false,
463 positional: false,
464 description: "Rows per destination transaction while copying from a server. Default \
465 10000. It changes how long the migration takes and nothing about what it \
466 produces.",
467 },
468 Param {
469 name: "insecure-plaintext",
470 kind: Kind::Boolean,
471 required: false,
472 positional: false,
473 description: "Permit an unencrypted connection to a server. A migration to a host that \
474 is not a loopback address uses verified TLS, and refuses rather than \
475 falling back; this permits plaintext, and only together with \
476 sslmode=disable in the URL. Both are needed because either one alone is \
477 something people type without meaning it. The choice is recorded in the \
478 migration report.",
479 },
480];
481
482const SEARCH_PARAMS: &[Param] = &[
484 Param {
485 name: "query",
486 kind: Kind::Text,
487 required: true,
488 positional: true,
489 description: "What to search for, in FTS5 query syntax: bare words are ANDed, \
490 \"a phrase\" is quoted, and OR and NOT are available.",
491 },
492 Param {
493 name: "table",
494 kind: Kind::Text,
495 required: true,
496 positional: false,
497 description: "The full-text table to search - one created with USING fts5(...) or \
498 USING inillucent_search(...).",
499 },
500 Param {
501 name: "k",
502 kind: Kind::Integer,
503 required: false,
504 positional: false,
505 description: "How many results to return, best first. Defaults to 10.",
506 },
507 Param {
508 name: "rerank",
509 kind: Kind::Boolean,
510 required: false,
511 positional: false,
512 description: "Reorder the rows the search found with the reranker, which reads the query \
513 and each row together. The query text is passed as the question, so write \
514 it as plain words. Needs an inillucent_search table and the reranker, \
515 installed with 'inillucent setup-embeddings reranker'. On the processor \
516 60 rows take about 10 seconds; on a graphics card about a tenth of one.",
517 },
518 DB,
519 FORMAT,
520];
521
522const VECTOR_PARAMS: &[Param] = &[
524 Param {
525 name: "table",
526 kind: Kind::Text,
527 required: true,
528 positional: true,
529 description: "The table holding the vectors.",
530 },
531 Param {
532 name: "column",
533 kind: Kind::Text,
534 required: true,
535 positional: false,
536 description: "The VECTOR(N) column to measure against.",
537 },
538 Param {
539 name: "vector",
540 kind: Kind::Values,
541 required: true,
542 positional: false,
543 description: "The query vector, as a JSON array of numbers with exactly N elements.",
544 },
545 Param {
546 name: "k",
547 kind: Kind::Integer,
548 required: false,
549 positional: false,
550 description: "How many nearest rows to return. Defaults to 10.",
551 },
552 Param {
553 name: "measure",
554 kind: Kind::Text,
555 required: false,
556 positional: false,
557 description: "'cos' for cosine distance (the default), 'l2' for Euclidean, or 'dot' \
558 for the inner product.",
559 },
560 DB,
561 FORMAT,
562];
563
564const CAPABILITY_PARAMS: &[Param] = &[
566 Param {
567 name: "name",
568 kind: Kind::Text,
569 required: false,
570 positional: true,
571 description: "One capability to ask about, such as 'triggers'. Omit it for the whole \
572 table. A name that is not in the table answers no, not yes.",
573 },
574 FORMAT,
575];
576
577const HELP_PARAMS: &[Param] = &[
579 Param {
580 name: "topic",
581 kind: Kind::Text,
582 required: false,
583 positional: true,
584 description: "A command to explain in full. Omit it for the list of every command.",
585 },
586 FORMAT,
587];
588
589const SETUP_PARAMS: &[Param] = &[
591 Param {
592 name: "component",
593 kind: Kind::Text,
594 required: false,
595 positional: true,
596 description: "What to install: 'all' for both halves, 'runtime' for the ONNX Runtime \
597 shared library on its own, 'model' for the embedding weights on their own, or \
598 'reranker' for the cross encoder that rerank() and a search naming question \
599 use (about 600 MB, and not part of 'all'). Omit it to report what is \
600 installed and download nothing.",
601 },
602 Param {
603 name: "status",
604 kind: Kind::Boolean,
605 required: false,
606 positional: false,
607 description: "Say what is installed, where, and which residency profile is in force, and \
608 download nothing.",
609 },
610 Param {
611 name: "residency",
612 kind: Kind::Text,
613 required: false,
614 positional: false,
615 description: "When the model is in memory: 'resident' keeps it for the life of the process, \
616 'on-demand' loads it per call and drops it, 'idle' or 'idle:90s' loads it on \
617 use and drops it after a quiet period. Recorded for this machine; \
618 INILLUCENT_EMBED_RESIDENCY overrides it for one process.",
619 },
620 Param {
621 name: "threads",
622 kind: Kind::Integer,
623 required: false,
624 positional: false,
625 description: "How many threads ONNX Runtime uses inside one operator, for embed(), \
626 rerank() and a reranked search. Recorded for this machine; \
627 INILLUCENT_EMBED_THREADS overrides it for one process. Without it ONNX \
628 Runtime picks its own count. Four was the fastest single process setting \
629 measured for the embedding model.",
630 },
631 Param {
632 name: "device",
633 kind: Kind::Text,
634 required: false,
635 positional: false,
636 description: "The processor the sessions run on: 'cpu', 'cuda' or 'cuda:N' for card N. \
637 Recorded for this machine; INILLUCENT_EMBED_DEVICE overrides it for one \
638 process. A cuda device that will not start is an error and never runs on \
639 the processor. It needs the runtime installed with '--gpu'.",
640 },
641 Param {
642 name: "gpu",
643 kind: Kind::Boolean,
644 required: false,
645 positional: false,
646 description: "Install the ONNX Runtime build carrying the CUDA execution provider, which \
647 exists for Windows and Linux on x86-64 only. It is a much larger download and \
648 it needs a CUDA install of its own to be usable.",
649 },
650 Param {
651 name: "force",
652 kind: Kind::Boolean,
653 required: false,
654 positional: false,
655 description: "Fetch and install again even when the files are already there and their \
656 digests match.",
657 },
658 Param {
659 name: "onnxruntime-version",
660 kind: Kind::Text,
661 required: false,
662 positional: false,
663 description: "The ONNX Runtime version to install. Defaults to the one this build pins a \
664 digest for; any other version is fetched and reported as unverified.",
665 },
666 Param {
667 name: "dir",
668 kind: Kind::Text,
669 required: false,
670 positional: false,
671 description: "Install somewhere other than the per-user directory. INILLUCENT_HOME does the \
672 same thing for every command at once.",
673 },
674 Param {
675 name: "from",
676 kind: Kind::Text,
677 required: false,
678 positional: false,
679 description: "Install the model or the reranker from a folder already on this machine \
680 instead of downloading it, for a network that cannot reach Hugging Face. \
681 Each file is found by its installed name or by its path in the Hugging \
682 Face repository, such as onnx/model.onnx, and is checked against the \
683 SHA-256 this build pins before anything is copied. One file missing or \
684 different and nothing is installed. The runtime still downloads.",
685 },
686 FORMAT,
687];
688
689const VERSION_PARAMS: &[Param] = &[FORMAT];
691
692pub static COMMANDS: &[Command] = &[
694 Command {
695 name: "query",
696 summary: "Run a SELECT and get its rows back.",
697 detail: "Use this for anything that reads. The rows come back as a table, or as typed \
698 JSON with output=json. 'total' is exact even when 'limit' cut the rows handed \
699 back, because the engine materialises the whole result - so a limit of 10 on a \
700 million-row query still costs what the million rows cost. Put a LIMIT in the SQL \
701 itself when you cannot afford that, where the planner can act on it.",
702 params: QUERY_PARAMS,
703 cli_only: None,
704 writes: Writes::No,
705 run: verbs::query,
706 },
707 Command {
708 name: "exec",
709 summary: "Run one statement that changes something, and get the row count back.",
710 detail: "INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, or a PRAGMA that sets a value. A \
711 statement with RETURNING gives its rows back as well as its count.",
712 params: EXEC_PARAMS,
713 cli_only: None,
714 writes: Writes::Yes,
715 run: verbs::exec,
716 },
717 Command {
718 name: "batch",
719 summary: "Run several statements as one transaction.",
720 detail: "Separate them with semicolons. Either all of them take effect or none of them \
721 do, which is what you want when creating a schema or loading related rows.",
722 params: BATCH_PARAMS,
723 cli_only: None,
724 writes: Writes::Yes,
725 run: verbs::batch,
726 },
727 Command {
728 name: "run",
729 summary: "Run shell input, dot commands and all, and get back what it printed.",
730 detail: "The escape hatch, and the reason every dot command is reachable from every front \
731 end: this drives the same shell 'inillucent shell' does. Use it for the things \
732 that have no verb of their own - '.eqp on', '.parameter set', '.testcase', \
733 '.archive'. Run 'inillucent run \".help\"' for the full list of dot commands.",
734 params: RUN_PARAMS,
735 cli_only: None,
736 writes: Writes::PerStatement,
742 run: verbs::run_input,
743 },
744 Command {
745 name: "create",
746 summary: "Make a new, empty database file.",
747 detail: "Refuses a path that already exists, so it can never destroy a database by being \
748 run twice. Every other command opens whatever it is pointed at.",
749 params: CREATE_PARAMS,
750 cli_only: None,
751 writes: Writes::Yes,
752 run: verbs::create,
753 },
754 Command {
755 name: "tables",
756 summary: "List the tables and views.",
757 detail: "The names, with what each one is. System tables whose names begin with sqlite_ \
758 are left out.",
759 params: PATTERN_PARAMS,
760 cli_only: None,
761 writes: Writes::No,
762 run: verbs::tables,
763 },
764 Command {
765 name: "describe",
766 summary: "Everything about one table: columns, types, keys, indexes, row count and DDL.",
767 detail: "Call this before writing SQL against a table you did not create. It answers in \
768 one call what four separate pragmas would, which matters because a caller that \
769 has to make four usually makes three and writes its query from an incomplete \
770 picture.",
771 params: DESCRIBE_PARAMS,
772 cli_only: None,
773 writes: Writes::No,
774 run: verbs::describe,
775 },
776 Command {
777 name: "schema",
778 summary: "Show the CREATE statements for the whole database or for what a pattern names.",
779 detail: "The schema as SQL, which is the form you can paste into another database.",
780 params: SCHEMA_PARAMS,
781 cli_only: None,
782 writes: Writes::No,
783 run: verbs::schema,
784 },
785 Command {
786 name: "indexes",
787 summary: "List the indexes and the table each one is on.",
788 detail: "Including the ones a UNIQUE constraint or a primary key created, which is why \
789 an index you did not write may appear here.",
790 params: PATTERN_PARAMS,
791 cli_only: None,
792 writes: Writes::No,
793 run: verbs::indexes,
794 },
795 Command {
796 name: "databases",
797 summary: "List the attached databases and the file behind each.",
798 detail: "'main' is the one that was opened; others come from ATTACH. 'temp' is the \
799 session's own scratch database and has no file.",
800 params: DB_ONLY,
801 cli_only: None,
802 writes: Writes::No,
803 run: verbs::databases,
804 },
805 Command {
806 name: "explain",
807 summary: "Show the query plan for a statement without running it.",
808 detail: "Which indexes are used, which scans are full, and in what order the tables are \
809 joined. This is how you find out why a query is slow before making it faster.",
810 params: EXPLAIN_PARAMS,
811 cli_only: None,
812 writes: Writes::No,
813 run: verbs::explain,
814 },
815 Command {
816 name: "import",
817 summary: "Load a CSV or tab-separated file into a table.",
818 detail: "The table is created from the file's first row if it does not exist. Quoting is \
819 RFC 4180 unless a different format is asked for.",
820 params: IMPORT_PARAMS,
821 cli_only: None,
822 writes: Writes::Yes,
823 run: verbs::import,
824 },
825 Command {
826 name: "embed",
827 summary: "Fill a vector column with embeddings, on the processor or a graphics card.",
828 detail: "Reads every row of --table whose --vector column IS NULL, embeds the --text column \
829 with the nomic-embed-text-v1.5 model, and writes the vectors in the layout embed() \
830 returns. It sorts the texts by length, groups them under a memory ceiling, and \
831 commits every --commit-every rows, so a stopped run continues where it ended. A \
832 corpus of 558,429 chunks took 22 minutes on a graphics card, where embed() in SQL, \
833 one row at a time on the processor, would take about 12 hours. It prints how many \
834 rows were embedded, how many were skipped because the text was NULL or empty, and \
835 how many were cut at the model's token limit, with the rowid of up to 20 of them. \
836 Needs the model: run 'inillucent setup-embeddings all' first, and \
837 'inillucent setup-embeddings runtime --gpu' for a card.",
838 params: EMBED_PARAMS,
839 cli_only: None,
840 writes: Writes::Yes,
841 run: crate::bulk_embed::embed_table,
842 },
843 Command {
844 name: "export",
845 summary: "Write a table or a query's rows out as CSV, JSON or one of six other formats.",
846 detail: "With 'out' the rows go to a file and the result says so; without it they come \
847 back in the result, which is usually what an agent wants.",
848 params: EXPORT_PARAMS,
849 cli_only: None,
850 writes: Writes::No,
851 run: verbs::export,
852 },
853 Command {
854 name: "dump",
855 summary: "Render the database as the SQL that would rebuild it.",
856 detail: "Schema and data, in dependency order, inside a transaction. This is the \
857 portable form: it is text, and another SQLite-speaking database will read it.",
858 params: DUMP_PARAMS,
859 cli_only: None,
860 writes: Writes::No,
861 run: verbs::dump,
862 },
863 Command {
864 name: "backup",
865 summary: "Write a copy of the database to another file.",
866 detail: "A consistent copy taken while the database is open. The copy is a database, not \
867 a text dump.",
868 params: BACKUP_PARAMS,
869 cli_only: None,
870 writes: Writes::No,
871 run: verbs::backup,
872 },
873 Command {
874 name: "encrypt",
875 summary: "Write an encrypted copy of a plaintext database.",
876 detail: "The copy is encrypted with the key from --key-file or INILLUCENT_KEY, and holds every row, index, view and trigger of the database --db names, which is read without the key. The source is left as it was: replace it with the copy once you have checked the copy opens.",
877 params: COPY_PARAMS,
878 cli_only: Some(
879 "it takes its key from the program's own --key-file or environment, and an MCP client has neither. Encrypt a database before serving it.",
880 ),
881 writes: Writes::No,
882 run: verbs::encrypt,
883 },
884 Command {
885 name: "decrypt",
886 summary: "Write a plaintext copy of an encrypted database.",
887 detail: "Opens the database --db names with the key from --key-file or INILLUCENT_KEY and writes every row, index, view and trigger to a file that is not encrypted. The encrypted database is left as it was.",
888 params: COPY_PARAMS,
889 cli_only: Some(
890 "it writes every row of an encrypted database to disk unprotected, which is a decision for the person who holds the key and not for an agent the database is served to.",
891 ),
892 writes: Writes::No,
893 run: verbs::decrypt,
894 },
895 Command {
896 name: "rekey",
897 summary: "Change the key an encrypted database is encrypted with.",
898 detail: "Opens the database with the key from --key-file or INILLUCENT_KEY and moves it to the new key from --new-key-file or INILLUCENT_NEW_KEY. Only the header of each file is rewritten, so it takes the same time whatever the database's size, and the old key stops opening the database when it returns. PRAGMA rekey does the same from SQL.",
899 params: REKEY_PARAMS,
900 cli_only: Some(
901 "an agent that could change the key could lock the database's owner out of it.",
902 ),
903 writes: Writes::Yes,
904 run: verbs::rekey,
905 },
906 Command {
907 name: "restore",
908 summary: "Point this session at a backup file, in place of the database it opened.",
909 detail: "The opposite of 'backup', and it does not overwrite anything: this engine's \
910 databases are whole files, so restoring is opening the other file rather than \
911 writing its pages over the one you are in. That means it lasts as long as the \
912 session does - useful from the shell and from 'run', where the statements after \
913 it read the restored file, and of no effect on its own, because a one-shot \
914 process ends immediately after. To replace a file, copy the backup over it. A \
915 backup file that is not there is refused rather than created empty.",
916 params: RESTORE_PARAMS,
917 cli_only: None,
918 writes: Writes::Yes,
919 run: verbs::restore,
920 },
921 Command {
922 name: "checkpoint",
923 summary: "Fold the write-ahead log back into the database file.",
924 detail: "Writes go to a log first and are folded in later. Doing it now shrinks the log \
925 and is what you want before copying the file by hand.",
926 params: DB_ONLY,
927 cli_only: None,
928 writes: Writes::Yes,
929 run: verbs::checkpoint,
930 },
931 Command {
932 name: "integrity-check",
933 summary: "Read every page and report whether the database holds together.",
934 detail: "Answers 'ok' on a healthy database. Anything else names what is wrong. It reads \
935 the whole file, so it costs what the file costs.",
936 params: DB_ONLY,
937 cli_only: None,
938 writes: Writes::No,
939 run: verbs::integrity_check,
940 },
941 Command {
942 name: "analyze",
943 summary: "Gather the statistics the query planner reads.",
944 detail: "Run it after loading a lot of data. Without statistics the planner guesses at \
945 how selective an index is, and a wrong guess is the usual reason a query that \
946 should use an index does not.",
947 params: ANALYZE_PARAMS,
948 cli_only: None,
949 writes: Writes::Yes,
950 run: verbs::analyze,
951 },
952 Command {
953 name: "stats",
954 summary: "Report the page cache, the pool size and the shape of the file.",
955 detail: "Cache hits and misses, how many pages the file holds and how many are free. \
956 This is where you look when a workload is slower than it should be.",
957 params: DB_ONLY,
958 cli_only: None,
959 writes: Writes::No,
960 run: verbs::stats,
961 },
962 Command {
963 name: "search",
964 summary: "Full-text search over an FTS5 or inillucent_search table.",
965 detail: "Writes the MATCH ... ORDER BY rank idiom for you, which is the part nobody \
966 remembers. The table has to be a full-text one; 'describe' will show you \
967 whether it is.",
968 params: SEARCH_PARAMS,
969 cli_only: None,
970 writes: Writes::No,
971 run: verbs::search,
972 },
973 Command {
974 name: "vector-search",
975 summary: "Find the rows whose vector is nearest to one you supply.",
976 detail: "Over a VECTOR(N) column, by cosine distance unless you ask for another measure. \
977 If there is an HNSW index on the column the planner uses it; if there is not, \
978 this is an exhaustive scan and is still correct.",
979 params: VECTOR_PARAMS,
980 cli_only: None,
981 writes: Writes::No,
982 run: verbs::vector_search,
983 },
984 Command {
985 name: "capabilities",
986 summary: "Ask what this engine can do before composing a statement.",
987 detail: "Every row is checked against the running engine by a test, in both directions - \
988 a claim of support that fails and a claim of absence that now works each turn \
989 the build red. So this is worth trusting in a way a hand-maintained feature \
990 list is not. A name that is not in the table answers no, because a capability \
991 that was never declared was never checked.",
992 params: CAPABILITY_PARAMS,
993 cli_only: None,
994 writes: Writes::No,
995 run: verbs::capabilities,
996 },
997 Command {
998 name: "functions",
999 summary: "List the SQL functions this engine answers.",
1000 detail: "From the engine's own register, which is compared against the reference \
1001 library's on every build - so this is what actually exists rather than what was \
1002 documented once.",
1003 params: PATTERN_PARAMS,
1004 cli_only: None,
1005 writes: Writes::No,
1006 run: verbs::functions,
1007 },
1008 Command {
1009 name: "migrate",
1010 summary: "Build an inillucent database from a SQLite file, PostgreSQL or MySQL.",
1011 detail: "Reads the source and writes a new .rdb with the same rows. A path is a SQLite \
1012 database file; a postgres:// or mysql:// URL is a running server, read inside \
1013 one repeatable-read snapshot so that every table is as of one instant. The \
1014 source is never written to and the destination is never overwritten: the new \
1015 file is staged under another name and published by a rename, so a half-written \
1016 database never sits where an application would open it. A server migration is \
1017 verified per table by row count and by an order-independent digest, and nothing \
1018 that fails a check is published.",
1019 params: MIGRATE_PARAMS,
1020 cli_only: None,
1021 writes: Writes::Yes,
1022 run: verbs::migrate,
1023 },
1024 Command {
1025 name: "setup-embeddings",
1026 summary: "Download and install the embedding model and the runtime it needs.",
1027 detail: "One command, on Windows, macOS and Linux. It fetches ONNX Runtime and the \
1028 nomic-embed-text-v1.5 weights into a per-user directory, checks every byte \
1029 against a digest pinned in this build, and leaves the engine able to answer \
1030 embed(TEXT) with nothing exported by hand - so a mismatch is a refusal that \
1031 names both digests rather than a shared library that loads and misbehaves. \
1032 Name what you want: 'all' installs both halves, 'runtime' and 'model' one \
1033 each. Run with no component at all and it reports what is installed and \
1034 downloads nothing, which is what stops a 620 MB fetch being a surprise; \
1035 '--status' does the same explicitly. About 620 MB the first time and \
1036 nothing on a later run. '--residency' chooses when the model is in memory: \
1037 'resident' keeps it, which is about 1.9 GB held and 12 to 36 ms a query; \
1038 'on-demand' loads it per call, which holds nothing and costs about 0.8 s a \
1039 query; 'idle' or 'idle:90s' loads it on use and drops it after a quiet \
1040 period, which is the default and pays the load once for a burst of \
1041 questions.",
1042 params: SETUP_PARAMS,
1043 cli_only: None,
1044 writes: Writes::Yes,
1045 run: crate::setup::setup_embeddings,
1046 },
1047 Command {
1048 name: "version",
1049 summary: "Report the engine, the dialect and the driver versions.",
1050 detail: "The SQLite version named here is the dialect this engine implements, not a \
1051 library it links. There is no SQLite in this binary.",
1052 params: VERSION_PARAMS,
1053 cli_only: None,
1054 writes: Writes::No,
1055 run: verbs::version,
1056 },
1057 Command {
1058 name: "help",
1059 summary: "List every command, or explain one in full.",
1060 detail: "With no topic it prints the table. With one it prints that command's usage, \
1061 what it is for, and every parameter it takes.",
1062 params: HELP_PARAMS,
1063 cli_only: None,
1064 writes: Writes::No,
1065 run: verbs::help,
1066 },
1067 Command {
1068 name: "shell",
1069 summary: "Start the interactive shell.",
1070 detail: "The sqlite3-shaped REPL, with all 63 dot commands. Everything it can do is also \
1071 reachable non-interactively through 'run'.",
1072 params: &[],
1073 cli_only: Some(
1074 "it is a terminal REPL: it reads a keyboard and writes a screen, and neither exists \
1075 at the other end of an MCP call. Use 'run' instead, which drives the same shell.",
1076 ),
1077 writes: Writes::Yes,
1078 run: verbs::shell_placeholder,
1079 },
1080 Command {
1081 name: "mcp",
1082 summary: "Serve these commands to an agent over MCP on standard input and output.",
1083 detail: "Every command in this table that is not marked cli-only becomes a tool named \
1084 inillucent_<command>, with this same description and these same parameters.",
1085 params: &[],
1086 cli_only: Some(
1087 "it is the server that would be exposing the tools, so offering it as one of them \
1088 would let a client ask the server to serve itself.",
1089 ),
1090 writes: Writes::Yes,
1091 run: verbs::mcp_placeholder,
1092 },
1093];