quiverdb 0.12.9 → 0.13.1
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 +2 -2
- package/libs/linux-aarch64/libquiver.so +0 -0
- package/libs/linux-aarch64/libquiver.so.0 +0 -0
- package/libs/linux-aarch64/libquiver_c.so +0 -0
- 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 +4 -2
- package/src/group-columns.ts +8 -6
- package/src/index.ts +2 -2
- package/src/loader.ts +19 -8
- package/src/{lua-api.ts → sandbox-api.ts} +91 -27
- package/src/{lua-runner.ts → sandbox.ts} +7 -7
- package/src/time-series.ts +8 -6
package/README.md
CHANGED
|
@@ -194,10 +194,10 @@ as an exact INTEGER, a `boolean` as the INTEGER 1 or 0).
|
|
|
194
194
|
|
|
195
195
|
### Lua
|
|
196
196
|
|
|
197
|
-
- `
|
|
197
|
+
- `Sandbox(db)` -- Create Lua script runner with database access
|
|
198
198
|
- `run(script)` -- Execute a Lua script; returns its return value as a JSON string, or `""` if it
|
|
199
199
|
returned nothing
|
|
200
|
-
- `close()` -- Close the
|
|
200
|
+
- `close()` -- Close the Sandbox
|
|
201
201
|
|
|
202
202
|
## Types
|
|
203
203
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
22
|
"test": "bun test test",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
23
24
|
"lint": "biome check",
|
|
24
25
|
"format": "biome format --write"
|
|
25
26
|
},
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@biomejs/biome": "^2.4.6",
|
|
32
|
-
"@types/bun": "latest"
|
|
33
|
+
"@types/bun": "latest",
|
|
34
|
+
"typescript": "7.0.2"
|
|
33
35
|
}
|
|
34
36
|
}
|
package/src/group-columns.ts
CHANGED
|
@@ -42,11 +42,11 @@ export type TimeSeriesData = Record<string, (number | string | null)[]>;
|
|
|
42
42
|
* (quiver_database_update_{time_series,vector,set}_group and their _by_label forms). The 4th
|
|
43
43
|
* argument addresses the element: an id for the by-id forms, a NUL-terminated label otherwise.
|
|
44
44
|
*/
|
|
45
|
-
type ColumnUpdateFn = (
|
|
45
|
+
type ColumnUpdateFn<Key extends number | string> = (
|
|
46
46
|
db: NativePointer,
|
|
47
47
|
collection: Uint8Array,
|
|
48
48
|
group: Uint8Array,
|
|
49
|
-
key:
|
|
49
|
+
key: Key extends string ? Uint8Array : bigint,
|
|
50
50
|
names: Uint8Array | null,
|
|
51
51
|
types: Uint8Array | null,
|
|
52
52
|
data: Uint8Array | null,
|
|
@@ -90,18 +90,20 @@ export function numericCells(
|
|
|
90
90
|
*
|
|
91
91
|
* Pass `{}` (no columns) to clear the group.
|
|
92
92
|
*/
|
|
93
|
-
export function updateGroupColumns(
|
|
93
|
+
export function updateGroupColumns<Key extends number | string>(
|
|
94
94
|
handle: NativePointer,
|
|
95
95
|
caller: string,
|
|
96
|
-
update: ColumnUpdateFn
|
|
96
|
+
update: ColumnUpdateFn<Key>,
|
|
97
97
|
collection: string,
|
|
98
98
|
group: string,
|
|
99
|
-
key:
|
|
99
|
+
key: Key,
|
|
100
100
|
data: GroupColumns,
|
|
101
101
|
): void {
|
|
102
102
|
const collBuf = toCString(collection);
|
|
103
103
|
const grpBuf = toCString(group);
|
|
104
|
-
const keyArg = typeof key === "string" ? toCString(key).buf : BigInt(key)
|
|
104
|
+
const keyArg = (typeof key === "string" ? toCString(key).buf : BigInt(key)) as Key extends string
|
|
105
|
+
? Uint8Array
|
|
106
|
+
: bigint;
|
|
105
107
|
const entries = Object.entries(data);
|
|
106
108
|
|
|
107
109
|
if (entries.length === 0) {
|
package/src/index.ts
CHANGED
|
@@ -12,8 +12,8 @@ export type { CsvOptions } from "./csv.ts";
|
|
|
12
12
|
export { Database } from "./database.ts";
|
|
13
13
|
export { QuiverError } from "./errors.ts";
|
|
14
14
|
export type { GroupColumns, TimeSeriesData } from "./group-columns.ts";
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
15
|
+
export { SANDBOX_API_REFERENCE } from "./sandbox-api.ts";
|
|
16
|
+
export { Sandbox } from "./sandbox.ts";
|
|
17
17
|
export type { GroupMetadata, ScalarMetadata } from "./metadata.ts";
|
|
18
18
|
export type {
|
|
19
19
|
ArrayValue,
|
package/src/loader.ts
CHANGED
|
@@ -196,11 +196,11 @@ const freeSymbols = {
|
|
|
196
196
|
quiver_database_free_group_metadata_array: { args: [P, USIZE], returns: I32 },
|
|
197
197
|
} as const;
|
|
198
198
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
const sandboxSymbols = {
|
|
200
|
+
quiver_sandbox_new: { args: [P, P], returns: I32 },
|
|
201
|
+
quiver_sandbox_free: { args: [P], returns: I32 },
|
|
202
|
+
quiver_sandbox_run: { args: [P, BUF, BUF], returns: I32 },
|
|
203
|
+
quiver_sandbox_free_string: { args: [P], returns: I32 },
|
|
204
204
|
} as const;
|
|
205
205
|
|
|
206
206
|
// Combined symbol map for dlopen.
|
|
@@ -216,7 +216,7 @@ const allSymbols = {
|
|
|
216
216
|
...timeSeriesSymbols,
|
|
217
217
|
...csvSymbols,
|
|
218
218
|
...freeSymbols,
|
|
219
|
-
...
|
|
219
|
+
...sandboxSymbols,
|
|
220
220
|
} as const;
|
|
221
221
|
|
|
222
222
|
// ---------------------------------------------------------------------------
|
|
@@ -288,7 +288,17 @@ function initLibrary(): QuiverLib {
|
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
// Tier 2:
|
|
291
|
+
// Tier 2: Standalone Bun executables ship native siblings beside the executable.
|
|
292
|
+
const executableDir = dirname(process.execPath);
|
|
293
|
+
if (existsSync(join(executableDir, C_API_LIB))) {
|
|
294
|
+
try {
|
|
295
|
+
return openLibrary(executableDir);
|
|
296
|
+
} catch (e) {
|
|
297
|
+
lastError = e;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Tier 3: Dev mode -- walk up directories looking for build/bin/.
|
|
292
302
|
for (const dir of getSearchPaths()) {
|
|
293
303
|
try {
|
|
294
304
|
return openLibrary(dir);
|
|
@@ -297,7 +307,7 @@ function initLibrary(): QuiverLib {
|
|
|
297
307
|
}
|
|
298
308
|
}
|
|
299
309
|
|
|
300
|
-
// Tier
|
|
310
|
+
// Tier 4: System PATH fallback -- the core lib is expected to be discoverable
|
|
301
311
|
// on PATH alongside the C API lib.
|
|
302
312
|
try {
|
|
303
313
|
return dlopen(C_API_LIB, allSymbols);
|
|
@@ -307,6 +317,7 @@ function initLibrary(): QuiverLib {
|
|
|
307
317
|
|
|
308
318
|
const searched = [
|
|
309
319
|
join(__dirname, "..", "libs", platformKey),
|
|
320
|
+
executableDir,
|
|
310
321
|
...getSearchPaths(),
|
|
311
322
|
"system PATH",
|
|
312
323
|
].join(", ");
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
// Agent-facing reference for the Lua `db` API available inside run_lua scripts.
|
|
2
2
|
//
|
|
3
|
-
// Authority: `src/
|
|
3
|
+
// Authority: the binders under `src/sandbox/` (`bind_database` through `bind_expression`,
|
|
4
|
+
// this repo) — extracted by hand, NOT imported.
|
|
4
5
|
// The shipped quiverdb native binding is the runtime truth; this is docs.
|
|
5
6
|
//
|
|
6
|
-
// SYNC: `test/
|
|
7
|
+
// SYNC: `test/sandbox-api-sync.test.ts` derives the bound surface from every `.cpp`/`.h` under
|
|
8
|
+
// `src/sandbox/` and checks
|
|
7
9
|
// it AUTOMATICALLY — every `db:`/`quiver.*` name is documented, no documented name has been
|
|
8
10
|
// removed, and the stdlib sentence matches `open_libraries` exactly. What it CANNOT check, and you
|
|
9
11
|
// must still re-diff by hand when the binding changes: arg order, arity, arg types, return shapes,
|
|
10
12
|
// and whether the prose is semantically true.
|
|
11
13
|
//
|
|
12
14
|
// NOTE: the binary/expression subsystems are bound in the native binding and documented below.
|
|
13
|
-
// File-touching operations
|
|
14
|
-
//
|
|
15
|
-
// the quiver.* global.
|
|
15
|
+
// File-touching operations are sandboxed to the database file's directory (the "Filesystem
|
|
16
|
+
// sandbox" bullet lists all of them); the pure-metadata builders stay under the quiver.* global.
|
|
16
17
|
//
|
|
17
18
|
// FORMAT CONVENTION: every db: method appears at least once as the literal token
|
|
18
19
|
// `db:<snake_case_name>`, and every quiver.* function as `quiver.<name>`, so coverage is greppable
|
|
19
20
|
// (the sync test relies on this).
|
|
20
|
-
export const
|
|
21
|
-
# Quiver
|
|
21
|
+
export const SANDBOX_API_REFERENCE = `
|
|
22
|
+
# Quiver Sandbox API Reference
|
|
22
23
|
|
|
23
|
-
Quiver embeds a Lua scripting layer
|
|
24
|
-
|
|
25
|
-
script.
|
|
24
|
+
Quiver embeds a Lua sandbox scripting layer that exposes the same database API as the other language
|
|
25
|
+
bindings. This document is a complete reference of every method available to a Lua script.
|
|
26
26
|
|
|
27
27
|
## Running scripts
|
|
28
28
|
|
|
29
29
|
The database is provided to your script as a global userdata named \`db\`. There are **no
|
|
30
|
-
open/close lifecycle methods in Lua** — the \`
|
|
30
|
+
open/close lifecycle methods in Lua** — the \`Sandbox\` opens the database and hands you \`db\`
|
|
31
31
|
already connected. All methods are called with the colon syntax:
|
|
32
32
|
|
|
33
33
|
\`\`\`lua
|
|
@@ -100,18 +100,27 @@ midnight.
|
|
|
100
100
|
and do not \`pcall\` inside it: a caught error lets the block commit whatever ran.
|
|
101
101
|
- **Standard library.** Loaded standard libraries: base, string, table, math, coroutine, utf8.
|
|
102
102
|
That is the pure-computation set — there is no \`os\`, \`io\`, \`debug\`, or \`package\`/\`require\`,
|
|
103
|
-
and \`dofile\`/\`loadfile\` are removed (string-form \`load\` stays
|
|
104
|
-
the Lua 5.4 \`//\` operator — a language operator,
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
and \`dofile\`/\`loadfile\` are removed (string-form \`load\` stays for source text; a precompiled
|
|
104
|
+
binary chunk is refused). Integer division is the Lua 5.4 \`//\` operator — a language operator,
|
|
105
|
+
unrelated to \`math\`. No \`io\` does **not** mean
|
|
106
|
+
a data file on disk is out of reach: use \`db:read_csv\` / \`db:read_csv_stream\` for CSV or
|
|
107
|
+
\`db:read_xlsx\` / \`db:read_xlsx_stream\` for XLSX (see the file reading sections below).
|
|
108
|
+
Never copy, paste, or re-type a data file's contents into
|
|
107
109
|
the script as literals — read the file.
|
|
108
110
|
- **Filesystem sandbox.** Every file-touching operation (\`db:export_csv\`, \`db:import_csv\`,
|
|
109
111
|
\`db:open_file\`, \`db:bin_to_csv\`, \`db:csv_to_bin\`, \`db:validate_migrations\`, \`db:read_csv\`,
|
|
110
|
-
\`db:read_csv_stream\`, \`db:write_csv\`, \`
|
|
112
|
+
\`db:read_csv_stream\`, \`db:write_csv\`, \`db:read_xlsx\`, \`db:read_xlsx_stream\`,
|
|
113
|
+
\`save\` on a file or an expression) resolves
|
|
111
114
|
relative paths against the directory containing the database file and rejects anything outside it
|
|
112
115
|
(subdirectories are fine; \`..\` escapes and outside absolute paths throw \`Cannot <op>: path '...' escapes the
|
|
113
116
|
database directory ...\`). On an in-memory database these operations throw
|
|
114
117
|
\`Cannot <op>: database is in-memory, file operations are unavailable\`.
|
|
118
|
+
- **What the sandbox does not limit.** The sandbox controls which files a script can touch and
|
|
119
|
+
which standard libraries exist. It does not bound how much work a script does: there is no
|
|
120
|
+
instruction-count limit, no memory cap and no wall-clock timeout (\`while true do end\` runs
|
|
121
|
+
until the host stops it). Globals persist across \`run()\` calls on the same runner, so a global
|
|
122
|
+
one script sets is visible to the next; use \`local\`. A host that runs untrusted scripts has to
|
|
123
|
+
impose those limits outside the library.
|
|
115
124
|
- **Output.** A script can \`return\` one value and the host receives it as JSON — prefer this over
|
|
116
125
|
\`print()\` when you need structured data back (\`print()\` still works and is captured). Only the
|
|
117
126
|
**first** returned value is encoded. Arrays are 1-indexed. Iterate with \`ipairs\` only where no
|
|
@@ -193,7 +202,7 @@ db:begin_transaction() -- start an explicit transaction
|
|
|
193
202
|
db:commit() -- commit it
|
|
194
203
|
db:rollback() -- roll it back
|
|
195
204
|
db:in_transaction() -- boolean: is a transaction currently open?
|
|
196
|
-
db:transaction(fn) -- run fn(db) inside begin/commit; rollback + rethrow if fn errors
|
|
205
|
+
db:transaction(fn) -- run fn(db) inside begin/commit; rollback + rethrow if fn errors or the commit fails
|
|
197
206
|
\`\`\`
|
|
198
207
|
|
|
199
208
|
To make a group of writes atomic, prefer the \`db:transaction\` wrapper:
|
|
@@ -303,8 +312,13 @@ Notes:
|
|
|
303
312
|
which only the new label resolves. Because the label form delegates to the id form, failures
|
|
304
313
|
that validate the *element* (an empty table, a type mismatch) report
|
|
305
314
|
\`Cannot update_element: ...\`.
|
|
306
|
-
- **
|
|
307
|
-
|
|
315
|
+
- **An empty array clears on update.** On \`update_element\` / \`update_element_by_label\`,
|
|
316
|
+
\`{ col = {} }\` clears the whole group holding \`col\` (all its columns, and every group that
|
|
317
|
+
shares the column name). An empty column beside a non-empty column of the same group throws a
|
|
318
|
+
length error, and a misspelled empty column throws
|
|
319
|
+
\`array '<name>' does not match any vector, set, or time series table ...\`. \`create_element\`
|
|
320
|
+
skips an empty array. To leave a group alone, omit its column: writing back a
|
|
321
|
+
\`read_vectors_by_id\` result clears a group whose read column came back empty or all-NULL.
|
|
308
322
|
- **Arrays must be dense.** A vector/set read returns a NULL cell as a \`nil\` hole, but an element
|
|
309
323
|
array cannot carry one: \`create_element\` / \`update_element\` throw \`array '<name>' has a nil
|
|
310
324
|
hole ...\` rather than cut the array short at the hole. Write NULL cells with
|
|
@@ -691,7 +705,7 @@ own) — it throws \`Cannot import_csv: transaction already active\`. Call it ou
|
|
|
691
705
|
|
|
692
706
|
## CSV file reading
|
|
693
707
|
|
|
694
|
-
Read a CSV file from disk directly into Lua
|
|
708
|
+
Read a CSV file from disk directly into Lua, since
|
|
695
709
|
\`io\` is deliberately absent from the sandbox. \`path\` is sandboxed the same way as every other
|
|
696
710
|
file-touching operation (see Critical rules).
|
|
697
711
|
|
|
@@ -712,8 +726,8 @@ no header at all, so \`csv.header\` is absent (\`nil\`, not an empty table) and
|
|
|
712
726
|
file's first line — useful for a file with a junk title row and/or a units row around the real
|
|
713
727
|
header (skip them by naming the header row and slicing \`csv.rows\` in the script). A \`header_row\`
|
|
714
728
|
past the end of the file throws. Passing the separator positionally (\`db:read_csv(path, ";")\`)
|
|
715
|
-
throws \`Cannot read_csv: options must be a table\` instead of silently parsing with
|
|
716
|
-
unknown key, a separator that isn't a single character (or is a quote, CR, LF or NUL — none of
|
|
729
|
+
throws \`Cannot read_csv: options must be a table, got string\` instead of silently parsing with
|
|
730
|
+
a comma; an unknown key, a separator that isn't a single character (or is a quote, CR, LF or NUL — none of
|
|
717
731
|
those can be a delimiter), a non-string option key, or a \`header_row\` that isn't a
|
|
718
732
|
non-negative integer also throws.
|
|
719
733
|
|
|
@@ -777,6 +791,47 @@ end
|
|
|
777
791
|
|
|
778
792
|
---
|
|
779
793
|
|
|
794
|
+
## XLSX file reading
|
|
795
|
+
|
|
796
|
+
Read one worksheet of an XLSX workbook through the same filesystem sandbox as CSV. XLSX only:
|
|
797
|
+
legacy XLS and encrypted workbooks are unsupported. These methods read cells; they do not write
|
|
798
|
+
the workbook or import rows into the database.
|
|
799
|
+
|
|
800
|
+
\`\`\`lua
|
|
801
|
+
local data = db:read_xlsx("input.xlsx", { sheet = "Data", header_row = 1 })
|
|
802
|
+
-- data.header: array of strings; data.rows: array of arrays of strings
|
|
803
|
+
local count = db:read_xlsx_stream("input.xlsx", function(row, index, header)
|
|
804
|
+
-- header is available even on the first callback; index counts emitted data rows.
|
|
805
|
+
-- Only an explicit false stops. No return, nil, 0 and strings all continue.
|
|
806
|
+
end, { sheet = 2 })
|
|
807
|
+
\`\`\`
|
|
808
|
+
|
|
809
|
+
The optional options table accepts exactly \`sheet\` and \`header_row\`. \`sheet\` is an exact
|
|
810
|
+
worksheet name or a positive 1-based integer, default \`1\`. Chart sheets do not count; hidden
|
|
811
|
+
worksheets do. Missing worksheets throw. \`header_row\` is the physical worksheet row number,
|
|
812
|
+
default \`1\`; rows above it are skipped. A missing or completely blank header throws.
|
|
813
|
+
\`header_row = 0\` reads all data rows with no header (\`data.header\` and the callback header are
|
|
814
|
+
\`nil\`). Omit options, pass \`nil\`, or pass \`{}\` to use defaults; unknown keys and wrong
|
|
815
|
+
types throw \`Cannot <operation>: ...\`.
|
|
816
|
+
|
|
817
|
+
Every cell is a string. Text, including leading zeros, Unicode, whitespace and rich-text content,
|
|
818
|
+
is preserved. Numbers keep their stored locale-independent text, booleans are \`"1"\`/\`"0"\`,
|
|
819
|
+
blank cells are \`""\`, and Excel errors such as \`#DIV/0!\` stay text. Formatting is ignored:
|
|
820
|
+
an Excel date stored as \`45292.5\` remains \`"45292.5"\`, and an explicit date stored as text
|
|
821
|
+
stays text. Formulas return their saved cached value, never the expression or a recalculated value.
|
|
822
|
+
A formula with no cache throws, naming the sheet and cell; recalculate and save it in Excel first.
|
|
823
|
+
|
|
824
|
+
Completely blank data rows are skipped. Rows and headers are padded with \`""\` to the worksheet's
|
|
825
|
+
last column containing a value or formula, so column positions stay aligned; formatting-only
|
|
826
|
+
cells do not expand that width. Merged cells contribute only their stored values (normally the
|
|
827
|
+
top-left cell); values are not copied across the merged area. Header-only sheets return no data
|
|
828
|
+
rows; an empty sheet is readable with \`header_row = 0\`.
|
|
829
|
+
|
|
830
|
+
Both forms use the same reader. The callback form returns the number of rows delivered, including
|
|
831
|
+
the row that returned false; callback errors propagate unchanged and handles close on every exit.
|
|
832
|
+
Unlike CSV streaming, XLSX callbacks avoid the full Lua rows table but still load worksheet XML
|
|
833
|
+
and shared strings into memory. For very large files, use CSV streaming instead.
|
|
834
|
+
|
|
780
835
|
## CSV file writing
|
|
781
836
|
|
|
782
837
|
Write a CSV file to disk — the only way to get data out of a script onto disk, since \`io\` is
|
|
@@ -870,9 +925,16 @@ File I/O is db-scoped (\`db:open_file\` / \`db:bin_to_csv\` / \`db:csv_to_bin\`)
|
|
|
870
925
|
base paths, sandboxed to the database directory (see Critical rules), and \`get_file_path()\` returns
|
|
871
926
|
the resolved absolute path. The pure-metadata builders and expression constructors live under the
|
|
872
927
|
global \`quiver\` table. Mirrors the Julia surface; aggregation ops are strings (Lua has no enums);
|
|
873
|
-
operators are \`+ - * /\` and unary \`-\`, with scalars allowed on either side. A file
|
|
874
|
-
|
|
875
|
-
|
|
928
|
+
operators are \`+ - * /\` and unary \`-\`, with scalars allowed on either side. A binary file **is** an
|
|
929
|
+
expression: every operator, every \`quiver.*\` expression function and every expression method
|
|
930
|
+
(\`aggregate\`, \`aggregate_agents\`, \`select_agents\`, \`rename_agents\`, \`save\`, \`get_metadata\`) takes
|
|
931
|
+
a file handle directly; \`quiver.expression(f)\` stays as an explicit conversion, and saving from a
|
|
932
|
+
file reads it by path and leaves the handle open. A wrong operand raises \`Cannot <op>: operand must
|
|
933
|
+
be an expression or a binary file, got <type>\`, and extra arguments to a \`quiver.*\` expression
|
|
934
|
+
function (or to an operator metamethod called directly) raise
|
|
935
|
+
\`Cannot <op>: too many arguments (expected N, got M)\`; expression methods ignore extra arguments,
|
|
936
|
+
like every other method. A file handle does not outlive the \`run()\` that opened it: any handle still open when the script returns
|
|
937
|
+
is closed (and a writer flushed), so reopen the file in each script.
|
|
876
938
|
|
|
877
939
|
\`\`\`lua
|
|
878
940
|
local md = quiver.metadata{
|
|
@@ -892,7 +954,9 @@ quiver.metadata_from_toml(text); quiver.metadata_from_element(tbl)
|
|
|
892
954
|
db:bin_to_csv(path) -- aggregate=true by default; pass false to keep time dims as columns
|
|
893
955
|
db:csv_to_bin(path)
|
|
894
956
|
|
|
895
|
-
local e = (
|
|
957
|
+
local e = (r + 10.0) * 2.0 -- a file is an expression; scalars either side
|
|
958
|
+
local per_stage = r:aggregate("stage", "sum") -- every expression method works on a file too
|
|
959
|
+
r:save(copy_path) -- reads the file by path; r stays open
|
|
896
960
|
e = quiver.abs(e); e = quiver.sqrt(e) -- also quiver.log / quiver.exp
|
|
897
961
|
local cond_e = quiver.gt(e, 3.0) -- also quiver.lt/quiver.gte/quiver.lte/quiver.eq/quiver.neq
|
|
898
962
|
-- all -> 1.0/0.0 per element (NaN operand -> NaN)
|
|
@@ -902,7 +966,7 @@ e = e:aggregate("stage", "sum") -- sum/mean/min/max/percent
|
|
|
902
966
|
e = e:aggregate("stage", "percentile", 0.9) -- percentile needs the fraction
|
|
903
967
|
e = e:aggregate_agents("mean") -- collapse the label axis
|
|
904
968
|
e = e:select_agents({"v2"}); e = e:rename_agents({v1 = "alpha"})
|
|
905
|
-
e:save(out_path); e:
|
|
969
|
+
e:save(out_path); e:get_metadata() -- save path is sandboxed like db:open_file
|
|
906
970
|
\`\`\`
|
|
907
971
|
|
|
908
972
|
**\`quiver.metadata{...}\` kwargs and defaults:** \`version\` defaults to \`"1"\`; \`initial_datetime\`
|
|
@@ -4,19 +4,19 @@ import { allocPtrOut, decodeStringFromBuf, readPtrOut, toCString } from "./ffi-h
|
|
|
4
4
|
import type { NativePointer } from "./loader.ts";
|
|
5
5
|
import { getSymbols } from "./loader.ts";
|
|
6
6
|
|
|
7
|
-
export class
|
|
7
|
+
export class Sandbox {
|
|
8
8
|
private _ptr: NativePointer;
|
|
9
9
|
private _closed = false;
|
|
10
10
|
|
|
11
11
|
constructor(db: Database) {
|
|
12
12
|
const lib = getSymbols();
|
|
13
13
|
const outRunner = allocPtrOut();
|
|
14
|
-
check(lib.
|
|
14
|
+
check(lib.quiver_sandbox_new(db._handle, outRunner.buf));
|
|
15
15
|
this._ptr = readPtrOut(outRunner);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Runs a
|
|
19
|
+
* Runs a script and returns its return value encoded as JSON, or "" if it returned nothing.
|
|
20
20
|
*
|
|
21
21
|
* To execute a script without keeping its writes, wrap the call in
|
|
22
22
|
* `db.beginDryRun()` / `db.endDryRun()`.
|
|
@@ -26,22 +26,22 @@ export class LuaRunner {
|
|
|
26
26
|
const lib = getSymbols();
|
|
27
27
|
const scriptBuf = toCString(script);
|
|
28
28
|
const outResult = allocPtrOut();
|
|
29
|
-
check(lib.
|
|
29
|
+
check(lib.quiver_sandbox_run(this._ptr, scriptBuf.buf, outResult.buf));
|
|
30
30
|
const result = decodeStringFromBuf(outResult);
|
|
31
|
-
lib.
|
|
31
|
+
lib.quiver_sandbox_free_string(readPtrOut(outResult));
|
|
32
32
|
return result;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
close(): void {
|
|
36
36
|
if (this._closed) return;
|
|
37
37
|
const lib = getSymbols();
|
|
38
|
-
check(lib.
|
|
38
|
+
check(lib.quiver_sandbox_free(this._ptr));
|
|
39
39
|
this._closed = true;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
private ensureOpen(): void {
|
|
43
43
|
if (this._closed) {
|
|
44
|
-
throw new QuiverError("
|
|
44
|
+
throw new QuiverError("Sandbox is closed");
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|
package/src/time-series.ts
CHANGED
|
@@ -149,11 +149,11 @@ Database.prototype.updateTimeSeriesGroupByLabel = function (
|
|
|
149
149
|
* (quiver_database_upsert_time_series_row and its _by_label form). `key` is an id for the by-id
|
|
150
150
|
* form, a NUL-terminated label otherwise.
|
|
151
151
|
*/
|
|
152
|
-
type UpsertRowFn = (
|
|
152
|
+
type UpsertRowFn<Key extends number | string> = (
|
|
153
153
|
db: NativePointer,
|
|
154
154
|
collection: Uint8Array,
|
|
155
155
|
group: Uint8Array,
|
|
156
|
-
key:
|
|
156
|
+
key: Key extends string ? Uint8Array : bigint,
|
|
157
157
|
names: Uint8Array | null,
|
|
158
158
|
types: Uint8Array | null,
|
|
159
159
|
data: Uint8Array | null,
|
|
@@ -165,18 +165,20 @@ type UpsertRowFn = (
|
|
|
165
165
|
* C functions. Shared by upsertTimeSeriesRow / upsertTimeSeriesRowByLabel: they differ only in
|
|
166
166
|
* which C entry point they call and whether `key` is an id or a label.
|
|
167
167
|
*/
|
|
168
|
-
function upsertRowColumns(
|
|
168
|
+
function upsertRowColumns<Key extends number | string>(
|
|
169
169
|
handle: NativePointer,
|
|
170
170
|
caller: string,
|
|
171
|
-
upsert: UpsertRowFn
|
|
171
|
+
upsert: UpsertRowFn<Key>,
|
|
172
172
|
collection: string,
|
|
173
173
|
group: string,
|
|
174
|
-
key:
|
|
174
|
+
key: Key,
|
|
175
175
|
row: Record<string, number | bigint | string | boolean>,
|
|
176
176
|
): void {
|
|
177
177
|
const collBuf = toCString(collection);
|
|
178
178
|
const grpBuf = toCString(group);
|
|
179
|
-
const keyArg = typeof key === "string" ? toCString(key).buf : BigInt(key)
|
|
179
|
+
const keyArg = (typeof key === "string" ? toCString(key).buf : BigInt(key)) as Key extends string
|
|
180
|
+
? Uint8Array
|
|
181
|
+
: bigint;
|
|
180
182
|
const entries = Object.entries(row);
|
|
181
183
|
const columnCount = entries.length;
|
|
182
184
|
const keepalive: Allocation[] = [];
|