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 FORMAT,
675];
676
677const VERSION_PARAMS: &[Param] = &[FORMAT];
679
680pub static COMMANDS: &[Command] = &[
682 Command {
683 name: "query",
684 summary: "Run a SELECT and get its rows back.",
685 detail: "Use this for anything that reads. The rows come back as a table, or as typed \
686 JSON with output=json. 'total' is exact even when 'limit' cut the rows handed \
687 back, because the engine materialises the whole result - so a limit of 10 on a \
688 million-row query still costs what the million rows cost. Put a LIMIT in the SQL \
689 itself when you cannot afford that, where the planner can act on it.",
690 params: QUERY_PARAMS,
691 cli_only: None,
692 writes: Writes::No,
693 run: verbs::query,
694 },
695 Command {
696 name: "exec",
697 summary: "Run one statement that changes something, and get the row count back.",
698 detail: "INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, or a PRAGMA that sets a value. A \
699 statement with RETURNING gives its rows back as well as its count.",
700 params: EXEC_PARAMS,
701 cli_only: None,
702 writes: Writes::Yes,
703 run: verbs::exec,
704 },
705 Command {
706 name: "batch",
707 summary: "Run several statements as one transaction.",
708 detail: "Separate them with semicolons. Either all of them take effect or none of them \
709 do, which is what you want when creating a schema or loading related rows.",
710 params: BATCH_PARAMS,
711 cli_only: None,
712 writes: Writes::Yes,
713 run: verbs::batch,
714 },
715 Command {
716 name: "run",
717 summary: "Run shell input, dot commands and all, and get back what it printed.",
718 detail: "The escape hatch, and the reason every dot command is reachable from every front \
719 end: this drives the same shell 'inillucent shell' does. Use it for the things \
720 that have no verb of their own - '.eqp on', '.parameter set', '.testcase', \
721 '.archive'. Run 'inillucent run \".help\"' for the full list of dot commands.",
722 params: RUN_PARAMS,
723 cli_only: None,
724 writes: Writes::PerStatement,
730 run: verbs::run_input,
731 },
732 Command {
733 name: "create",
734 summary: "Make a new, empty database file.",
735 detail: "Refuses a path that already exists, so it can never destroy a database by being \
736 run twice. Every other command opens whatever it is pointed at.",
737 params: CREATE_PARAMS,
738 cli_only: None,
739 writes: Writes::Yes,
740 run: verbs::create,
741 },
742 Command {
743 name: "tables",
744 summary: "List the tables and views.",
745 detail: "The names, with what each one is. System tables whose names begin with sqlite_ \
746 are left out.",
747 params: PATTERN_PARAMS,
748 cli_only: None,
749 writes: Writes::No,
750 run: verbs::tables,
751 },
752 Command {
753 name: "describe",
754 summary: "Everything about one table: columns, types, keys, indexes, row count and DDL.",
755 detail: "Call this before writing SQL against a table you did not create. It answers in \
756 one call what four separate pragmas would, which matters because a caller that \
757 has to make four usually makes three and writes its query from an incomplete \
758 picture.",
759 params: DESCRIBE_PARAMS,
760 cli_only: None,
761 writes: Writes::No,
762 run: verbs::describe,
763 },
764 Command {
765 name: "schema",
766 summary: "Show the CREATE statements for the whole database or for what a pattern names.",
767 detail: "The schema as SQL, which is the form you can paste into another database.",
768 params: SCHEMA_PARAMS,
769 cli_only: None,
770 writes: Writes::No,
771 run: verbs::schema,
772 },
773 Command {
774 name: "indexes",
775 summary: "List the indexes and the table each one is on.",
776 detail: "Including the ones a UNIQUE constraint or a primary key created, which is why \
777 an index you did not write may appear here.",
778 params: PATTERN_PARAMS,
779 cli_only: None,
780 writes: Writes::No,
781 run: verbs::indexes,
782 },
783 Command {
784 name: "databases",
785 summary: "List the attached databases and the file behind each.",
786 detail: "'main' is the one that was opened; others come from ATTACH. 'temp' is the \
787 session's own scratch database and has no file.",
788 params: DB_ONLY,
789 cli_only: None,
790 writes: Writes::No,
791 run: verbs::databases,
792 },
793 Command {
794 name: "explain",
795 summary: "Show the query plan for a statement without running it.",
796 detail: "Which indexes are used, which scans are full, and in what order the tables are \
797 joined. This is how you find out why a query is slow before making it faster.",
798 params: EXPLAIN_PARAMS,
799 cli_only: None,
800 writes: Writes::No,
801 run: verbs::explain,
802 },
803 Command {
804 name: "import",
805 summary: "Load a CSV or tab-separated file into a table.",
806 detail: "The table is created from the file's first row if it does not exist. Quoting is \
807 RFC 4180 unless a different format is asked for.",
808 params: IMPORT_PARAMS,
809 cli_only: None,
810 writes: Writes::Yes,
811 run: verbs::import,
812 },
813 Command {
814 name: "embed",
815 summary: "Fill a vector column with embeddings, on the processor or a graphics card.",
816 detail: "Reads every row of --table whose --vector column IS NULL, embeds the --text column \
817 with the nomic-embed-text-v1.5 model, and writes the vectors in the layout embed() \
818 returns. It sorts the texts by length, groups them under a memory ceiling, and \
819 commits every --commit-every rows, so a stopped run continues where it ended. A \
820 corpus of 558,429 chunks took 22 minutes on a graphics card, where embed() in SQL, \
821 one row at a time on the processor, would take about 12 hours. It prints how many \
822 rows were embedded, how many were skipped because the text was NULL or empty, and \
823 how many were cut at the model's token limit, with the rowid of up to 20 of them. \
824 Needs the model: run 'inillucent setup-embeddings all' first, and \
825 'inillucent setup-embeddings runtime --gpu' for a card.",
826 params: EMBED_PARAMS,
827 cli_only: None,
828 writes: Writes::Yes,
829 run: crate::bulk_embed::embed_table,
830 },
831 Command {
832 name: "export",
833 summary: "Write a table or a query's rows out as CSV, JSON or one of six other formats.",
834 detail: "With 'out' the rows go to a file and the result says so; without it they come \
835 back in the result, which is usually what an agent wants.",
836 params: EXPORT_PARAMS,
837 cli_only: None,
838 writes: Writes::No,
839 run: verbs::export,
840 },
841 Command {
842 name: "dump",
843 summary: "Render the database as the SQL that would rebuild it.",
844 detail: "Schema and data, in dependency order, inside a transaction. This is the \
845 portable form: it is text, and another SQLite-speaking database will read it.",
846 params: DUMP_PARAMS,
847 cli_only: None,
848 writes: Writes::No,
849 run: verbs::dump,
850 },
851 Command {
852 name: "backup",
853 summary: "Write a copy of the database to another file.",
854 detail: "A consistent copy taken while the database is open. The copy is a database, not \
855 a text dump.",
856 params: BACKUP_PARAMS,
857 cli_only: None,
858 writes: Writes::No,
859 run: verbs::backup,
860 },
861 Command {
862 name: "encrypt",
863 summary: "Write an encrypted copy of a plaintext database.",
864 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.",
865 params: COPY_PARAMS,
866 cli_only: Some(
867 "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.",
868 ),
869 writes: Writes::No,
870 run: verbs::encrypt,
871 },
872 Command {
873 name: "decrypt",
874 summary: "Write a plaintext copy of an encrypted database.",
875 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.",
876 params: COPY_PARAMS,
877 cli_only: Some(
878 "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.",
879 ),
880 writes: Writes::No,
881 run: verbs::decrypt,
882 },
883 Command {
884 name: "rekey",
885 summary: "Change the key an encrypted database is encrypted with.",
886 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.",
887 params: REKEY_PARAMS,
888 cli_only: Some(
889 "an agent that could change the key could lock the database's owner out of it.",
890 ),
891 writes: Writes::Yes,
892 run: verbs::rekey,
893 },
894 Command {
895 name: "restore",
896 summary: "Point this session at a backup file, in place of the database it opened.",
897 detail: "The opposite of 'backup', and it does not overwrite anything: this engine's \
898 databases are whole files, so restoring is opening the other file rather than \
899 writing its pages over the one you are in. That means it lasts as long as the \
900 session does - useful from the shell and from 'run', where the statements after \
901 it read the restored file, and of no effect on its own, because a one-shot \
902 process ends immediately after. To replace a file, copy the backup over it. A \
903 backup file that is not there is refused rather than created empty.",
904 params: RESTORE_PARAMS,
905 cli_only: None,
906 writes: Writes::Yes,
907 run: verbs::restore,
908 },
909 Command {
910 name: "checkpoint",
911 summary: "Fold the write-ahead log back into the database file.",
912 detail: "Writes go to a log first and are folded in later. Doing it now shrinks the log \
913 and is what you want before copying the file by hand.",
914 params: DB_ONLY,
915 cli_only: None,
916 writes: Writes::Yes,
917 run: verbs::checkpoint,
918 },
919 Command {
920 name: "integrity-check",
921 summary: "Read every page and report whether the database holds together.",
922 detail: "Answers 'ok' on a healthy database. Anything else names what is wrong. It reads \
923 the whole file, so it costs what the file costs.",
924 params: DB_ONLY,
925 cli_only: None,
926 writes: Writes::No,
927 run: verbs::integrity_check,
928 },
929 Command {
930 name: "analyze",
931 summary: "Gather the statistics the query planner reads.",
932 detail: "Run it after loading a lot of data. Without statistics the planner guesses at \
933 how selective an index is, and a wrong guess is the usual reason a query that \
934 should use an index does not.",
935 params: ANALYZE_PARAMS,
936 cli_only: None,
937 writes: Writes::Yes,
938 run: verbs::analyze,
939 },
940 Command {
941 name: "stats",
942 summary: "Report the page cache, the pool size and the shape of the file.",
943 detail: "Cache hits and misses, how many pages the file holds and how many are free. \
944 This is where you look when a workload is slower than it should be.",
945 params: DB_ONLY,
946 cli_only: None,
947 writes: Writes::No,
948 run: verbs::stats,
949 },
950 Command {
951 name: "search",
952 summary: "Full-text search over an FTS5 or inillucent_search table.",
953 detail: "Writes the MATCH ... ORDER BY rank idiom for you, which is the part nobody \
954 remembers. The table has to be a full-text one; 'describe' will show you \
955 whether it is.",
956 params: SEARCH_PARAMS,
957 cli_only: None,
958 writes: Writes::No,
959 run: verbs::search,
960 },
961 Command {
962 name: "vector-search",
963 summary: "Find the rows whose vector is nearest to one you supply.",
964 detail: "Over a VECTOR(N) column, by cosine distance unless you ask for another measure. \
965 If there is an HNSW index on the column the planner uses it; if there is not, \
966 this is an exhaustive scan and is still correct.",
967 params: VECTOR_PARAMS,
968 cli_only: None,
969 writes: Writes::No,
970 run: verbs::vector_search,
971 },
972 Command {
973 name: "capabilities",
974 summary: "Ask what this engine can do before composing a statement.",
975 detail: "Every row is checked against the running engine by a test, in both directions - \
976 a claim of support that fails and a claim of absence that now works each turn \
977 the build red. So this is worth trusting in a way a hand-maintained feature \
978 list is not. A name that is not in the table answers no, because a capability \
979 that was never declared was never checked.",
980 params: CAPABILITY_PARAMS,
981 cli_only: None,
982 writes: Writes::No,
983 run: verbs::capabilities,
984 },
985 Command {
986 name: "functions",
987 summary: "List the SQL functions this engine answers.",
988 detail: "From the engine's own register, which is compared against the reference \
989 library's on every build - so this is what actually exists rather than what was \
990 documented once.",
991 params: PATTERN_PARAMS,
992 cli_only: None,
993 writes: Writes::No,
994 run: verbs::functions,
995 },
996 Command {
997 name: "migrate",
998 summary: "Build an inillucent database from a SQLite file, PostgreSQL or MySQL.",
999 detail: "Reads the source and writes a new .rdb with the same rows. A path is a SQLite \
1000 database file; a postgres:// or mysql:// URL is a running server, read inside \
1001 one repeatable-read snapshot so that every table is as of one instant. The \
1002 source is never written to and the destination is never overwritten: the new \
1003 file is staged under another name and published by a rename, so a half-written \
1004 database never sits where an application would open it. A server migration is \
1005 verified per table by row count and by an order-independent digest, and nothing \
1006 that fails a check is published.",
1007 params: MIGRATE_PARAMS,
1008 cli_only: None,
1009 writes: Writes::Yes,
1010 run: verbs::migrate,
1011 },
1012 Command {
1013 name: "setup-embeddings",
1014 summary: "Download and install the embedding model and the runtime it needs.",
1015 detail: "One command, on Windows, macOS and Linux. It fetches ONNX Runtime and the \
1016 nomic-embed-text-v1.5 weights into a per-user directory, checks every byte \
1017 against a digest pinned in this build, and leaves the engine able to answer \
1018 embed(TEXT) with nothing exported by hand - so a mismatch is a refusal that \
1019 names both digests rather than a shared library that loads and misbehaves. \
1020 Name what you want: 'all' installs both halves, 'runtime' and 'model' one \
1021 each. Run with no component at all and it reports what is installed and \
1022 downloads nothing, which is what stops a 620 MB fetch being a surprise; \
1023 '--status' does the same explicitly. About 620 MB the first time and \
1024 nothing on a later run. '--residency' chooses when the model is in memory: \
1025 'resident' keeps it, which is about 1.9 GB held and 12 to 36 ms a query; \
1026 'on-demand' loads it per call, which holds nothing and costs about 0.8 s a \
1027 query; 'idle' or 'idle:90s' loads it on use and drops it after a quiet \
1028 period, which is the default and pays the load once for a burst of \
1029 questions.",
1030 params: SETUP_PARAMS,
1031 cli_only: None,
1032 writes: Writes::Yes,
1033 run: crate::setup::setup_embeddings,
1034 },
1035 Command {
1036 name: "version",
1037 summary: "Report the engine, the dialect and the driver versions.",
1038 detail: "The SQLite version named here is the dialect this engine implements, not a \
1039 library it links. There is no SQLite in this binary.",
1040 params: VERSION_PARAMS,
1041 cli_only: None,
1042 writes: Writes::No,
1043 run: verbs::version,
1044 },
1045 Command {
1046 name: "help",
1047 summary: "List every command, or explain one in full.",
1048 detail: "With no topic it prints the table. With one it prints that command's usage, \
1049 what it is for, and every parameter it takes.",
1050 params: HELP_PARAMS,
1051 cli_only: None,
1052 writes: Writes::No,
1053 run: verbs::help,
1054 },
1055 Command {
1056 name: "shell",
1057 summary: "Start the interactive shell.",
1058 detail: "The sqlite3-shaped REPL, with all 63 dot commands. Everything it can do is also \
1059 reachable non-interactively through 'run'.",
1060 params: &[],
1061 cli_only: Some(
1062 "it is a terminal REPL: it reads a keyboard and writes a screen, and neither exists \
1063 at the other end of an MCP call. Use 'run' instead, which drives the same shell.",
1064 ),
1065 writes: Writes::Yes,
1066 run: verbs::shell_placeholder,
1067 },
1068 Command {
1069 name: "mcp",
1070 summary: "Serve these commands to an agent over MCP on standard input and output.",
1071 detail: "Every command in this table that is not marked cli-only becomes a tool named \
1072 inillucent_<command>, with this same description and these same parameters.",
1073 params: &[],
1074 cli_only: Some(
1075 "it is the server that would be exposing the tools, so offering it as one of them \
1076 would let a client ask the server to serve itself.",
1077 ),
1078 writes: Writes::Yes,
1079 run: verbs::mcp_placeholder,
1080 },
1081];