quiverdb 0.9.6 → 0.9.7
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.
- package/libs/linux-x86_64/libquiver.so +0 -0
- package/libs/linux-x86_64/libquiver.so.0 +0 -0
- package/libs/linux-x86_64/libquiver_c.so +0 -0
- package/libs/macos-aarch64/libquiver.0.dylib +0 -0
- package/libs/macos-aarch64/libquiver_c.dylib +0 -0
- package/libs/windows-x86_64/libquiver.dll +0 -0
- package/libs/windows-x86_64/libquiver_c.dll +0 -0
- package/mod.ts +1 -1
- package/package.json +1 -1
- package/src/database.ts +3 -1
- package/src/index.ts +8 -7
- package/src/introspection.ts +27 -3
- package/src/loader.ts +7 -1
- package/src/lua-api.ts +493 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/mod.ts
CHANGED
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -180,7 +180,9 @@ export class Database {
|
|
|
180
180
|
declare isHealthy: () => boolean;
|
|
181
181
|
declare currentVersion: () => number;
|
|
182
182
|
declare path: () => string;
|
|
183
|
-
declare describe: () =>
|
|
183
|
+
declare describe: () => string;
|
|
184
|
+
declare describeCollection: (collection: string) => string;
|
|
185
|
+
declare summarizeCollection: (collection: string) => string;
|
|
184
186
|
|
|
185
187
|
// --- Composite helpers (implemented in composites.ts) ---
|
|
186
188
|
declare readScalarsById: (
|
package/src/index.ts
CHANGED
|
@@ -11,16 +11,10 @@ import "./composites.ts";
|
|
|
11
11
|
export type { CsvOptions } from "./csv.ts";
|
|
12
12
|
export { Database } from "./database.ts";
|
|
13
13
|
export { QuiverError } from "./errors.ts";
|
|
14
|
+
export { LUA_DB_API_REFERENCE } from "./lua-api.ts";
|
|
14
15
|
export { LuaRunner } from "./lua-runner.ts";
|
|
15
16
|
export type { GroupMetadata, ScalarMetadata } from "./metadata.ts";
|
|
16
17
|
export type { TimeSeriesData } from "./time-series.ts";
|
|
17
|
-
export {
|
|
18
|
-
LOG_LEVEL_DEBUG,
|
|
19
|
-
LOG_LEVEL_ERROR,
|
|
20
|
-
LOG_LEVEL_INFO,
|
|
21
|
-
LOG_LEVEL_OFF,
|
|
22
|
-
LOG_LEVEL_WARN,
|
|
23
|
-
} from "./types.ts";
|
|
24
18
|
export type {
|
|
25
19
|
ArrayValue,
|
|
26
20
|
DatabaseOptions,
|
|
@@ -29,3 +23,10 @@ export type {
|
|
|
29
23
|
ScalarValue,
|
|
30
24
|
Value,
|
|
31
25
|
} from "./types.ts";
|
|
26
|
+
export {
|
|
27
|
+
LOG_LEVEL_DEBUG,
|
|
28
|
+
LOG_LEVEL_ERROR,
|
|
29
|
+
LOG_LEVEL_INFO,
|
|
30
|
+
LOG_LEVEL_OFF,
|
|
31
|
+
LOG_LEVEL_WARN,
|
|
32
|
+
} from "./types.ts";
|
package/src/introspection.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Database } from "./database.ts";
|
|
2
2
|
import { check } from "./errors.ts";
|
|
3
|
-
import { allocPtrOut, decodeStringFromBuf } from "./ffi-helpers.ts";
|
|
3
|
+
import { allocPtrOut, decodeStringFromBuf, readPtrOut, toCString } from "./ffi-helpers.ts";
|
|
4
4
|
import { getSymbols } from "./loader.ts";
|
|
5
5
|
|
|
6
6
|
Database.prototype.isHealthy = function (this: Database): boolean {
|
|
@@ -24,7 +24,31 @@ Database.prototype.path = function (this: Database): string {
|
|
|
24
24
|
return decodeStringFromBuf(outPath);
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
Database.prototype.describe = function (this: Database):
|
|
27
|
+
Database.prototype.describe = function (this: Database): string {
|
|
28
28
|
const lib = getSymbols();
|
|
29
|
-
|
|
29
|
+
const out = allocPtrOut();
|
|
30
|
+
check(lib.quiver_database_describe(this._handle, out.buf));
|
|
31
|
+
const result = decodeStringFromBuf(out);
|
|
32
|
+
lib.quiver_database_free_string(readPtrOut(out));
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
Database.prototype.describeCollection = function (this: Database, collection: string): string {
|
|
37
|
+
const lib = getSymbols();
|
|
38
|
+
const collBuf = toCString(collection);
|
|
39
|
+
const out = allocPtrOut();
|
|
40
|
+
check(lib.quiver_database_describe_collection(this._handle, collBuf.buf, out.buf));
|
|
41
|
+
const result = decodeStringFromBuf(out);
|
|
42
|
+
lib.quiver_database_free_string(readPtrOut(out));
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
Database.prototype.summarizeCollection = function (this: Database, collection: string): string {
|
|
47
|
+
const lib = getSymbols();
|
|
48
|
+
const collBuf = toCString(collection);
|
|
49
|
+
const out = allocPtrOut();
|
|
50
|
+
check(lib.quiver_database_summarize_collection(this._handle, collBuf.buf, out.buf));
|
|
51
|
+
const result = decodeStringFromBuf(out);
|
|
52
|
+
lib.quiver_database_free_string(readPtrOut(out));
|
|
53
|
+
return result;
|
|
30
54
|
};
|
package/src/loader.ts
CHANGED
|
@@ -40,7 +40,7 @@ const lifecycleSymbols = {
|
|
|
40
40
|
quiver_database_is_healthy: { args: [P, P], returns: I32 },
|
|
41
41
|
quiver_database_path: { args: [P, P], returns: I32 },
|
|
42
42
|
quiver_database_current_version: { args: [P, P], returns: I32 },
|
|
43
|
-
quiver_database_describe: { args: [P], returns: I32 },
|
|
43
|
+
quiver_database_describe: { args: [P, P], returns: I32 },
|
|
44
44
|
} as const;
|
|
45
45
|
|
|
46
46
|
const elementSymbols = {
|
|
@@ -112,6 +112,11 @@ const metadataSymbols = {
|
|
|
112
112
|
quiver_database_list_time_series_groups: { args: [P, BUF, P, P], returns: I32 },
|
|
113
113
|
} as const;
|
|
114
114
|
|
|
115
|
+
const describeSymbols = {
|
|
116
|
+
quiver_database_describe_collection: { args: [P, BUF, P], returns: I32 },
|
|
117
|
+
quiver_database_summarize_collection: { args: [P, BUF, P], returns: I32 },
|
|
118
|
+
} as const;
|
|
119
|
+
|
|
115
120
|
const timeSeriesSymbols = {
|
|
116
121
|
quiver_database_read_time_series_group: { args: [P, BUF, BUF, I64, P, P, P, P, P], returns: I32 },
|
|
117
122
|
quiver_database_read_time_series_row: { args: [P, BUF, BUF, BUF, BUF, P, P, P], returns: I32 },
|
|
@@ -160,6 +165,7 @@ const allSymbols = {
|
|
|
160
165
|
...querySymbols,
|
|
161
166
|
...transactionSymbols,
|
|
162
167
|
...metadataSymbols,
|
|
168
|
+
...describeSymbols,
|
|
163
169
|
...timeSeriesSymbols,
|
|
164
170
|
...csvSymbols,
|
|
165
171
|
...freeSymbols,
|
package/src/lua-api.ts
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
// Agent-facing reference for the Lua `db` API available inside run_lua scripts.
|
|
2
|
+
//
|
|
3
|
+
// Authority: `src/lua_runner.cpp` `bind_database()` (this repo) — extracted by hand, NOT imported.
|
|
4
|
+
// The shipped quiverdb native binding is the runtime truth; this is docs. Whenever the C++ Lua
|
|
5
|
+
// binding changes, re-diff every signature below against `bind_database()` (names, arg order, arg
|
|
6
|
+
// types, return shapes).
|
|
7
|
+
//
|
|
8
|
+
// NOTE: the binary/expression subsystems (quiver.* globals + file:/expr: methods) are bound in the
|
|
9
|
+
// native binding and documented below. They are quiver.*/file:/expr: calls, not db:<name> tokens,
|
|
10
|
+
// and they read/write files on the host filesystem.
|
|
11
|
+
//
|
|
12
|
+
// FORMAT CONVENTION: every db: method should appear at least once as the literal token
|
|
13
|
+
// `db:<snake_case_name>` so coverage is greppable.
|
|
14
|
+
export const LUA_DB_API_REFERENCE = `
|
|
15
|
+
# Quiver Lua API Reference
|
|
16
|
+
|
|
17
|
+
Quiver embeds a Lua scripting layer (via \`LuaRunner\`) that exposes the same database API as the
|
|
18
|
+
other language bindings. This document is a complete reference of every method available to a Lua
|
|
19
|
+
script.
|
|
20
|
+
|
|
21
|
+
## Running scripts
|
|
22
|
+
|
|
23
|
+
The database is provided to your script as a global userdata named \`db\`. There are **no
|
|
24
|
+
open/close lifecycle methods in Lua** — the \`LuaRunner\` opens the database and hands you \`db\`
|
|
25
|
+
already connected. All methods are called with the colon syntax:
|
|
26
|
+
|
|
27
|
+
\`\`\`lua
|
|
28
|
+
db:create_element("Collection", { label = "Item 1", value = 42 })
|
|
29
|
+
local ids = db:read_element_ids("Collection")
|
|
30
|
+
\`\`\`
|
|
31
|
+
|
|
32
|
+
If a script raises an error (including any error thrown by a \`db:\` call), it surfaces to the host
|
|
33
|
+
as:
|
|
34
|
+
|
|
35
|
+
\`\`\`
|
|
36
|
+
Failed to run Lua script: <message>
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
## Value type mapping
|
|
40
|
+
|
|
41
|
+
Lua values map to Quiver column values as follows:
|
|
42
|
+
|
|
43
|
+
| Lua value | Quiver value | Notes |
|
|
44
|
+
| ------------------ | ------------ | ---------------------------------------------- |
|
|
45
|
+
| integer | INTEGER | Lua integers map to int64. |
|
|
46
|
+
| number (float) | REAL | REAL columns reject bare integers: write 75.0, not 75. |
|
|
47
|
+
| string | TEXT | Also used for \`date_time\` columns (ISO 8601). |
|
|
48
|
+
| \`nil\` | NULL | In query params, file paths, and ts rows. |
|
|
49
|
+
| table (1-indexed) | array | Used for vectors/sets and column-oriented data.|
|
|
50
|
+
|
|
51
|
+
**Unsupported types throw.** Passing a boolean, a function, or a nested table where a scalar is
|
|
52
|
+
expected raises an error rather than silently dropping the value. This applies to element
|
|
53
|
+
attributes, time-series rows, and query parameters. A skipped positional query parameter would
|
|
54
|
+
shift every later parameter and bind NULL to the trailing placeholder, so this is rejected loudly.
|
|
55
|
+
|
|
56
|
+
Dates are plain strings in ISO 8601 format: \`YYYY-MM-DDTHH:MM:SS\`. (Lua keeps a string-based
|
|
57
|
+
datetime surface — there are no DateTime wrapper helpers, unlike Julia/Dart/Python.)
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Critical rules
|
|
62
|
+
|
|
63
|
+
- **STRICT typing.** A REAL column rejects integer literals — write \`75.0\`, not \`75\`; an INTEGER
|
|
64
|
+
column rejects floats. A mismatch raises a validation error and rolls the whole script back.
|
|
65
|
+
- **Errors abort the script.** Any error thrown by a \`db:\` call stops the script and surfaces as
|
|
66
|
+
\`Failed to run Lua script: <message>\`. Validation failures roll back whatever the current
|
|
67
|
+
transaction covered.
|
|
68
|
+
- **Standard library.** Only the \`base\`, \`string\`, and \`table\` libraries are loaded — there is NO
|
|
69
|
+
\`math\` (use \`//\` for integer division), and no \`os\`, \`io\`, \`require\`, \`load\`, or \`dofile\`.
|
|
70
|
+
- **Output.** Scripts cannot return values to the tool — use \`print()\` for anything you need to
|
|
71
|
+
see (it is captured). Arrays are 1-indexed (iterate with \`ipairs\`); reading a NULL yields \`nil\`,
|
|
72
|
+
writing \`nil\` stores NULL where NULL is accepted (query params, ts rows, file columns — but NOT
|
|
73
|
+
element scalar attributes; see CRUD).
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Database info
|
|
78
|
+
|
|
79
|
+
\`\`\`lua
|
|
80
|
+
db:is_healthy() -- boolean
|
|
81
|
+
db:current_version() -- integer (current migration version)
|
|
82
|
+
db:path() -- string (database file path)
|
|
83
|
+
db:describe() -- string: whole-DB text report (returns it, does NOT print)
|
|
84
|
+
db:describe_collection(collection) -- string: one collection's structure (text report)
|
|
85
|
+
db:summarize_collection(collection)-- string: per-scalar null/non-null counts, low-cardinality
|
|
86
|
+
-- integer value distributions, per-group sizes
|
|
87
|
+
\`\`\`
|
|
88
|
+
|
|
89
|
+
All three \`describe*\`/\`summarize*\` methods **return** a string — \`print()\` it to see it.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Transactions
|
|
94
|
+
|
|
95
|
+
\`\`\`lua
|
|
96
|
+
db:begin_transaction() -- start an explicit transaction
|
|
97
|
+
db:commit() -- commit it
|
|
98
|
+
db:rollback() -- roll it back
|
|
99
|
+
db:in_transaction() -- boolean: is a transaction currently open?
|
|
100
|
+
db:transaction(fn) -- run fn(db) inside begin/commit; rollback + rethrow if fn errors
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
To make a group of writes atomic, prefer the \`db:transaction\` wrapper:
|
|
104
|
+
|
|
105
|
+
\`\`\`lua
|
|
106
|
+
db:transaction(function(db)
|
|
107
|
+
local id = db:create_element("Collection", { label = "Item 1", some_integer = 42 })
|
|
108
|
+
db:update_element("Collection", id, { some_integer = 99 })
|
|
109
|
+
end) -- both writes commit together; if either throws, both roll back
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
**Caveat:** if the host already runs your script inside a transaction, an explicit
|
|
113
|
+
\`db:begin_transaction()\` will error (\`cannot start a transaction within a transaction\`) and a
|
|
114
|
+
mid-script \`db:commit()\` would prematurely end the host's transaction. When unsure whether a
|
|
115
|
+
transaction is already open, check \`db:in_transaction()\` first, or just issue writes directly —
|
|
116
|
+
each \`db:\` write is durable on its own.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## CRUD
|
|
121
|
+
|
|
122
|
+
\`\`\`lua
|
|
123
|
+
local id = db:create_element(collection, element_table) -- returns new integer id
|
|
124
|
+
db:update_element(collection, id, element_table)
|
|
125
|
+
db:delete_element(collection, id)
|
|
126
|
+
\`\`\`
|
|
127
|
+
|
|
128
|
+
The element table holds scalar attributes as \`key = value\`, and vector/set attributes as
|
|
129
|
+
1-indexed arrays. The element type of an array is inferred from its **first** entry:
|
|
130
|
+
|
|
131
|
+
\`\`\`lua
|
|
132
|
+
local id = db:create_element("Collection", {
|
|
133
|
+
label = "Item 1", -- scalar string
|
|
134
|
+
some_integer = 42, -- scalar integer
|
|
135
|
+
some_float = 3.14, -- scalar real
|
|
136
|
+
value_int = { 1, 2, 3 }, -- vector/set of integers
|
|
137
|
+
tags = { "a", "b", "c" }, -- vector/set of strings
|
|
138
|
+
})
|
|
139
|
+
\`\`\`
|
|
140
|
+
|
|
141
|
+
\`update_element\` only touches the attributes you pass:
|
|
142
|
+
|
|
143
|
+
\`\`\`lua
|
|
144
|
+
db:update_element("Collection", id, { some_integer = 999 })
|
|
145
|
+
\`\`\`
|
|
146
|
+
|
|
147
|
+
Notes:
|
|
148
|
+
- **Empty arrays are skipped.** An attribute whose value is \`{}\` writes no vector/set (the element
|
|
149
|
+
type can't be inferred from an empty array), so it is silently dropped.
|
|
150
|
+
- **No \`nil\` scalar attributes.** Passing \`nil\` for a scalar attribute **throws** (unsupported
|
|
151
|
+
type). To leave a column unset, omit the key — you cannot set a scalar to NULL via the element
|
|
152
|
+
table. (\`nil\` → NULL is only accepted by \`add_time_series_row\` and \`update_time_series_files\`.)
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Scalar reads (bulk, across all elements)
|
|
157
|
+
|
|
158
|
+
Each returns a flat array (1-indexed table), one value per element, in id order.
|
|
159
|
+
|
|
160
|
+
\`\`\`lua
|
|
161
|
+
db:read_scalar_integers(collection, attribute) -- { 42, 37, ... }
|
|
162
|
+
db:read_scalar_floats(collection, attribute) -- { 3.14, 2.71, ... }
|
|
163
|
+
db:read_scalar_strings(collection, attribute) -- { "Item 1", "Item 2", ... }
|
|
164
|
+
\`\`\`
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Vector reads (bulk)
|
|
169
|
+
|
|
170
|
+
Each returns an array of arrays — one inner array per element.
|
|
171
|
+
|
|
172
|
+
\`\`\`lua
|
|
173
|
+
db:read_vector_integers(collection, attribute) -- { {1,2,3}, {2,3,4}, ... }
|
|
174
|
+
db:read_vector_floats(collection, attribute)
|
|
175
|
+
db:read_vector_strings(collection, attribute)
|
|
176
|
+
\`\`\`
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Set reads (bulk)
|
|
181
|
+
|
|
182
|
+
Same shape as vector reads — an array of arrays.
|
|
183
|
+
|
|
184
|
+
\`\`\`lua
|
|
185
|
+
db:read_set_integers(collection, attribute)
|
|
186
|
+
db:read_set_floats(collection, attribute)
|
|
187
|
+
db:read_set_strings(collection, attribute)
|
|
188
|
+
\`\`\`
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Composite by-id reads (Lua convenience helpers)
|
|
193
|
+
|
|
194
|
+
\`\`\`lua
|
|
195
|
+
db:read_element_ids(collection) -- { 1, 2, 3, ... }
|
|
196
|
+
|
|
197
|
+
db:read_scalars_by_id(collection, id) -- { attr = value, ... } (missing -> nil)
|
|
198
|
+
db:read_vectors_by_id(collection, id) -- { column = { v1, v2, ... }, ... }
|
|
199
|
+
db:read_sets_by_id(collection, id) -- { column = { v1, v2, ... }, ... }
|
|
200
|
+
db:read_element_by_id(collection, id) -- scalars + vectors + sets merged into one table
|
|
201
|
+
\`\`\`
|
|
202
|
+
|
|
203
|
+
\`read_element_by_id\` merges every scalar, vector, and set for the element into a single table.
|
|
204
|
+
Scalar attributes with no value come back as \`nil\`.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Time series
|
|
209
|
+
|
|
210
|
+
Time-series group data is **column-oriented** in Lua: \`{ column = { v1, v2, ... }, ... }\`. The
|
|
211
|
+
dimension (ordering) column is a \`date_*\` text column holding ISO 8601 timestamps.
|
|
212
|
+
|
|
213
|
+
### Read a whole group (column-oriented)
|
|
214
|
+
|
|
215
|
+
\`\`\`lua
|
|
216
|
+
local ts = db:read_time_series_group(collection, group, id)
|
|
217
|
+
-- ts = { date_time = { "2024-01-01T00:00:00", ... }, value = { 10.5, 20.0, ... } }
|
|
218
|
+
-- returns an empty table {} if the element has no rows
|
|
219
|
+
\`\`\`
|
|
220
|
+
|
|
221
|
+
### Read one value per element at a date (\`read_time_series_row\`)
|
|
222
|
+
|
|
223
|
+
\`\`\`lua
|
|
224
|
+
local values = db:read_time_series_row(collection, group, attribute, date_time)
|
|
225
|
+
-- { v_elem1, v_elem2, ... } in element-id order
|
|
226
|
+
\`\`\`
|
|
227
|
+
|
|
228
|
+
One value per element using **last non-null value at or before \`date_time\`** semantics. Elements
|
|
229
|
+
with no matching data yield \`nil\` in the array. \`date_time\` is an ISO 8601 string.
|
|
230
|
+
|
|
231
|
+
### Replace a whole group (column-oriented — SAME shape as the read)
|
|
232
|
+
|
|
233
|
+
\`update_time_series_group\` takes the **exact column-oriented shape \`read_time_series_group\`
|
|
234
|
+
returns**: a table mapping each column name to a 1-indexed array of its values. Read → modify →
|
|
235
|
+
write round-trips. Passing an empty table \`{}\` clears the group.
|
|
236
|
+
|
|
237
|
+
\`\`\`lua
|
|
238
|
+
db:update_time_series_group("Items", "data", id, {
|
|
239
|
+
date_time = { "2024-01-01T00:00:00", "2024-01-02T00:00:00", "2024-01-03T00:00:00" },
|
|
240
|
+
value = { 10.5, 20.0, 30.0 },
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
db:update_time_series_group("Items", "data", id, {}) -- clears the group
|
|
244
|
+
\`\`\`
|
|
245
|
+
|
|
246
|
+
A read-modify-write looks like this:
|
|
247
|
+
|
|
248
|
+
\`\`\`lua
|
|
249
|
+
local ts = db:read_time_series_group("Items", "data", id)
|
|
250
|
+
ts.value[2] = 125.0 -- edit the 2nd row's value
|
|
251
|
+
db:update_time_series_group("Items", "data", id, ts) -- write the whole group back
|
|
252
|
+
\`\`\`
|
|
253
|
+
|
|
254
|
+
**DO NOT pass an array of row tables** (\`{ { date_time = ..., value = ... }, ... }\`) — that is the
|
|
255
|
+
\`add_time_series_row\` shape, not this one. Doing so raises
|
|
256
|
+
\`stack index -1, expected string, received number\` (the integer array indices 1, 2, 3 are not
|
|
257
|
+
column names). Each value of the top-level table must be an **array**, not a scalar.
|
|
258
|
+
|
|
259
|
+
**Rules** (validation throws, rolling the script back):
|
|
260
|
+
- Every column value must be an array — a bare scalar throws \`column '...' must be an array of values\`.
|
|
261
|
+
- All columns must have the **same length** — a mismatch throws \`column '...' has length N but expected M\`.
|
|
262
|
+
- Named-but-empty columns throw (\`contain no rows; pass an empty table {} to clear\`) rather than
|
|
263
|
+
silently clearing — only a bare \`{}\` clears.
|
|
264
|
+
- Write REAL-column values as float literals (\`30.0\`, not \`30\`); STRICT validation rejects an
|
|
265
|
+
integer for a REAL column. Other Lua types throw \`column '...' has unsupported Lua type\`.
|
|
266
|
+
- \`nil\` is **not** accepted inside a column here (it throws \`unsupported Lua type\`); columns must
|
|
267
|
+
be fully populated. To write a NULL into a single row, use \`add_time_series_row\` instead, which
|
|
268
|
+
does accept \`nil\`.
|
|
269
|
+
|
|
270
|
+
### Append/upsert a single row (\`add_time_series_row\` — ROW-oriented, the one exception)
|
|
271
|
+
|
|
272
|
+
Unlike the column-oriented group update above, this takes **one row table of scalars** (dimension
|
|
273
|
+
column + value column(s)), and upserts that single row:
|
|
274
|
+
|
|
275
|
+
\`\`\`lua
|
|
276
|
+
db:add_time_series_row("Items", "data", id, {
|
|
277
|
+
date_time = "2024-01-04T00:00:00",
|
|
278
|
+
value = 40.0,
|
|
279
|
+
})
|
|
280
|
+
\`\`\`
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Time series files
|
|
285
|
+
|
|
286
|
+
For schemas that reference external time-series files (the \`{Collection}_time_series_files\`
|
|
287
|
+
singleton table):
|
|
288
|
+
|
|
289
|
+
\`\`\`lua
|
|
290
|
+
db:has_time_series_files(collection) -- boolean
|
|
291
|
+
db:list_time_series_files_columns(collection) -- { "data_file", "metadata_file", ... }
|
|
292
|
+
db:read_time_series_files(collection) -- { data_file = "path", metadata_file = nil, ... }
|
|
293
|
+
db:update_time_series_files(collection, { data_file = "path/to/data.bin", metadata_file = nil })
|
|
294
|
+
\`\`\`
|
|
295
|
+
|
|
296
|
+
In \`update_time_series_files\`, a \`nil\` value clears that column.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Metadata
|
|
301
|
+
|
|
302
|
+
### Single attribute / group
|
|
303
|
+
|
|
304
|
+
\`\`\`lua
|
|
305
|
+
db:get_scalar_metadata(collection, attribute) -- scalar metadata table (below)
|
|
306
|
+
db:get_vector_metadata(collection, group_name) -- group metadata table (below)
|
|
307
|
+
db:get_set_metadata(collection, group_name) -- group metadata table
|
|
308
|
+
db:get_time_series_metadata(collection, group_name) -- group metadata table (+ dimension_column)
|
|
309
|
+
\`\`\`
|
|
310
|
+
|
|
311
|
+
### Lists (one entry per attribute/group)
|
|
312
|
+
|
|
313
|
+
\`\`\`lua
|
|
314
|
+
db:list_scalar_attributes(collection) -- array of scalar metadata tables
|
|
315
|
+
db:list_vector_groups(collection) -- array of group metadata tables
|
|
316
|
+
db:list_set_groups(collection) -- array of group metadata tables
|
|
317
|
+
db:list_time_series_groups(collection) -- array of group metadata tables (+ dimension_column)
|
|
318
|
+
\`\`\`
|
|
319
|
+
|
|
320
|
+
### Scalar metadata table shape
|
|
321
|
+
|
|
322
|
+
\`\`\`lua
|
|
323
|
+
{
|
|
324
|
+
name = "value",
|
|
325
|
+
data_type = "integer", -- "integer" | "real" | "text" | "date_time"
|
|
326
|
+
not_null = true,
|
|
327
|
+
primary_key = false,
|
|
328
|
+
default_value = nil, -- string, or nil
|
|
329
|
+
is_foreign_key = false,
|
|
330
|
+
references_collection = nil, -- string, or nil
|
|
331
|
+
references_column = nil, -- string, or nil
|
|
332
|
+
}
|
|
333
|
+
\`\`\`
|
|
334
|
+
|
|
335
|
+
### Group metadata table shape
|
|
336
|
+
|
|
337
|
+
\`\`\`lua
|
|
338
|
+
{
|
|
339
|
+
group_name = "data",
|
|
340
|
+
value_columns = { <scalar metadata table>, ... },
|
|
341
|
+
dimension_column = "date_time", -- present ONLY for time-series groups
|
|
342
|
+
}
|
|
343
|
+
\`\`\`
|
|
344
|
+
|
|
345
|
+
The \`dimension_column\` key is present only in **time-series** group metadata
|
|
346
|
+
(\`get_time_series_metadata\` / \`list_time_series_groups\`). Vector and set group metadata omit it
|
|
347
|
+
entirely.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Query (parameterized SQL)
|
|
352
|
+
|
|
353
|
+
Positional \`?\` placeholders; \`params\` is an optional 1-indexed array. Each returns the first
|
|
354
|
+
column of the first row as the requested type, or \`nil\` if there is no result.
|
|
355
|
+
|
|
356
|
+
\`\`\`lua
|
|
357
|
+
db:query_string(sql, params) -- string or nil
|
|
358
|
+
db:query_integer(sql, params) -- integer or nil
|
|
359
|
+
db:query_float(sql, params) -- number or nil
|
|
360
|
+
\`\`\`
|
|
361
|
+
|
|
362
|
+
Example:
|
|
363
|
+
|
|
364
|
+
\`\`\`lua
|
|
365
|
+
local label = db:query_string(
|
|
366
|
+
"SELECT label FROM Collection WHERE some_integer = ?",
|
|
367
|
+
{ 42 }
|
|
368
|
+
)
|
|
369
|
+
local count = db:query_integer("SELECT COUNT(*) FROM Collection")
|
|
370
|
+
\`\`\`
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## CSV import / export
|
|
375
|
+
|
|
376
|
+
Export a time-series group to a CSV file, or import one from a CSV file. \`path\` is resolved by the
|
|
377
|
+
host filesystem; \`options\` is optional.
|
|
378
|
+
|
|
379
|
+
\`\`\`lua
|
|
380
|
+
db:export_csv(collection, group, path, options)
|
|
381
|
+
db:import_csv(collection, group, path, options)
|
|
382
|
+
\`\`\`
|
|
383
|
+
|
|
384
|
+
The optional \`options\` table has two keys:
|
|
385
|
+
|
|
386
|
+
\`\`\`lua
|
|
387
|
+
{
|
|
388
|
+
date_time_format = "%Y-%m-%d", -- strftime-style format for the dimension column
|
|
389
|
+
|
|
390
|
+
-- enum_labels: write/read integer codes as human labels. Three nested levels:
|
|
391
|
+
-- attribute name -> locale -> { label = integer_id }
|
|
392
|
+
enum_labels = {
|
|
393
|
+
status = {
|
|
394
|
+
en = { active = 1, inactive = 0 },
|
|
395
|
+
pt = { ativo = 1, inativo = 0 },
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
}
|
|
399
|
+
\`\`\`
|
|
400
|
+
|
|
401
|
+
**Precondition:** \`db:import_csv\` cannot run inside an open transaction (it toggles
|
|
402
|
+
\`PRAGMA foreign_keys\`, a no-op mid-transaction) — it throws \`Cannot import_csv: transaction already
|
|
403
|
+
active\`. Call it outside any \`db:transaction\` / \`db:begin_transaction\` block.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Complete example
|
|
408
|
+
|
|
409
|
+
\`\`\`lua
|
|
410
|
+
-- create
|
|
411
|
+
db:create_element("Configuration", { label = "Configuration" })
|
|
412
|
+
|
|
413
|
+
local item1 = db:create_element("Collection",
|
|
414
|
+
{ label = "Item 1", some_integer = 42, some_float = 3.14, value_int = { 1, 2, 3 } })
|
|
415
|
+
local item2 = db:create_element("Collection",
|
|
416
|
+
{ label = "Item 2", some_integer = 37, some_float = 2.71, value_int = { 2, 3, 4 } })
|
|
417
|
+
|
|
418
|
+
-- read every element
|
|
419
|
+
local ids = db:read_element_ids("Collection")
|
|
420
|
+
for _, id in ipairs(ids) do
|
|
421
|
+
local element = db:read_element_by_id("Collection", id)
|
|
422
|
+
print("Element " .. id .. ", label = " .. tostring(element.label))
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
-- update
|
|
426
|
+
db:update_element("Collection", item1, { some_integer = 999 })
|
|
427
|
+
|
|
428
|
+
-- delete
|
|
429
|
+
db:delete_element("Collection", item2)
|
|
430
|
+
\`\`\`
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Binary & expression subsystems
|
|
435
|
+
|
|
436
|
+
Dense N-dimensional \`float64\` arrays (\`.qvr\` + \`.toml\` sidecar) plus lazy arithmetic over them,
|
|
437
|
+
under a global \`quiver\` table (not \`db\`). Mirrors the Julia surface; aggregation ops are strings
|
|
438
|
+
(Lua has no enums); operators are \`+ - * /\` and unary \`-\`, with scalars allowed on either side.
|
|
439
|
+
These read/write files on the host filesystem (\`path\` is host-resolved).
|
|
440
|
+
|
|
441
|
+
\`\`\`lua
|
|
442
|
+
local md = quiver.metadata{
|
|
443
|
+
initial_datetime = "2025-01-01T00:00:00", unit = "MW",
|
|
444
|
+
labels = {"v1", "v2"}, dimensions = {"stage", "block"}, dimension_sizes = {4, 31},
|
|
445
|
+
time_dimensions = {"stage", "block"}, frequencies = {"monthly", "daily"},
|
|
446
|
+
}
|
|
447
|
+
local f = quiver.open_file(path, "w", md) -- mode "r"/"w"; md required for "w"
|
|
448
|
+
f:write({1.0, 2.0}, {stage = 1, block = 1}) -- data table, dims table
|
|
449
|
+
f:close()
|
|
450
|
+
local r = quiver.open_file(path, "r")
|
|
451
|
+
local cell = r:read({stage = 1, block = 1}) -- { v1, v2 }; pass true as 2nd arg to allow NaN
|
|
452
|
+
r:get_metadata(); r:get_file_path(); r:is_open()
|
|
453
|
+
md:get_unit(); md:get_version(); md:get_initial_datetime()
|
|
454
|
+
md:get_labels(); md:get_dimensions(); md:get_number_of_time_dimensions(); md:to_toml()
|
|
455
|
+
quiver.metadata_from_toml(text); quiver.metadata_from_element(tbl)
|
|
456
|
+
quiver.bin_to_csv(path) -- aggregate=true by default; pass false to keep time dims as columns
|
|
457
|
+
quiver.csv_to_bin(path)
|
|
458
|
+
|
|
459
|
+
local e = (quiver.expression(r) + 10.0) * 2.0 -- files auto-wrap; scalars either side
|
|
460
|
+
e = quiver.abs(e); e = quiver.sqrt(e) -- also quiver.log / quiver.exp
|
|
461
|
+
e = quiver.ifelse(cond_e, then_e, else_e)
|
|
462
|
+
e = e:aggregate("stage", "sum") -- sum/mean/min/max/percentile
|
|
463
|
+
e = e:aggregate("stage", "percentile", 0.9) -- percentile needs the fraction
|
|
464
|
+
e = e:aggregate_agents("mean") -- collapse the label axis
|
|
465
|
+
e = e:select_agents({"v2"}); e = e:rename_agents({v1 = "alpha"})
|
|
466
|
+
e:save(out_path); e:metadata()
|
|
467
|
+
\`\`\`
|
|
468
|
+
|
|
469
|
+
**\`quiver.metadata{...}\` kwargs and defaults:** \`version\` defaults to \`"1"\`; \`initial_datetime\`
|
|
470
|
+
and \`unit\` default to \`""\`; \`labels\`, \`dimensions\`, \`dimension_sizes\`, \`time_dimensions\`, and
|
|
471
|
+
\`frequencies\` default to empty arrays.
|
|
472
|
+
|
|
473
|
+
**\`get_dimensions()\` / \`get_metadata()\` dimension shape** — returns an array of dimension tables:
|
|
474
|
+
|
|
475
|
+
\`\`\`lua
|
|
476
|
+
{
|
|
477
|
+
name = "stage",
|
|
478
|
+
size = 4,
|
|
479
|
+
is_time_dimension = true,
|
|
480
|
+
frequency = "monthly", -- nil for non-time dimensions
|
|
481
|
+
initial_value = 1, -- nil for non-time dimensions
|
|
482
|
+
parent_dimension_index = -1, -- nil for non-time dimensions
|
|
483
|
+
}
|
|
484
|
+
\`\`\`
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## What Lua does *not* expose
|
|
489
|
+
|
|
490
|
+
DateTime wrapper helpers (Lua uses ISO 8601 strings) and \`_by_id\` single-scalar variants (use the
|
|
491
|
+
composite by-id readers or the bulk readers instead). Everything else the native binding exposes —
|
|
492
|
+
CRUD, reads, time series, metadata, query, CSV, and the binary/expression subsystems — is
|
|
493
|
+
documented above and callable.`;
|