quiverdb 0.12.6 → 0.12.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quiverdb",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/lua-api.ts CHANGED
@@ -52,7 +52,7 @@ Lua values map to Quiver column values as follows:
52
52
  | number (float) | REAL | A float is rejected for an INTEGER column. |
53
53
  | string | TEXT | Also used for \`date_time\` columns (ISO 8601). |
54
54
  | boolean | INTEGER 1/0 | SQLite has no boolean type; \`true\` writes 1, \`false\` 0. |
55
- | \`nil\` | NULL | In query params, file paths, ts rows, relations.|
55
+ | \`nil\` | NULL | In query params (not trailing), ts rows, group cells, relations. |
56
56
  | table (1-indexed) | array | Used for vectors/sets and column-oriented data.|
57
57
 
58
58
  A boolean is accepted **wherever an integer is** — element scalars and arrays, query parameters,
@@ -86,10 +86,18 @@ midnight.
86
86
  - **Type coercion.** An integer is accepted for a REAL column (coerced to real on insert); a float
87
87
  is rejected for an INTEGER column. A string bound to a \`date_*\` column must parse as ISO 8601
88
88
  (\`YYYY-MM-DD\`, optionally \`THH:MM:SS\` or \` HH:MM:SS\`). Other type mismatches raise a
89
- validation error and roll the whole script back.
90
- - **Errors abort the script.** Any error thrown by a \`db:\` call stops the script and surfaces as
91
- \`Failed to run Lua script: <message>\`. Validation failures roll back whatever the current
92
- transaction covered.
89
+ validation error.
90
+ - **An uncaught error aborts the script, and the script is not rolled back.** Any error thrown by
91
+ a \`db:\` call that you do not catch with \`pcall\` stops the script and surfaces as
92
+ \`Failed to run Lua script: <message>\`. Outside a transaction each \`db:\` write commits on its
93
+ own, so the failing call changes nothing in the database, but every write that finished before
94
+ it **stays**. Only \`db:transaction(fn)\` and \`db:dry_run(fn)\` undo their block's database
95
+ writes when \`fn\` errors; nothing undoes a file a \`db:\` call wrote. A
96
+ \`db:begin_transaction()\` (or \`db:begin_dry_run()\`) you opened and never closed stays open
97
+ after the error, and closing it is up to the host. A host that runs your script inside its own
98
+ transaction or dry run may undo the whole run; check \`db:in_transaction()\` if it matters. To
99
+ make several writes all-or-nothing, put them in one \`db:transaction(function(db) ... end)\`
100
+ and do not \`pcall\` inside it: a caught error lets the block commit whatever ran.
93
101
  - **Standard library.** Loaded standard libraries: base, string, table, math, coroutine, utf8.
94
102
  That is the pure-computation set — there is no \`os\`, \`io\`, \`debug\`, or \`package\`/\`require\`,
95
103
  and \`dofile\`/\`loadfile\` are removed (string-form \`load\` stays available). Integer division is
@@ -106,9 +114,12 @@ midnight.
106
114
  \`Cannot <op>: database is in-memory, file operations are unavailable\`.
107
115
  - **Output.** A script can \`return\` one value and the host receives it as JSON — prefer this over
108
116
  \`print()\` when you need structured data back (\`print()\` still works and is captured). Only the
109
- **first** returned value is encoded. Arrays are 1-indexed (iterate with \`ipairs\`); reading a NULL
110
- yields \`nil\`, writing \`nil\` stores NULL where NULL is accepted (query params, ts rows, file
111
- columns, relation targets — but NOT element scalar attributes; see CRUD).
117
+ **first** returned value is encoded. Arrays are 1-indexed. Iterate with \`ipairs\` only where no
118
+ NULL can appear: a read of a nullable column (a bulk scalar read, a vector/set inner list, a
119
+ time-series value column, \`db:read_time_series_row\`) has \`nil\` holes, and \`ipairs\` stops at
120
+ the first one (see Scalar reads). Reading a NULL yields \`nil\`; writing \`nil\` stores NULL where
121
+ NULL is accepted (query params except a trailing one, ts rows, group cells, relation targets —
122
+ but NOT element scalar attributes; see CRUD).
112
123
 
113
124
  \`\`\`lua
114
125
  return { ids = db:read_element_ids("Collection"), total = 3 }
@@ -126,10 +137,10 @@ midnight.
126
137
  | any other table | object, keys sorted; integer keys stringify |
127
138
 
128
139
  **A table with holes is an object, not an array.** A bulk read of a nullable column returns
129
- \`nil\` holes (see Reading), so \`return db:read_scalar_integers(c, a)\` encodes as
140
+ \`nil\` holes (see Scalar reads), so \`return db:read_scalar_integers(c, a)\` encodes as
130
141
  \`{"1":10,"3":30}\` — not \`[10,null,30]\` — and the keys sort as text (\`"1","11","2"\`). When the
131
142
  host needs positional scalar data, return the ids alongside and fill the holes yourself:
132
- \`local v = db:read_scalar_integers(c, a); local out = {}; for i in ipairs(ids) do out[i] = v[i] or false end\`.
143
+ \`local ids = db:read_element_ids(c); local v = db:read_scalar_integers(c, a); local out = {}; for i = 1, #ids do out[i] = v[i] or false end\`.
133
144
  **Inside** a vector/set read an inner list with an interior NULL cell encodes as an object nested
134
145
  in the outer array (\`[{"1":10,"3":30},[]]\`), but a trailing NULL cell leaves no trace at all:
135
146
  \`[10, NULL]\` encodes as the plain \`[10]\`. The ids count elements, not rows, so they cannot
@@ -142,8 +153,10 @@ midnight.
142
153
  self-referencing table); a result over 64 MB raises
143
154
  \`Cannot run: script return value exceeds ... bytes of JSON\`; a string holding bytes that are not
144
155
  valid UTF-8 raises \`Cannot run: script return value contains a string that is not valid UTF-8\`;
145
- and a table where an integer key and a string key spell the same thing (\`{[1] = 'a', ['1'] = 'b'}\`)
146
- raises \`Cannot run: script returned a table with duplicate key '1'\`.
156
+ a table where an integer key and a string key spell the same thing (\`{[1] = 'a', ['1'] = 'b'}\`)
157
+ raises \`Cannot run: script returned a table with duplicate key '1'\`; and a table with a key that
158
+ is neither an integer nor a string (a boolean, a float such as \`1.5\`, a table) raises
159
+ \`Cannot run: script returned a table with an unsupported key type\`.
147
160
  - **Embedding harness may restrict further.** The library itself allows transactions and the
148
161
  (sandboxed) CSV/file operations below. A host that runs your script (e.g. a hosted \`run_lua\`
149
162
  tool) may disable some of them and report \`disabled in the run_lua sandbox\` — that limit comes
@@ -193,10 +206,12 @@ end) -- both writes commit together; if either throws, both roll back
193
206
  \`\`\`
194
207
 
195
208
  **Caveat:** if the host already runs your script inside a plain transaction (not a dry run), an
196
- explicit \`db:begin_transaction()\` will error (\`cannot start a transaction within a transaction\`)
197
- and a mid-script \`db:commit()\` would prematurely end the host's transaction. When unsure whether
198
- a transaction is already open, check \`db:in_transaction()\` first, or just issue writes directly —
199
- each \`db:\` write is durable on its own.
209
+ explicit \`db:begin_transaction()\` will error
210
+ (\`Cannot begin_transaction: transaction already active\`) and a mid-script \`db:commit()\` would
211
+ prematurely end the host's transaction. \`db:transaction(fn)\` begins one too, so it fails the same
212
+ way. When unsure whether a transaction is already open, check \`db:in_transaction()\` first, or just issue writes directly:
213
+ outside a transaction each \`db:\` write commits on its own, while inside the host's transaction it
214
+ commits or rolls back with the host's.
200
215
 
201
216
  ---
202
217
 
@@ -235,6 +250,11 @@ Rules worth knowing:
235
250
  \`Cannot begin_dry_run: dry run already active\` — check \`db:in_dry_run()\` first and skip the
236
251
  wrapper when one is already active. Do **not** call \`db:end_dry_run()\` to get around it: that
237
252
  ends the host's dry run, and everything you write afterwards is committed for real.
253
+ - **Nor inside a plain transaction.** If the host (or a \`db:transaction\` block) already holds a
254
+ plain transaction, \`db:begin_dry_run()\` and \`db:dry_run(fn)\` throw
255
+ \`Cannot begin_dry_run: transaction already active\`. \`db:in_transaction()\` is true under a host
256
+ dry run *and* under a host transaction, so it is the one check that covers both;
257
+ \`db:in_dry_run()\` covers only the first.
238
258
  - For a rough sense of how much a run touched, \`db:query_integer("SELECT total_changes()")\` gives
239
259
  the number of rows inserted, updated or deleted on this connection.
240
260
 
@@ -293,8 +313,10 @@ Notes:
293
313
  \`{ x = nil }\` is identical to \`{}\`; an update/create table that ends up with no attributes
294
314
  **throws** (\`...must have at least one scalar attribute\` on create, \`...at least one attribute
295
315
  to update\` on update). To leave a column unchanged, omit the key — you cannot set a scalar to
296
- NULL via the element table. (\`nil\` → NULL is only accepted by
297
- \`upsert_time_series_row\`, \`update_time_series_files\` and \`update_relation\`.)
316
+ NULL via the element table. (\`nil\` → NULL is accepted by \`update_relation\` and in query params
317
+ (not a trailing one), and as a cell in the group writers; \`upsert_time_series_row\` and
318
+ \`update_time_series_files\` replace the whole row, so a column you leave out (or set to \`nil\`,
319
+ the same thing) is cleared — see Time series and Time series files.)
298
320
  - **\`update_relation\` points one scalar foreign-key relation at another element**, named by the
299
321
  target's label. The column is derived from the naming convention —
300
322
  \`lowercase(collection_to) .. "_" .. relation_type\`, so
@@ -311,7 +333,10 @@ Notes:
311
333
 
312
334
  ## Scalar reads (bulk, across all elements)
313
335
 
314
- Each returns a flat array (1-indexed table), one value per element, in id order.
336
+ Each returns a flat array (1-indexed table), one value per element, in id order. A NULL is a
337
+ \`nil\` hole at that position, so \`ipairs\` stops at the first one and \`#\` is unreliable. Loop
338
+ \`for i = 1, #ids\` over \`local ids = db:read_element_ids(collection)\` and index the result with
339
+ \`i\`.
315
340
 
316
341
  \`\`\`lua
317
342
  db:read_scalar_integers(collection, attribute) -- { 42, 37, ... }
@@ -432,9 +457,10 @@ local values = db:read_time_series_row(collection, group, attribute, date_time)
432
457
  \`\`\`
433
458
 
434
459
  One value per element using **last non-null value at or before \`date_time\`** semantics. Elements
435
- with no matching data yield \`nil\` in the array. \`date_time\` is an ISO 8601 string. A group with
436
- more than one dimension column (e.g. \`date_time\` + \`block\`) throws — read it with
437
- \`read_time_series_group\`.
460
+ with no matching data yield \`nil\` in the array, so the result can hold \`nil\` holes: loop over
461
+ \`db:read_element_ids(collection)\` rather than using \`ipairs\`. \`date_time\` is an ISO 8601
462
+ string. A group with more than one dimension column (e.g. \`date_time\` + \`block\`) throws — read
463
+ it with \`read_time_series_group\`.
438
464
 
439
465
  ### Replace a whole group (column-oriented — SAME shape as the read)
440
466
 
@@ -463,10 +489,10 @@ db:update_time_series_group("Items", "data", id, ts) -- write the whole group
463
489
 
464
490
  **DO NOT pass an array of row tables** (\`{ { date_time = ..., value = ... }, ... }\`) — that is the
465
491
  \`upsert_time_series_row\` shape, not this one. Doing so raises
466
- \`stack index -1, expected string, received number\` (the integer array indices 1, 2, 3 are not
467
- column names). Each value of the top-level table must be an **array**, not a scalar.
492
+ \`Cannot update_time_series_group: column names must be strings; pass { column = { values... } }, not an array of row tables\`.
493
+ Each value of the top-level table must be an **array**, not a scalar.
468
494
 
469
- **Rules** (validation throws, rolling the script back):
495
+ **Rules** (a violation throws; see "Errors abort the script" for what is kept):
470
496
  - Every column value must be an array — a bare scalar throws \`column '...' must be an array of values\`.
471
497
  - The **dimension column(s) set the row count** and must be present and fully populated: the
472
498
  \`date_*\` ordering column, plus any extra primary-key columns in a multi-dimensional group (e.g.
@@ -478,8 +504,9 @@ column names). Each value of the top-level table must be an **array**, not a sca
478
504
  column **longer** than the dimension column throws \`column '...' has length N but expected M\`.
479
505
  - Named columns whose dimension transposes to zero rows throw (\`contain no rows; pass an empty
480
506
  table {} to clear the group\`) — only a bare \`{}\` clears.
481
- - Integer values are accepted for REAL columns (converted on insert). Booleans, functions, and
482
- other unsupported Lua types throw \`column '...' has unsupported Lua type\`.
507
+ - Integer values are accepted for REAL columns (converted on insert), and a boolean is written as
508
+ 1/0. A function, a table or another unsupported Lua type throws
509
+ \`column '...' has unsupported Lua type\`.
483
510
  - The element id must exist; the \`_by_label\` form takes a label in its place, with
484
511
  \`update_element_by_label\`'s resolution and miss semantics. Every rule above applies to both.
485
512
 
@@ -503,6 +530,10 @@ db:upsert_time_series_row_by_label("Items", "data", "Item 1", {
503
530
  The element id must exist; the \`_by_label\` form takes a label in its place, with
504
531
  \`update_element_by_label\`'s resolution and miss semantics.
505
532
 
533
+ An existing row with the same dimension key is **replaced whole**: every value column you leave
534
+ out (or set to \`nil\`) is reset to NULL (or its DEFAULT). To change one value, pass every value
535
+ column of the row.
536
+
506
537
  ---
507
538
 
508
539
  ## Time series files
@@ -514,10 +545,15 @@ singleton table):
514
545
  db:has_time_series_files(collection) -- boolean
515
546
  db:list_time_series_files_columns(collection) -- { "data_file", "metadata_file", ... }
516
547
  db:read_time_series_files(collection) -- { data_file = "path", metadata_file = nil, ... }
517
- db:update_time_series_files(collection, { data_file = "path/to/data.bin", metadata_file = nil })
548
+ db:update_time_series_files(collection, { data_file = "path/to/data.bin" }) -- metadata_file is cleared
518
549
  \`\`\`
519
550
 
520
- In \`update_time_series_files\`, a \`nil\` value clears that column.
551
+ \`update_time_series_files\` **replaces the whole row**: every column you do not give a string is
552
+ set to NULL (or to its DEFAULT, if the schema declares one), and in Lua a \`nil\` value and a
553
+ missing key are the same thing. A table with no string values (\`{}\`, or only \`nil\` values)
554
+ changes nothing and clears nothing. A value that is not a string throws. To change one path, read
555
+ the row with \`db:read_time_series_files(collection)\`, change that one entry, and pass the whole
556
+ table back.
521
557
 
522
558
  ---
523
559
 
@@ -575,9 +611,15 @@ entirely.
575
611
  ## Query (parameterized SQL)
576
612
 
577
613
  Positional \`?\` placeholders; \`params\` is an optional 1-indexed array. Each returns the first
578
- column of the first row as the requested type, or \`nil\` if there is no result. The number of
614
+ column of the first row, or \`nil\` when there is no row, the value is NULL, or it is not already
615
+ the requested type. Only \`query_float\` converts (it widens an INTEGER), so
616
+ \`db:query_string("SELECT COUNT(*) ...")\` and \`db:query_integer("SELECT AVG(x) ...")\` return
617
+ \`nil\`; \`CAST\` in the SQL when unsure (\`SELECT CAST(AVG(x) AS INTEGER)\`). The number of
579
618
  \`params\` must match the number of \`?\` placeholders exactly — a mismatch (too few or too many)
580
- throws rather than binding NULL or ignoring extras.
619
+ throws rather than binding NULL or ignoring extras. A \`nil\` param binds NULL only in a table
620
+ constructor whose last entry is not \`nil\` (\`{ nil, 5 }\`): Lua stores no key for a \`nil\`, so
621
+ \`{ 5, nil }\` or \`{ nil }\` comes up short and throws that mismatch. To test for NULL, write
622
+ \`IS NULL\` in the SQL.
581
623
 
582
624
  \`\`\`lua
583
625
  db:query_string(sql, params) -- string or nil
@@ -599,9 +641,22 @@ local count = db:query_integer("SELECT COUNT(*) FROM Collection")
599
641
 
600
642
  ## CSV import / export
601
643
 
602
- Export a time-series group to a CSV file, or import one from a CSV file. \`path\` is sandboxed:
603
- relative paths resolve against the database file's directory and must stay inside it (see
604
- Critical rules); \`options\` is optional.
644
+ Export one table of \`collection\` to a CSV file, or import one from a CSV file. \`group\` names a
645
+ vector, set or time-series group of \`collection\`; pass \`""\` for the collection's own scalar table
646
+ (the CSV then holds exactly the table's columns except \`id\`, \`label\` included). \`path\` is
647
+ sandboxed: relative paths resolve against the database file's directory and must stay inside it
648
+ (see Critical rules); \`options\` is optional.
649
+
650
+ **\`db:import_csv\` replaces, it does not merge.** A group import deletes every existing row of
651
+ that group, for every element, not only the elements named in the file, and then inserts the
652
+ file's rows. A scalar import (\`group = ""\`) matches elements by exact label. An element whose
653
+ label is in the file keeps its id and has every column overwritten from the file (a blank cell
654
+ writes NULL). **Every element whose label is not in the file is deleted** as
655
+ \`db:delete_element\` would delete it: its group rows go with it, and every relation pointing at
656
+ it from another table follows its \`ON DELETE\` action (\`SET NULL\` clears it, \`CASCADE\`
657
+ deletes the row that holds it, a group row or an element of another collection). So editing a
658
+ label in the file deletes that element and creates a new one. A header-only CSV empties the
659
+ table. To add or change a few elements, use \`db:create_element\` / \`db:update_element\` instead.
605
660
 
606
661
  \`\`\`lua
607
662
  db:export_csv(collection, group, path, options)
@@ -612,7 +667,7 @@ The optional \`options\` table has two keys:
612
667
 
613
668
  \`\`\`lua
614
669
  {
615
- date_time_format = "%Y-%m-%d", -- strftime-style format for the dimension column
670
+ date_time_format = "%Y-%m-%d", -- strftime-style format for every date-time column
616
671
 
617
672
  -- enum_labels: write/read integer codes as human labels. Three nested levels:
618
673
  -- attribute name -> locale -> { label = integer_id }
@@ -625,6 +680,9 @@ The optional \`options\` table has two keys:
625
680
  }
626
681
  \`\`\`
627
682
 
683
+ An options value that is not a table, an unknown key or a wrong-typed value throws
684
+ (\`Cannot export_csv: unknown option '...'\`).
685
+
628
686
  **Precondition:** \`db:import_csv\` cannot run inside an open transaction (it opens and commits its
629
687
  own) — it throws \`Cannot import_csv: transaction already active\`. Call it outside any
630
688
  \`db:transaction\` / \`db:begin_transaction\` block.
@@ -812,7 +870,9 @@ File I/O is db-scoped (\`db:open_file\` / \`db:bin_to_csv\` / \`db:csv_to_bin\`)
812
870
  base paths, sandboxed to the database directory (see Critical rules), and \`get_file_path()\` returns
813
871
  the resolved absolute path. The pure-metadata builders and expression constructors live under the
814
872
  global \`quiver\` table. Mirrors the Julia surface; aggregation ops are strings (Lua has no enums);
815
- operators are \`+ - * /\` and unary \`-\`, with scalars allowed on either side.
873
+ operators are \`+ - * /\` and unary \`-\`, with scalars allowed on either side. A file handle does not
874
+ outlive the \`run()\` that opened it: any handle still open when the script returns is closed (and a
875
+ writer flushed), so reopen the file in each script.
816
876
 
817
877
  \`\`\`lua
818
878
  local md = quiver.metadata{
@@ -847,7 +907,8 @@ e:save(out_path); e:metadata() -- save path is sandboxed l
847
907
 
848
908
  **\`quiver.metadata{...}\` kwargs and defaults:** \`version\` defaults to \`"1"\`; \`initial_datetime\`
849
909
  and \`unit\` default to \`""\`; \`labels\`, \`dimensions\`, \`dimension_sizes\`, \`time_dimensions\`, and
850
- \`frequencies\` default to empty arrays.
910
+ \`frequencies\` default to empty arrays. Only these eight keys are accepted; an unknown key or a
911
+ value of the wrong type throws.
851
912
 
852
913
  **\`get_dimensions()\` / \`get_metadata()\` dimension shape** — returns an array of dimension tables:
853
914