quiverdb 0.10.2 → 0.10.3
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 +10 -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/boolean.ts +20 -0
- package/src/create.ts +58 -0
- package/src/database.ts +36 -0
- package/src/loader.ts +3 -0
- package/src/lua-api.ts +37 -11
- package/src/query.ts +14 -0
- package/src/read.ts +67 -0
- package/src/types.ts +4 -3
package/README.md
CHANGED
|
@@ -64,6 +64,7 @@ bun run example.ts
|
|
|
64
64
|
|
|
65
65
|
- `Database.fromSchema(dbPath, schemaPath)` -- Create database from SQL schema file
|
|
66
66
|
- `Database.fromMigrations(dbPath, migrationsPath)` -- Create database from migrations directory
|
|
67
|
+
- `Database.validateMigrations(migrationsPath)` -- Validate a migrations directory (every `up.sql`, then every `down.sql`, ending with no table left behind) in-memory; throws on failure
|
|
67
68
|
- `close()` -- Close the database connection
|
|
68
69
|
|
|
69
70
|
### Create / Delete
|
|
@@ -77,24 +78,30 @@ bun run example.ts
|
|
|
77
78
|
### Read (bulk)
|
|
78
79
|
|
|
79
80
|
- `readScalarIntegers(collection, attribute)` -- Read all integer scalars
|
|
81
|
+
- `readScalarBooleans(collection, attribute)` -- Read INTEGER-backed boolean scalars
|
|
80
82
|
- `readScalarFloats(collection, attribute)` -- Read all float scalars
|
|
81
83
|
- `readScalarStrings(collection, attribute)` -- Read all string scalars
|
|
82
84
|
- `readVectorIntegers(collection, attribute)` -- Read all integer vectors
|
|
85
|
+
- `readVectorBooleans(collection, attribute)` -- Read INTEGER-backed boolean vectors
|
|
83
86
|
- `readVectorFloats(collection, attribute)` -- Read all float vectors
|
|
84
87
|
- `readVectorStrings(collection, attribute)` -- Read all string vectors
|
|
85
88
|
- `readSetIntegers(collection, attribute)` -- Read all integer sets
|
|
89
|
+
- `readSetBooleans(collection, attribute)` -- Read INTEGER-backed boolean sets
|
|
86
90
|
- `readSetFloats(collection, attribute)` -- Read all float sets
|
|
87
91
|
- `readSetStrings(collection, attribute)` -- Read all string sets
|
|
88
92
|
|
|
89
93
|
### Read (by ID)
|
|
90
94
|
|
|
91
95
|
- `readScalarIntegerById(collection, attribute, id)` -- Read integer or null
|
|
96
|
+
- `readScalarBooleanById(collection, attribute, id)` -- Read INTEGER-backed boolean or null
|
|
92
97
|
- `readScalarFloatById(collection, attribute, id)` -- Read float or null
|
|
93
98
|
- `readScalarStringById(collection, attribute, id)` -- Read string or null
|
|
94
99
|
- `readVectorIntegersById(collection, attribute, id)` -- Read integer vector
|
|
100
|
+
- `readVectorBooleansById(collection, attribute, id)` -- Read INTEGER-backed boolean vector
|
|
95
101
|
- `readVectorFloatsById(collection, attribute, id)` -- Read float vector
|
|
96
102
|
- `readVectorStringsById(collection, attribute, id)` -- Read string vector
|
|
97
103
|
- `readSetIntegersById(collection, attribute, id)` -- Read integer set
|
|
104
|
+
- `readSetBooleansById(collection, attribute, id)` -- Read INTEGER-backed boolean set
|
|
98
105
|
- `readSetFloatsById(collection, attribute, id)` -- Read float set
|
|
99
106
|
- `readSetStringsById(collection, attribute, id)` -- Read string set
|
|
100
107
|
|
|
@@ -123,9 +130,11 @@ bun run example.ts
|
|
|
123
130
|
|
|
124
131
|
- `queryString(sql, parameters?)` -- Query returning string or null
|
|
125
132
|
- `queryInteger(sql, parameters?)` -- Query returning integer or null
|
|
133
|
+
- `queryBoolean(sql, parameters?)` -- Query returning an INTEGER-backed boolean or null
|
|
126
134
|
- `queryFloat(sql, parameters?)` -- Query returning float or null
|
|
127
135
|
|
|
128
|
-
Parameters are passed as an array of `number | string | null
|
|
136
|
+
Parameters are passed as an array of `number | boolean | string | null` (a `boolean` binds as the
|
|
137
|
+
INTEGER 1 or 0).
|
|
129
138
|
|
|
130
139
|
### Transaction
|
|
131
140
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/src/boolean.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function integerToBoolean(value: number, collection?: string, attribute?: string): boolean;
|
|
2
|
+
export function integerToBoolean(value: null, collection?: string, attribute?: string): null;
|
|
3
|
+
export function integerToBoolean(
|
|
4
|
+
value: number | null,
|
|
5
|
+
collection?: string,
|
|
6
|
+
attribute?: string,
|
|
7
|
+
): boolean | null;
|
|
8
|
+
export function integerToBoolean(
|
|
9
|
+
value: number | null,
|
|
10
|
+
collection?: string,
|
|
11
|
+
attribute?: string,
|
|
12
|
+
): boolean | null {
|
|
13
|
+
if (value === null) return null;
|
|
14
|
+
if (value === 0) return false;
|
|
15
|
+
if (value === 1) return true;
|
|
16
|
+
const source = collection ? ` in '${collection}.${attribute}'` : "";
|
|
17
|
+
// A RangeError, not a QuiverError: the message is crafted here, not read from
|
|
18
|
+
// quiver_get_last_error — these readers are a binding-only convenience.
|
|
19
|
+
throw new RangeError(`Cannot convert integer ${value} to boolean${source}: expected 0 or 1`);
|
|
20
|
+
}
|
package/src/create.ts
CHANGED
|
@@ -35,6 +35,12 @@ function setElementArray(
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
if (typeof first === "boolean") {
|
|
39
|
+
const arr = allocNativeInt64((values as boolean[]).map((v) => (v ? 1 : 0)));
|
|
40
|
+
check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.buf, values.length, null));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
if (typeof first === "number") {
|
|
39
45
|
const allIntegers = (values as number[]).every((v) => Number.isInteger(v));
|
|
40
46
|
if (allIntegers) {
|
|
@@ -73,6 +79,11 @@ function setElementField(lib: Symbols, elemPtr: NativePointer, name: string, val
|
|
|
73
79
|
return;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
if (typeof value === "boolean") {
|
|
83
|
+
check(lib.quiver_element_set_integer(elemPtr, nameBuf.buf, value ? 1n : 0n));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
if (typeof value === "number") {
|
|
77
88
|
if (Number.isInteger(value)) {
|
|
78
89
|
check(lib.quiver_element_set_integer(elemPtr, nameBuf.buf, BigInt(value)));
|
|
@@ -175,6 +186,53 @@ Database.prototype.updateElementByLabel = function (
|
|
|
175
186
|
}
|
|
176
187
|
};
|
|
177
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Points one scalar foreign-key relation at the element labeled `targetLabel`; `null` clears it.
|
|
191
|
+
* The column is derived as `collectionTo.toLowerCase() + "_" + relationType`.
|
|
192
|
+
*/
|
|
193
|
+
Database.prototype.updateRelation = function (
|
|
194
|
+
this: Database,
|
|
195
|
+
collectionFrom: string,
|
|
196
|
+
collectionTo: string,
|
|
197
|
+
relationType: string,
|
|
198
|
+
id: number,
|
|
199
|
+
targetLabel: string | null,
|
|
200
|
+
): void {
|
|
201
|
+
const lib = getSymbols();
|
|
202
|
+
check(
|
|
203
|
+
lib.quiver_database_update_relation(
|
|
204
|
+
this._handle,
|
|
205
|
+
toCString(collectionFrom).buf,
|
|
206
|
+
toCString(collectionTo).buf,
|
|
207
|
+
toCString(relationType).buf,
|
|
208
|
+
BigInt(id),
|
|
209
|
+
targetLabel === null ? null : toCString(targetLabel).buf,
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** Label-addressed counterpart of updateRelation. */
|
|
215
|
+
Database.prototype.updateRelationByLabel = function (
|
|
216
|
+
this: Database,
|
|
217
|
+
collectionFrom: string,
|
|
218
|
+
collectionTo: string,
|
|
219
|
+
relationType: string,
|
|
220
|
+
label: string,
|
|
221
|
+
targetLabel: string | null,
|
|
222
|
+
): void {
|
|
223
|
+
const lib = getSymbols();
|
|
224
|
+
check(
|
|
225
|
+
lib.quiver_database_update_relation_by_label(
|
|
226
|
+
this._handle,
|
|
227
|
+
toCString(collectionFrom).buf,
|
|
228
|
+
toCString(collectionTo).buf,
|
|
229
|
+
toCString(relationType).buf,
|
|
230
|
+
toCString(label).buf,
|
|
231
|
+
targetLabel === null ? null : toCString(targetLabel).buf,
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
|
|
178
236
|
Database.prototype.deleteElement = function (this: Database, collection: string, id: number): void {
|
|
179
237
|
const lib = getSymbols();
|
|
180
238
|
const collBuf = toCString(collection);
|
package/src/database.ts
CHANGED
|
@@ -64,6 +64,17 @@ export class Database {
|
|
|
64
64
|
return new Database(readPtrOut(outDb));
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Applies every migration's up.sql in order, then every down.sql in reverse.
|
|
69
|
+
* Throws on failure, including a round trip that leaves any table behind.
|
|
70
|
+
*/
|
|
71
|
+
static validateMigrations(migrationsPath: string): void {
|
|
72
|
+
const lib = getSymbols();
|
|
73
|
+
const migrPathBuf = toCString(migrationsPath);
|
|
74
|
+
|
|
75
|
+
check(lib.quiver_database_validate_migrations(migrPathBuf.buf));
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
close(): void {
|
|
68
79
|
if (this._closed) return;
|
|
69
80
|
const lib = getSymbols();
|
|
@@ -89,9 +100,24 @@ export class Database {
|
|
|
89
100
|
declare updateElementByLabel: (collection: string, label: string, data: ElementData) => void;
|
|
90
101
|
declare deleteElement: (collection: string, id: number) => void;
|
|
91
102
|
declare deleteElementByLabel: (collection: string, label: string) => void;
|
|
103
|
+
declare updateRelation: (
|
|
104
|
+
collectionFrom: string,
|
|
105
|
+
collectionTo: string,
|
|
106
|
+
relationType: string,
|
|
107
|
+
id: number,
|
|
108
|
+
targetLabel: string | null,
|
|
109
|
+
) => void;
|
|
110
|
+
declare updateRelationByLabel: (
|
|
111
|
+
collectionFrom: string,
|
|
112
|
+
collectionTo: string,
|
|
113
|
+
relationType: string,
|
|
114
|
+
label: string,
|
|
115
|
+
targetLabel: string | null,
|
|
116
|
+
) => void;
|
|
92
117
|
|
|
93
118
|
// --- Reads (implemented in read.ts) ---
|
|
94
119
|
declare readScalarIntegers: (collection: string, attribute: string) => (number | null)[];
|
|
120
|
+
declare readScalarBooleans: (collection: string, attribute: string) => (boolean | null)[];
|
|
95
121
|
declare readScalarFloats: (collection: string, attribute: string) => (number | null)[];
|
|
96
122
|
declare readScalarStrings: (collection: string, attribute: string) => (string | null)[];
|
|
97
123
|
declare readScalarIntegerById: (
|
|
@@ -99,6 +125,11 @@ export class Database {
|
|
|
99
125
|
attribute: string,
|
|
100
126
|
id: number,
|
|
101
127
|
) => number | null;
|
|
128
|
+
declare readScalarBooleanById: (
|
|
129
|
+
collection: string,
|
|
130
|
+
attribute: string,
|
|
131
|
+
id: number,
|
|
132
|
+
) => boolean | null;
|
|
102
133
|
declare readScalarFloatById: (collection: string, attribute: string, id: number) => number | null;
|
|
103
134
|
declare readScalarStringById: (
|
|
104
135
|
collection: string,
|
|
@@ -108,21 +139,26 @@ export class Database {
|
|
|
108
139
|
declare readElementIds: (collection: string) => number[];
|
|
109
140
|
declare numberOfElements: (collection: string) => number;
|
|
110
141
|
declare readVectorIntegers: (collection: string, attribute: string) => number[][];
|
|
142
|
+
declare readVectorBooleans: (collection: string, attribute: string) => boolean[][];
|
|
111
143
|
declare readVectorFloats: (collection: string, attribute: string) => number[][];
|
|
112
144
|
declare readVectorStrings: (collection: string, attribute: string) => string[][];
|
|
113
145
|
declare readVectorIntegersById: (collection: string, attribute: string, id: number) => number[];
|
|
146
|
+
declare readVectorBooleansById: (collection: string, attribute: string, id: number) => boolean[];
|
|
114
147
|
declare readVectorFloatsById: (collection: string, attribute: string, id: number) => number[];
|
|
115
148
|
declare readVectorStringsById: (collection: string, attribute: string, id: number) => string[];
|
|
116
149
|
declare readSetIntegers: (collection: string, attribute: string) => number[][];
|
|
150
|
+
declare readSetBooleans: (collection: string, attribute: string) => boolean[][];
|
|
117
151
|
declare readSetFloats: (collection: string, attribute: string) => number[][];
|
|
118
152
|
declare readSetStrings: (collection: string, attribute: string) => string[][];
|
|
119
153
|
declare readSetIntegersById: (collection: string, attribute: string, id: number) => number[];
|
|
154
|
+
declare readSetBooleansById: (collection: string, attribute: string, id: number) => boolean[];
|
|
120
155
|
declare readSetFloatsById: (collection: string, attribute: string, id: number) => number[];
|
|
121
156
|
declare readSetStringsById: (collection: string, attribute: string, id: number) => string[];
|
|
122
157
|
|
|
123
158
|
// --- Queries (implemented in query.ts) ---
|
|
124
159
|
declare queryString: (sql: string, parameters?: QueryParam[]) => string | null;
|
|
125
160
|
declare queryInteger: (sql: string, parameters?: QueryParam[]) => number | null;
|
|
161
|
+
declare queryBoolean: (sql: string, parameters?: QueryParam[]) => boolean | null;
|
|
126
162
|
declare queryFloat: (sql: string, parameters?: QueryParam[]) => number | null;
|
|
127
163
|
|
|
128
164
|
// --- Transactions (implemented in transaction.ts) ---
|
package/src/loader.ts
CHANGED
|
@@ -35,6 +35,7 @@ const lifecycleSymbols = {
|
|
|
35
35
|
// struct in JS.
|
|
36
36
|
quiver_database_from_schema: { args: [BUF, BUF, BUF, P], returns: I32 },
|
|
37
37
|
quiver_database_from_migrations: { args: [BUF, BUF, BUF, P], returns: I32 },
|
|
38
|
+
quiver_database_validate_migrations: { args: [BUF], returns: I32 },
|
|
38
39
|
quiver_database_open: { args: [BUF, BUF, P], returns: I32 },
|
|
39
40
|
quiver_database_close: { args: [P], returns: I32 },
|
|
40
41
|
quiver_database_is_healthy: { args: [P, P], returns: I32 },
|
|
@@ -61,6 +62,8 @@ const crudSymbols = {
|
|
|
61
62
|
quiver_database_update_element_by_label: { args: [P, BUF, BUF, P], returns: I32 },
|
|
62
63
|
quiver_database_delete_element: { args: [P, BUF, I64], returns: I32 },
|
|
63
64
|
quiver_database_delete_element_by_label: { args: [P, BUF, BUF], returns: I32 },
|
|
65
|
+
quiver_database_update_relation: { args: [P, BUF, BUF, BUF, I64, BUF], returns: I32 },
|
|
66
|
+
quiver_database_update_relation_by_label: { args: [P, BUF, BUF, BUF, BUF, BUF], returns: I32 },
|
|
64
67
|
} as const;
|
|
65
68
|
|
|
66
69
|
const readSymbols = {
|
package/src/lua-api.ts
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
// and whether the prose is semantically true.
|
|
11
11
|
//
|
|
12
12
|
// NOTE: the binary/expression subsystems are bound in the native binding and documented below.
|
|
13
|
-
// File-touching operations (db:open_file, db:bin_to_csv, db:csv_to_bin,
|
|
14
|
-
// to the database file's directory; the pure-metadata builders stay under
|
|
13
|
+
// File-touching operations (db:open_file, db:bin_to_csv, db:csv_to_bin, db:validate_migrations,
|
|
14
|
+
// expr:save) are sandboxed to the database file's directory; the pure-metadata builders stay under
|
|
15
|
+
// the quiver.* global.
|
|
15
16
|
//
|
|
16
17
|
// FORMAT CONVENTION: every db: method appears at least once as the literal token
|
|
17
18
|
// `db:<snake_case_name>`, and every quiver.* function as `quiver.<name>`, so coverage is greppable
|
|
@@ -50,7 +51,7 @@ Lua values map to Quiver column values as follows:
|
|
|
50
51
|
| integer | INTEGER | Also accepted for REAL columns (coerced to real). |
|
|
51
52
|
| number (float) | REAL | A float is rejected for an INTEGER column. |
|
|
52
53
|
| string | TEXT | Also used for \`date_time\` columns (ISO 8601). |
|
|
53
|
-
| \`nil\` | NULL | In query params, file paths,
|
|
54
|
+
| \`nil\` | NULL | In query params, file paths, ts rows, relations.|
|
|
54
55
|
| table (1-indexed) | array | Used for vectors/sets and column-oriented data.|
|
|
55
56
|
|
|
56
57
|
**Unsupported types throw.** Passing a boolean, a function, or a nested table where a scalar is
|
|
@@ -59,15 +60,21 @@ attributes, time-series rows, and query parameters. A skipped positional query p
|
|
|
59
60
|
shift every later parameter and bind NULL to the trailing placeholder, so this is rejected loudly.
|
|
60
61
|
|
|
61
62
|
Dates are plain strings in ISO 8601 format: \`YYYY-MM-DDTHH:MM:SS\`. (Lua keeps a string-based
|
|
62
|
-
datetime surface — there are no DateTime wrapper helpers, unlike Julia/Dart/Python.)
|
|
63
|
+
datetime surface — there are no DateTime wrapper helpers, unlike Julia/Dart/Python.) The time part
|
|
64
|
+
is optional, so \`"2024-01-15"\` is also valid, and a space may replace the \`T\`. Every field is
|
|
65
|
+
fixed-width and zero-padded. Anything shorter or malformed — \`"2005"\`, \`"2005-01"\`,
|
|
66
|
+
\`"2024-02-31"\`, \`"2024-1-5"\`, \`"2024-01-15T10:30"\` — is **rejected when you write it**, not
|
|
67
|
+
silently stored. The value is stored exactly as written; a date-only value is not padded to
|
|
68
|
+
midnight.
|
|
63
69
|
|
|
64
70
|
---
|
|
65
71
|
|
|
66
72
|
## Critical rules
|
|
67
73
|
|
|
68
74
|
- **Type coercion.** An integer is accepted for a REAL column (coerced to real on insert); a float
|
|
69
|
-
is rejected for an INTEGER column.
|
|
70
|
-
|
|
75
|
+
is rejected for an INTEGER column. A string bound to a \`date_*\` column must parse as ISO 8601
|
|
76
|
+
(\`YYYY-MM-DD\`, optionally \`THH:MM:SS\` or \` HH:MM:SS\`). Other type mismatches raise a
|
|
77
|
+
validation error and roll the whole script back.
|
|
71
78
|
- **Errors abort the script.** Any error thrown by a \`db:\` call stops the script and surfaces as
|
|
72
79
|
\`Failed to run Lua script: <message>\`. Validation failures roll back whatever the current
|
|
73
80
|
transaction covered.
|
|
@@ -76,16 +83,16 @@ datetime surface — there are no DateTime wrapper helpers, unlike Julia/Dart/Py
|
|
|
76
83
|
and \`dofile\`/\`loadfile\` are removed (string-form \`load\` stays available). Integer division is
|
|
77
84
|
the Lua 5.4 \`//\` operator — a language operator, unrelated to \`math\`.
|
|
78
85
|
- **Filesystem sandbox.** Every file-touching operation (\`db:export_csv\`, \`db:import_csv\`,
|
|
79
|
-
\`db:open_file\`, \`db:bin_to_csv\`, \`db:csv_to_bin\`, \`expr:save\`) resolves
|
|
80
|
-
the directory containing the database file and rejects anything outside it
|
|
81
|
-
fine; \`..\` escapes and outside absolute paths throw \`Cannot <op>: path '...' escapes the
|
|
86
|
+
\`db:open_file\`, \`db:bin_to_csv\`, \`db:csv_to_bin\`, \`db:validate_migrations\`, \`expr:save\`) resolves
|
|
87
|
+
relative paths against the directory containing the database file and rejects anything outside it
|
|
88
|
+
(subdirectories are fine; \`..\` escapes and outside absolute paths throw \`Cannot <op>: path '...' escapes the
|
|
82
89
|
database directory ...\`). On an in-memory database these operations throw
|
|
83
90
|
\`Cannot <op>: database is in-memory, file operations are unavailable\`.
|
|
84
91
|
- **Output.** A script can \`return\` one value and the host receives it as JSON — prefer this over
|
|
85
92
|
\`print()\` when you need structured data back (\`print()\` still works and is captured). Only the
|
|
86
93
|
**first** returned value is encoded. Arrays are 1-indexed (iterate with \`ipairs\`); reading a NULL
|
|
87
94
|
yields \`nil\`, writing \`nil\` stores NULL where NULL is accepted (query params, ts rows, file
|
|
88
|
-
columns — but NOT element scalar attributes; see CRUD).
|
|
95
|
+
columns, relation targets — but NOT element scalar attributes; see CRUD).
|
|
89
96
|
|
|
90
97
|
\`\`\`lua
|
|
91
98
|
return { ids = db:read_element_ids("Collection"), total = 3 }
|
|
@@ -134,10 +141,15 @@ db:describe() -- string: whole-DB text report (returns it,
|
|
|
134
141
|
db:describe_collection(collection) -- string: one collection's structure (text report)
|
|
135
142
|
db:summarize_collection(collection)-- string: per-scalar null/non-null counts, low-cardinality
|
|
136
143
|
-- integer value distributions, per-group sizes
|
|
144
|
+
db:validate_migrations(path) -- validate a migrations dir (up then down) in-memory; no return
|
|
137
145
|
\`\`\`
|
|
138
146
|
|
|
139
147
|
All three \`describe*\`/\`summarize*\` methods **return** a string — \`print()\` it to see it.
|
|
140
148
|
|
|
149
|
+
\`db:validate_migrations(path)\` applies every \`up.sql\` in version order, then every \`down.sql\` in
|
|
150
|
+
reverse, against a throwaway in-memory database — nothing in \`db\` itself is touched. The round trip
|
|
151
|
+
must end with an empty database; leftover tables are named in the error.
|
|
152
|
+
|
|
141
153
|
---
|
|
142
154
|
|
|
143
155
|
## Transactions
|
|
@@ -215,6 +227,9 @@ db:update_element(collection, id, element_table)
|
|
|
215
227
|
db:update_element_by_label(collection, label, element_table) -- same update, addressed by label
|
|
216
228
|
db:delete_element(collection, id)
|
|
217
229
|
db:delete_element_by_label(collection, label) -- same delete, addressed by label
|
|
230
|
+
|
|
231
|
+
db:update_relation(collection_from, collection_to, relation_type, id, target_label)
|
|
232
|
+
db:update_relation_by_label(collection_from, collection_to, relation_type, label, target_label)
|
|
218
233
|
\`\`\`
|
|
219
234
|
|
|
220
235
|
The element table holds scalar attributes as \`key = value\`, and vector/set attributes as
|
|
@@ -254,7 +269,18 @@ Notes:
|
|
|
254
269
|
**throws** (\`...must have at least one scalar attribute\` on create, \`...at least one attribute
|
|
255
270
|
to update\` on update). To leave a column unchanged, omit the key — you cannot set a scalar to
|
|
256
271
|
NULL via the element table. (\`nil\` → NULL is only accepted by
|
|
257
|
-
\`upsert_time_series_row\` and \`
|
|
272
|
+
\`upsert_time_series_row\`, \`update_time_series_files\` and \`update_relation\`.)
|
|
273
|
+
- **\`update_relation\` points one scalar foreign-key relation at another element**, named by the
|
|
274
|
+
target's label. The column is derived from the naming convention —
|
|
275
|
+
\`lowercase(collection_to) .. "_" .. relation_type\`, so
|
|
276
|
+
\`db:update_relation("Child", "Parent", "id", id, "Parent A")\` writes \`Child.parent_id\`. A
|
|
277
|
+
\`nil\` or omitted \`target_label\` clears the relation; anything that is not a string throws
|
|
278
|
+
(\`target_label has unsupported Lua type\`). The derived column must exist and be a
|
|
279
|
+
foreign key to \`collection_to\`, otherwise \`Cannot update_relation: ...\`. The write delegates
|
|
280
|
+
to \`update_element\`, so a missing id reports that method's error;
|
|
281
|
+
\`update_relation_by_label\` takes a label in place of the id, with
|
|
282
|
+
\`update_element_by_label\`'s resolution and miss semantics. A relation living in a vector, set
|
|
283
|
+
or time-series group is a list of targets — use that group's writer instead.
|
|
258
284
|
|
|
259
285
|
---
|
|
260
286
|
|
package/src/query.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ptr } from "bun:ffi";
|
|
2
|
+
import { integerToBoolean } from "./boolean.ts";
|
|
2
3
|
import { Database } from "./database.ts";
|
|
3
4
|
import { check, QuiverError } from "./errors.ts";
|
|
4
5
|
import {
|
|
@@ -38,6 +39,11 @@ function marshalParams(parameters: QueryParam[]): {
|
|
|
38
39
|
if (p === null) {
|
|
39
40
|
typesDv.setInt32(i * 4, DATA_TYPE_NULL, true);
|
|
40
41
|
valuesDv.setBigInt64(i * 8, 0n, true);
|
|
42
|
+
} else if (typeof p === "boolean") {
|
|
43
|
+
typesDv.setInt32(i * 4, DATA_TYPE_INTEGER, true);
|
|
44
|
+
const native = allocNativeInt64([p ? 1 : 0]);
|
|
45
|
+
keepalive.push(native);
|
|
46
|
+
valuesDv.setBigInt64(i * 8, nativeAddress(native.ptr), true);
|
|
41
47
|
} else if (typeof p === "number") {
|
|
42
48
|
if (Number.isInteger(p)) {
|
|
43
49
|
typesDv.setInt32(i * 4, DATA_TYPE_INTEGER, true);
|
|
@@ -130,6 +136,14 @@ Database.prototype.queryInteger = function (
|
|
|
130
136
|
return Number(new DataView(outValue.buffer).getBigInt64(0, true));
|
|
131
137
|
};
|
|
132
138
|
|
|
139
|
+
Database.prototype.queryBoolean = function (
|
|
140
|
+
this: Database,
|
|
141
|
+
sql: string,
|
|
142
|
+
parameters?: QueryParam[],
|
|
143
|
+
): boolean | null {
|
|
144
|
+
return integerToBoolean(this.queryInteger(sql, parameters));
|
|
145
|
+
};
|
|
146
|
+
|
|
133
147
|
Database.prototype.queryFloat = function (
|
|
134
148
|
this: Database,
|
|
135
149
|
sql: string,
|
package/src/read.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CString, type Pointer, toArrayBuffer } from "bun:ffi";
|
|
2
|
+
import { integerToBoolean } from "./boolean.ts";
|
|
2
3
|
import { Database } from "./database.ts";
|
|
3
4
|
import { check } from "./errors.ts";
|
|
4
5
|
import {
|
|
@@ -52,6 +53,16 @@ Database.prototype.readScalarIntegers = function (
|
|
|
52
53
|
return result;
|
|
53
54
|
};
|
|
54
55
|
|
|
56
|
+
Database.prototype.readScalarBooleans = function (
|
|
57
|
+
this: Database,
|
|
58
|
+
collection: string,
|
|
59
|
+
attribute: string,
|
|
60
|
+
): (boolean | null)[] {
|
|
61
|
+
return this.readScalarIntegers(collection, attribute).map((value) =>
|
|
62
|
+
integerToBoolean(value, collection, attribute),
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
55
66
|
Database.prototype.readScalarFloats = function (
|
|
56
67
|
this: Database,
|
|
57
68
|
collection: string,
|
|
@@ -143,6 +154,19 @@ Database.prototype.readScalarIntegerById = function (
|
|
|
143
154
|
return Number(new DataView(outValBuf.buffer).getBigInt64(0, true));
|
|
144
155
|
};
|
|
145
156
|
|
|
157
|
+
Database.prototype.readScalarBooleanById = function (
|
|
158
|
+
this: Database,
|
|
159
|
+
collection: string,
|
|
160
|
+
attribute: string,
|
|
161
|
+
id: number,
|
|
162
|
+
): boolean | null {
|
|
163
|
+
return integerToBoolean(
|
|
164
|
+
this.readScalarIntegerById(collection, attribute, id),
|
|
165
|
+
collection,
|
|
166
|
+
attribute,
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
146
170
|
Database.prototype.readScalarFloatById = function (
|
|
147
171
|
this: Database,
|
|
148
172
|
collection: string,
|
|
@@ -342,6 +366,19 @@ Database.prototype.readVectorIntegers = function (
|
|
|
342
366
|
attribute,
|
|
343
367
|
);
|
|
344
368
|
};
|
|
369
|
+
/**
|
|
370
|
+
* NULL cells are dropped and only elements that own rows are returned, so the result is not
|
|
371
|
+
* positionally aligned with `readElementIds` (unlike `readScalarBooleans`).
|
|
372
|
+
*/
|
|
373
|
+
Database.prototype.readVectorBooleans = function (
|
|
374
|
+
this: Database,
|
|
375
|
+
collection: string,
|
|
376
|
+
attribute: string,
|
|
377
|
+
): boolean[][] {
|
|
378
|
+
return this.readVectorIntegers(collection, attribute).map((values) =>
|
|
379
|
+
values.map((value) => integerToBoolean(value, collection, attribute)),
|
|
380
|
+
);
|
|
381
|
+
};
|
|
345
382
|
Database.prototype.readVectorFloats = function (
|
|
346
383
|
this: Database,
|
|
347
384
|
collection: string,
|
|
@@ -381,6 +418,16 @@ Database.prototype.readSetIntegers = function (
|
|
|
381
418
|
attribute,
|
|
382
419
|
);
|
|
383
420
|
};
|
|
421
|
+
/** Same alignment caveat as `readVectorBooleans`: NULL cells dropped, only ids that own rows. */
|
|
422
|
+
Database.prototype.readSetBooleans = function (
|
|
423
|
+
this: Database,
|
|
424
|
+
collection: string,
|
|
425
|
+
attribute: string,
|
|
426
|
+
): boolean[][] {
|
|
427
|
+
return this.readSetIntegers(collection, attribute).map((values) =>
|
|
428
|
+
values.map((value) => integerToBoolean(value, collection, attribute)),
|
|
429
|
+
);
|
|
430
|
+
};
|
|
384
431
|
Database.prototype.readSetFloats = function (
|
|
385
432
|
this: Database,
|
|
386
433
|
collection: string,
|
|
@@ -515,6 +562,16 @@ Database.prototype.readVectorIntegersById = function (
|
|
|
515
562
|
id,
|
|
516
563
|
);
|
|
517
564
|
};
|
|
565
|
+
Database.prototype.readVectorBooleansById = function (
|
|
566
|
+
this: Database,
|
|
567
|
+
collection: string,
|
|
568
|
+
attribute: string,
|
|
569
|
+
id: number,
|
|
570
|
+
): boolean[] {
|
|
571
|
+
return this.readVectorIntegersById(collection, attribute, id).map((value) =>
|
|
572
|
+
integerToBoolean(value, collection, attribute),
|
|
573
|
+
);
|
|
574
|
+
};
|
|
518
575
|
Database.prototype.readVectorFloatsById = function (
|
|
519
576
|
this: Database,
|
|
520
577
|
collection: string,
|
|
@@ -560,6 +617,16 @@ Database.prototype.readSetIntegersById = function (
|
|
|
560
617
|
id,
|
|
561
618
|
);
|
|
562
619
|
};
|
|
620
|
+
Database.prototype.readSetBooleansById = function (
|
|
621
|
+
this: Database,
|
|
622
|
+
collection: string,
|
|
623
|
+
attribute: string,
|
|
624
|
+
id: number,
|
|
625
|
+
): boolean[] {
|
|
626
|
+
return this.readSetIntegersById(collection, attribute, id).map((value) =>
|
|
627
|
+
integerToBoolean(value, collection, attribute),
|
|
628
|
+
);
|
|
629
|
+
};
|
|
563
630
|
Database.prototype.readSetFloatsById = function (
|
|
564
631
|
this: Database,
|
|
565
632
|
collection: string,
|
package/src/types.ts
CHANGED
|
@@ -21,11 +21,12 @@ export type DatabaseOptions = {
|
|
|
21
21
|
consoleLevel?: number;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
export type
|
|
24
|
+
/** A `boolean` is stored as the INTEGER 1 or 0 (see `readScalarBooleans` for the read side). */
|
|
25
|
+
export type ScalarValue = number | bigint | boolean | string | null;
|
|
26
|
+
export type ArrayValue = number[] | bigint[] | boolean[] | string[];
|
|
26
27
|
export type Value = ScalarValue | ArrayValue;
|
|
27
28
|
export type ElementData = Record<string, Value | undefined>;
|
|
28
|
-
export type QueryParam = number | string | null;
|
|
29
|
+
export type QueryParam = number | boolean | string | null;
|
|
29
30
|
|
|
30
31
|
/** Native memory allocation result. Callers MUST hold `buf` in scope to prevent GC. */
|
|
31
32
|
export type Allocation = {
|