quiverdb 0.9.15 → 0.9.16
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/README.md +6 -1
- 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/package.json +1 -1
- package/src/database.ts +3 -0
- package/src/loader.ts +5 -1
- package/src/lua-api.ts +98 -19
- package/src/lua-runner.ts +13 -3
- package/src/transaction.ts +24 -0
package/README.md
CHANGED
|
@@ -130,6 +130,10 @@ Parameters are passed as an array of `number | string | null`.
|
|
|
130
130
|
- `commit()` -- Commit current transaction
|
|
131
131
|
- `rollback()` -- Rollback current transaction
|
|
132
132
|
- `inTransaction()` -- Check if transaction is active
|
|
133
|
+
- `beginDryRun()` -- Begin a dry run: a transaction `endDryRun()` always rolls back. While it is
|
|
134
|
+
active, `beginTransaction`/`commit`/`rollback` are absorbed (no-ops)
|
|
135
|
+
- `endDryRun()` -- End the active dry run, rolling back everything it covered
|
|
136
|
+
- `inDryRun()` -- Check if a dry run is active
|
|
133
137
|
|
|
134
138
|
### CSV
|
|
135
139
|
|
|
@@ -152,7 +156,8 @@ Parameters are passed as an array of `number | string | null`.
|
|
|
152
156
|
### Lua
|
|
153
157
|
|
|
154
158
|
- `LuaRunner(db)` -- Create Lua script runner with database access
|
|
155
|
-
- `run(script)` -- Execute a Lua script
|
|
159
|
+
- `run(script)` -- Execute a Lua script; returns its return value as a JSON string, or `""` if it
|
|
160
|
+
returned nothing
|
|
156
161
|
- `close()` -- Close the Lua runner
|
|
157
162
|
|
|
158
163
|
## Types
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -126,6 +126,9 @@ export class Database {
|
|
|
126
126
|
declare commit: () => void;
|
|
127
127
|
declare rollback: () => void;
|
|
128
128
|
declare inTransaction: () => boolean;
|
|
129
|
+
declare beginDryRun: () => void;
|
|
130
|
+
declare endDryRun: () => void;
|
|
131
|
+
declare inDryRun: () => boolean;
|
|
129
132
|
|
|
130
133
|
// --- Metadata (implemented in metadata.ts) ---
|
|
131
134
|
declare getScalarMetadata: (collection: string, attribute: string) => ScalarMetadata;
|
package/src/loader.ts
CHANGED
|
@@ -97,6 +97,9 @@ const transactionSymbols = {
|
|
|
97
97
|
quiver_database_commit: { args: [P], returns: I32 },
|
|
98
98
|
quiver_database_rollback: { args: [P], returns: I32 },
|
|
99
99
|
quiver_database_in_transaction: { args: [P, P], returns: I32 },
|
|
100
|
+
quiver_database_begin_dry_run: { args: [P], returns: I32 },
|
|
101
|
+
quiver_database_end_dry_run: { args: [P], returns: I32 },
|
|
102
|
+
quiver_database_in_dry_run: { args: [P, P], returns: I32 },
|
|
100
103
|
} as const;
|
|
101
104
|
|
|
102
105
|
const metadataSymbols = {
|
|
@@ -160,7 +163,8 @@ const freeSymbols = {
|
|
|
160
163
|
const luaSymbols = {
|
|
161
164
|
quiver_lua_runner_new: { args: [P, P], returns: I32 },
|
|
162
165
|
quiver_lua_runner_free: { args: [P], returns: I32 },
|
|
163
|
-
quiver_lua_runner_run: { args: [P, BUF], returns: I32 },
|
|
166
|
+
quiver_lua_runner_run: { args: [P, BUF, BUF], returns: I32 },
|
|
167
|
+
quiver_lua_runner_free_string: { args: [P], returns: I32 },
|
|
164
168
|
} as const;
|
|
165
169
|
|
|
166
170
|
// Combined symbol map for dlopen.
|
package/src/lua-api.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
// Agent-facing reference for the Lua `db` API available inside run_lua scripts.
|
|
2
2
|
//
|
|
3
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.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// The shipped quiverdb native binding is the runtime truth; this is docs.
|
|
5
|
+
//
|
|
6
|
+
// SYNC: `test/lua-api-sync.test.ts` derives the bound surface from `src/lua_runner.cpp` and checks
|
|
7
|
+
// it AUTOMATICALLY — every `db:`/`quiver.*` name is documented, no documented name has been
|
|
8
|
+
// removed, and the stdlib sentence matches `open_libraries` exactly. What it CANNOT check, and you
|
|
9
|
+
// must still re-diff by hand when the binding changes: arg order, arity, arg types, return shapes,
|
|
10
|
+
// and whether the prose is semantically true.
|
|
7
11
|
//
|
|
8
12
|
// NOTE: the binary/expression subsystems are bound in the native binding and documented below.
|
|
9
13
|
// File-touching operations (db:open_file, db:bin_to_csv, db:csv_to_bin, expr:save) are sandboxed
|
|
10
14
|
// to the database file's directory; the pure-metadata builders stay under the quiver.* global.
|
|
11
15
|
//
|
|
12
|
-
// FORMAT CONVENTION: every db: method
|
|
13
|
-
// `db:<snake_case_name
|
|
16
|
+
// FORMAT CONVENTION: every db: method appears at least once as the literal token
|
|
17
|
+
// `db:<snake_case_name>`, and every quiver.* function as `quiver.<name>`, so coverage is greppable
|
|
18
|
+
// (the sync test relies on this).
|
|
14
19
|
export const LUA_DB_API_REFERENCE = `
|
|
15
20
|
# Quiver Lua API Reference
|
|
16
21
|
|
|
@@ -66,19 +71,51 @@ datetime surface — there are no DateTime wrapper helpers, unlike Julia/Dart/Py
|
|
|
66
71
|
- **Errors abort the script.** Any error thrown by a \`db:\` call stops the script and surfaces as
|
|
67
72
|
\`Failed to run Lua script: <message>\`. Validation failures roll back whatever the current
|
|
68
73
|
transaction covered.
|
|
69
|
-
- **Standard library.**
|
|
70
|
-
|
|
71
|
-
\`loadfile\` are removed (string-form \`load\`
|
|
74
|
+
- **Standard library.** Loaded standard libraries: base, string, table, math, coroutine, utf8.
|
|
75
|
+
That is the pure-computation set — there is no \`os\`, \`io\`, \`debug\`, or \`package\`/\`require\`,
|
|
76
|
+
and \`dofile\`/\`loadfile\` are removed (string-form \`load\` stays available). Integer division is
|
|
77
|
+
the Lua 5.4 \`//\` operator — a language operator, unrelated to \`math\`.
|
|
72
78
|
- **Filesystem sandbox.** Every file-touching operation (\`db:export_csv\`, \`db:import_csv\`,
|
|
73
79
|
\`db:open_file\`, \`db:bin_to_csv\`, \`db:csv_to_bin\`, \`expr:save\`) resolves relative paths against
|
|
74
80
|
the directory containing the database file and rejects anything outside it (subdirectories are
|
|
75
81
|
fine; \`..\` escapes and outside absolute paths throw \`Cannot <op>: path '...' escapes the
|
|
76
82
|
database directory ...\`). On an in-memory database these operations throw
|
|
77
83
|
\`Cannot <op>: database is in-memory, file operations are unavailable\`.
|
|
78
|
-
- **Output.**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
- **Output.** A script can \`return\` one value and the host receives it as JSON — prefer this over
|
|
85
|
+
\`print()\` when you need structured data back (\`print()\` still works and is captured). Only the
|
|
86
|
+
**first** returned value is encoded. Arrays are 1-indexed (iterate with \`ipairs\`); reading a NULL
|
|
87
|
+
yields \`nil\`, writing \`nil\` stores NULL where NULL is accepted (query params, ts rows, file
|
|
88
|
+
columns — but NOT element scalar attributes; see CRUD).
|
|
89
|
+
|
|
90
|
+
\`\`\`lua
|
|
91
|
+
return { ids = db:read_element_ids("Collection"), total = 3 }
|
|
92
|
+
-- host receives: {"ids":[1,2,3],"total":3}
|
|
93
|
+
\`\`\`
|
|
94
|
+
|
|
95
|
+
| Returned | JSON |
|
|
96
|
+
| -------------------------- | ------------------------------------------------------- |
|
|
97
|
+
| nothing | \`""\` (empty — distinct from \`nil\`) |
|
|
98
|
+
| \`nil\` | \`null\` |
|
|
99
|
+
| integer / float / string | the value; a float keeps its shortest round-trip form |
|
|
100
|
+
| boolean | \`true\` / \`false\` |
|
|
101
|
+
| NaN, \`math.huge\` | \`null\` (JSON has no NaN/Infinity) |
|
|
102
|
+
| table keyed \`1..n\` | array — an empty table \`{}\` encodes as \`[]\` |
|
|
103
|
+
| any other table | object, keys sorted; integer keys stringify |
|
|
104
|
+
|
|
105
|
+
**A table with holes is an object, not an array.** A bulk read of a nullable column returns
|
|
106
|
+
\`nil\` holes (see Reading), so \`return db:read_scalar_integers(c, a)\` encodes as
|
|
107
|
+
\`{"1":10,"3":30}\` — not \`[10,null,30]\` — and the keys sort as text (\`"1","11","2"\`). When the host
|
|
108
|
+
needs positional data, return the ids alongside and fill the holes yourself:
|
|
109
|
+
\`local v = db:read_scalar_integers(c, a); local out = {}; for i in ipairs(ids) do out[i] = v[i] or false end\`.
|
|
110
|
+
|
|
111
|
+
Returning a function, a coroutine, or a userdata (including \`db\` itself) raises
|
|
112
|
+
\`Cannot run: script returned an unsupported Lua type\`; nesting deeper than 32 levels raises
|
|
113
|
+
\`Cannot run: script return value nests deeper than 32 levels\` (this is also what stops a
|
|
114
|
+
self-referencing table); a result over 64 MB raises
|
|
115
|
+
\`Cannot run: script return value exceeds ... bytes of JSON\`; a string holding bytes that are not
|
|
116
|
+
valid UTF-8 raises \`Cannot run: script return value contains a string that is not valid UTF-8\`;
|
|
117
|
+
and a table where an integer key and a string key spell the same thing (\`{[1] = 'a', ['1'] = 'b'}\`)
|
|
118
|
+
raises \`Cannot run: script returned a table with duplicate key '1'\`.
|
|
82
119
|
- **Embedding harness may restrict further.** The library itself allows transactions and the
|
|
83
120
|
(sandboxed) CSV/file operations below. A host that runs your script (e.g. a hosted \`run_lua\`
|
|
84
121
|
tool) may disable some of them and report \`disabled in the run_lua sandbox\` — that limit comes
|
|
@@ -121,14 +158,54 @@ db:transaction(function(db)
|
|
|
121
158
|
end) -- both writes commit together; if either throws, both roll back
|
|
122
159
|
\`\`\`
|
|
123
160
|
|
|
124
|
-
**Caveat:** if the host already runs your script inside a transaction, an
|
|
125
|
-
\`db:begin_transaction()\` will error (\`cannot start a transaction within a transaction\`)
|
|
126
|
-
mid-script \`db:commit()\` would prematurely end the host's transaction. When unsure whether
|
|
127
|
-
transaction is already open, check \`db:in_transaction()\` first, or just issue writes directly —
|
|
161
|
+
**Caveat:** if the host already runs your script inside a plain transaction (not a dry run), an
|
|
162
|
+
explicit \`db:begin_transaction()\` will error (\`cannot start a transaction within a transaction\`)
|
|
163
|
+
and a mid-script \`db:commit()\` would prematurely end the host's transaction. When unsure whether
|
|
164
|
+
a transaction is already open, check \`db:in_transaction()\` first, or just issue writes directly —
|
|
128
165
|
each \`db:\` write is durable on its own.
|
|
129
166
|
|
|
130
167
|
---
|
|
131
168
|
|
|
169
|
+
## Dry runs
|
|
170
|
+
|
|
171
|
+
A dry run executes writes and then throws them away. Use it to check that a sequence works — that
|
|
172
|
+
the collections exist, the types match, the foreign keys resolve — before committing to it.
|
|
173
|
+
|
|
174
|
+
\`\`\`lua
|
|
175
|
+
db:dry_run(fn) -- run fn(db), roll everything back, return fn's result
|
|
176
|
+
db:begin_dry_run() -- start one explicitly
|
|
177
|
+
db:end_dry_run() -- end it, rolling back everything it covered
|
|
178
|
+
db:in_dry_run() -- boolean: is a dry run currently active?
|
|
179
|
+
\`\`\`
|
|
180
|
+
|
|
181
|
+
\`\`\`lua
|
|
182
|
+
local preview = db:dry_run(function(db)
|
|
183
|
+
db:create_element("Collection", { label = "Item 1", some_integer = 42 })
|
|
184
|
+
return db:read_element_ids("Collection") -- reads see the uncommitted writes
|
|
185
|
+
end)
|
|
186
|
+
-- nothing was kept; preview holds what the reads saw
|
|
187
|
+
\`\`\`
|
|
188
|
+
|
|
189
|
+
Rules worth knowing:
|
|
190
|
+
|
|
191
|
+
- **Nested transaction control is absorbed.** Inside a dry run, \`db:begin_transaction\`,
|
|
192
|
+
\`db:commit\` and \`db:rollback\` become no-ops, so the \`db:transaction\` pattern above composes
|
|
193
|
+
instead of erroring — \`db:transaction(fn)\` still runs \`fn\` and still performs its writes, only
|
|
194
|
+
its BEGIN/COMMIT are absorbed. The flip side: a nested \`db:rollback\` does **not** partially
|
|
195
|
+
undo — everything is undone when the dry run ends, whatever the nested calls asked for.
|
|
196
|
+
- \`db:in_transaction()\` still reports \`true\` during a dry run: a real transaction is open.
|
|
197
|
+
- \`db:import_csv\` cannot run inside a dry run (it toggles a pragma that is a no-op mid-transaction)
|
|
198
|
+
and throws \`Cannot import_csv: transaction already active\`.
|
|
199
|
+
- **Dry runs do not nest.** The host may have already opened one around your whole script, in which
|
|
200
|
+
case both \`db:begin_dry_run()\` and \`db:dry_run(fn)\` (which calls it internally) error with
|
|
201
|
+
\`Cannot begin_dry_run: dry run already active\` — check \`db:in_dry_run()\` first and skip the
|
|
202
|
+
wrapper when one is already active. Do **not** call \`db:end_dry_run()\` to get around it: that
|
|
203
|
+
ends the host's dry run, and everything you write afterwards is committed for real.
|
|
204
|
+
- For a rough sense of how much a run touched, \`db:query_integer("SELECT total_changes()")\` gives
|
|
205
|
+
the number of rows inserted, updated or deleted on this connection.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
132
209
|
## CRUD
|
|
133
210
|
|
|
134
211
|
\`\`\`lua
|
|
@@ -164,8 +241,9 @@ Notes:
|
|
|
164
241
|
type can't be inferred from an empty array), so it is silently dropped.
|
|
165
242
|
- **No \`nil\` scalar attributes.** In Lua a key set to \`nil\` is dropped from the table, so
|
|
166
243
|
\`{ x = nil }\` is identical to \`{}\`; an update/create table that ends up with no attributes
|
|
167
|
-
**throws** (
|
|
168
|
-
|
|
244
|
+
**throws** (\`...must have at least one scalar attribute\` on create, \`...at least one attribute
|
|
245
|
+
to update\` on update). To leave a column unchanged, omit the key — you cannot set a scalar to
|
|
246
|
+
NULL via the element table. (\`nil\` → NULL is only accepted by
|
|
169
247
|
\`upsert_time_series_row\` and \`update_time_series_files\`.)
|
|
170
248
|
|
|
171
249
|
---
|
|
@@ -489,7 +567,8 @@ db:csv_to_bin(path)
|
|
|
489
567
|
|
|
490
568
|
local e = (quiver.expression(r) + 10.0) * 2.0 -- files auto-wrap; scalars either side
|
|
491
569
|
e = quiver.abs(e); e = quiver.sqrt(e) -- also quiver.log / quiver.exp
|
|
492
|
-
local cond_e = quiver.gt(e, 3.0) --
|
|
570
|
+
local cond_e = quiver.gt(e, 3.0) -- also quiver.lt/quiver.gte/quiver.lte/quiver.eq/quiver.neq
|
|
571
|
+
-- all -> 1.0/0.0 per element (NaN operand -> NaN)
|
|
493
572
|
cond_e = cond_e & ~quiver.lt(e, 1.0) -- boolean logic via & | ~ operators (and/or/not are keywords)
|
|
494
573
|
e = quiver.ifelse(cond_e, then_e, else_e) -- build cond_e with comparison + logical operators
|
|
495
574
|
e = e:aggregate("stage", "sum") -- sum/mean/min/max/percentile
|
package/src/lua-runner.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Database } from "./database.ts";
|
|
2
2
|
import { check, QuiverError } from "./errors.ts";
|
|
3
|
-
import { allocPtrOut, readPtrOut, toCString } from "./ffi-helpers.ts";
|
|
3
|
+
import { allocPtrOut, decodeStringFromBuf, readPtrOut, toCString } from "./ffi-helpers.ts";
|
|
4
4
|
import type { NativePointer } from "./loader.ts";
|
|
5
5
|
import { getSymbols } from "./loader.ts";
|
|
6
6
|
|
|
@@ -15,11 +15,21 @@ export class LuaRunner {
|
|
|
15
15
|
this._ptr = readPtrOut(outRunner);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Runs a Lua script and returns its return value encoded as JSON, or "" if it returned nothing.
|
|
20
|
+
*
|
|
21
|
+
* To execute a script without keeping its writes, wrap the call in
|
|
22
|
+
* `db.beginDryRun()` / `db.endDryRun()`.
|
|
23
|
+
*/
|
|
24
|
+
run(script: string): string {
|
|
19
25
|
this.ensureOpen();
|
|
20
26
|
const lib = getSymbols();
|
|
21
27
|
const scriptBuf = toCString(script);
|
|
22
|
-
|
|
28
|
+
const outResult = allocPtrOut();
|
|
29
|
+
check(lib.quiver_lua_runner_run(this._ptr, scriptBuf.buf, outResult.buf));
|
|
30
|
+
const result = decodeStringFromBuf(outResult);
|
|
31
|
+
lib.quiver_lua_runner_free_string(readPtrOut(outResult));
|
|
32
|
+
return result;
|
|
23
33
|
}
|
|
24
34
|
|
|
25
35
|
close(): void {
|
package/src/transaction.ts
CHANGED
|
@@ -23,3 +23,27 @@ Database.prototype.inTransaction = function (this: Database): boolean {
|
|
|
23
23
|
check(lib.quiver_database_in_transaction(this._handle, outBuf));
|
|
24
24
|
return new DataView(outBuf.buffer).getInt32(0, true) !== 0;
|
|
25
25
|
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Begin a dry run: a transaction that endDryRun always rolls back.
|
|
29
|
+
*
|
|
30
|
+
* While it is active, beginTransaction/commit/rollback are absorbed (no-ops), so code that
|
|
31
|
+
* manages its own transactions composes instead of erroring on a nested BEGIN. A nested rollback
|
|
32
|
+
* is therefore not partial -- everything is undone when the dry run ends.
|
|
33
|
+
*/
|
|
34
|
+
Database.prototype.beginDryRun = function (this: Database): void {
|
|
35
|
+
const lib = getSymbols();
|
|
36
|
+
check(lib.quiver_database_begin_dry_run(this._handle));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
Database.prototype.endDryRun = function (this: Database): void {
|
|
40
|
+
const lib = getSymbols();
|
|
41
|
+
check(lib.quiver_database_end_dry_run(this._handle));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
Database.prototype.inDryRun = function (this: Database): boolean {
|
|
45
|
+
const lib = getSymbols();
|
|
46
|
+
const outBuf = new Uint8Array(4);
|
|
47
|
+
check(lib.quiver_database_in_dry_run(this._handle, outBuf));
|
|
48
|
+
return new DataView(outBuf.buffer).getInt32(0, true) !== 0;
|
|
49
|
+
};
|