quiverdb 0.10.1 → 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 +12 -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 +135 -0
- package/src/database.ts +62 -0
- package/src/group-columns.ts +9 -7
- package/src/loader.ts +21 -0
- package/src/lua-api.ts +63 -12
- package/src/query.ts +14 -0
- package/src/read.ts +67 -0
- package/src/time-series.ts +83 -7
- 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
|
|
@@ -71,28 +72,36 @@ bun run example.ts
|
|
|
71
72
|
- `createElement(collection, data)` -- Create element, returns numeric ID
|
|
72
73
|
- `updateElement(collection, id, data)` -- Update element by ID
|
|
73
74
|
- `deleteElement(collection, id)` -- Delete element by ID
|
|
75
|
+
- `updateElementByLabel(collection, label, data)` -- Update element by label
|
|
76
|
+
- `deleteElementByLabel(collection, label)` -- Delete element by label
|
|
74
77
|
|
|
75
78
|
### Read (bulk)
|
|
76
79
|
|
|
77
80
|
- `readScalarIntegers(collection, attribute)` -- Read all integer scalars
|
|
81
|
+
- `readScalarBooleans(collection, attribute)` -- Read INTEGER-backed boolean scalars
|
|
78
82
|
- `readScalarFloats(collection, attribute)` -- Read all float scalars
|
|
79
83
|
- `readScalarStrings(collection, attribute)` -- Read all string scalars
|
|
80
84
|
- `readVectorIntegers(collection, attribute)` -- Read all integer vectors
|
|
85
|
+
- `readVectorBooleans(collection, attribute)` -- Read INTEGER-backed boolean vectors
|
|
81
86
|
- `readVectorFloats(collection, attribute)` -- Read all float vectors
|
|
82
87
|
- `readVectorStrings(collection, attribute)` -- Read all string vectors
|
|
83
88
|
- `readSetIntegers(collection, attribute)` -- Read all integer sets
|
|
89
|
+
- `readSetBooleans(collection, attribute)` -- Read INTEGER-backed boolean sets
|
|
84
90
|
- `readSetFloats(collection, attribute)` -- Read all float sets
|
|
85
91
|
- `readSetStrings(collection, attribute)` -- Read all string sets
|
|
86
92
|
|
|
87
93
|
### Read (by ID)
|
|
88
94
|
|
|
89
95
|
- `readScalarIntegerById(collection, attribute, id)` -- Read integer or null
|
|
96
|
+
- `readScalarBooleanById(collection, attribute, id)` -- Read INTEGER-backed boolean or null
|
|
90
97
|
- `readScalarFloatById(collection, attribute, id)` -- Read float or null
|
|
91
98
|
- `readScalarStringById(collection, attribute, id)` -- Read string or null
|
|
92
99
|
- `readVectorIntegersById(collection, attribute, id)` -- Read integer vector
|
|
100
|
+
- `readVectorBooleansById(collection, attribute, id)` -- Read INTEGER-backed boolean vector
|
|
93
101
|
- `readVectorFloatsById(collection, attribute, id)` -- Read float vector
|
|
94
102
|
- `readVectorStringsById(collection, attribute, id)` -- Read string vector
|
|
95
103
|
- `readSetIntegersById(collection, attribute, id)` -- Read integer set
|
|
104
|
+
- `readSetBooleansById(collection, attribute, id)` -- Read INTEGER-backed boolean set
|
|
96
105
|
- `readSetFloatsById(collection, attribute, id)` -- Read float set
|
|
97
106
|
- `readSetStringsById(collection, attribute, id)` -- Read string set
|
|
98
107
|
|
|
@@ -121,9 +130,11 @@ bun run example.ts
|
|
|
121
130
|
|
|
122
131
|
- `queryString(sql, parameters?)` -- Query returning string or null
|
|
123
132
|
- `queryInteger(sql, parameters?)` -- Query returning integer or null
|
|
133
|
+
- `queryBoolean(sql, parameters?)` -- Query returning an INTEGER-backed boolean or null
|
|
124
134
|
- `queryFloat(sql, parameters?)` -- Query returning float or null
|
|
125
135
|
|
|
126
|
-
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).
|
|
127
138
|
|
|
128
139
|
### Transaction
|
|
129
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)));
|
|
@@ -148,12 +159,98 @@ Database.prototype.updateElement = function (
|
|
|
148
159
|
}
|
|
149
160
|
};
|
|
150
161
|
|
|
162
|
+
/** Label-addressed counterpart of updateElement. */
|
|
163
|
+
Database.prototype.updateElementByLabel = function (
|
|
164
|
+
this: Database,
|
|
165
|
+
collection: string,
|
|
166
|
+
label: string,
|
|
167
|
+
data: ElementData,
|
|
168
|
+
): void {
|
|
169
|
+
const lib = getSymbols();
|
|
170
|
+
const handle = this._handle;
|
|
171
|
+
|
|
172
|
+
const outElem = allocPtrOut();
|
|
173
|
+
check(lib.quiver_element_create(outElem.buf));
|
|
174
|
+
const elemPtr = readPtrOut(outElem);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
for (const [key, value] of Object.entries(data)) {
|
|
178
|
+
if (value === undefined) continue;
|
|
179
|
+
setElementField(lib, elemPtr, key, value);
|
|
180
|
+
}
|
|
181
|
+
const collBuf = toCString(collection);
|
|
182
|
+
const labelBuf = toCString(label);
|
|
183
|
+
check(lib.quiver_database_update_element_by_label(handle, collBuf.buf, labelBuf.buf, elemPtr));
|
|
184
|
+
} finally {
|
|
185
|
+
lib.quiver_element_destroy(elemPtr);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
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
|
+
|
|
151
236
|
Database.prototype.deleteElement = function (this: Database, collection: string, id: number): void {
|
|
152
237
|
const lib = getSymbols();
|
|
153
238
|
const collBuf = toCString(collection);
|
|
154
239
|
check(lib.quiver_database_delete_element(this._handle, collBuf.buf, BigInt(id)));
|
|
155
240
|
};
|
|
156
241
|
|
|
242
|
+
/** Label-addressed counterpart of deleteElement. */
|
|
243
|
+
Database.prototype.deleteElementByLabel = function (
|
|
244
|
+
this: Database,
|
|
245
|
+
collection: string,
|
|
246
|
+
label: string,
|
|
247
|
+
): void {
|
|
248
|
+
const lib = getSymbols();
|
|
249
|
+
const collBuf = toCString(collection);
|
|
250
|
+
const labelBuf = toCString(label);
|
|
251
|
+
check(lib.quiver_database_delete_element_by_label(this._handle, collBuf.buf, labelBuf.buf));
|
|
252
|
+
};
|
|
253
|
+
|
|
157
254
|
/**
|
|
158
255
|
* Replace all of an element's rows in one *named* vector group, from column arrays keyed by name.
|
|
159
256
|
*
|
|
@@ -180,6 +277,25 @@ Database.prototype.updateVectorGroup = function (
|
|
|
180
277
|
);
|
|
181
278
|
};
|
|
182
279
|
|
|
280
|
+
/** Label-addressed counterpart of updateVectorGroup. */
|
|
281
|
+
Database.prototype.updateVectorGroupByLabel = function (
|
|
282
|
+
this: Database,
|
|
283
|
+
collection: string,
|
|
284
|
+
group: string,
|
|
285
|
+
label: string,
|
|
286
|
+
data: GroupColumns,
|
|
287
|
+
): void {
|
|
288
|
+
updateGroupColumns(
|
|
289
|
+
this._handle,
|
|
290
|
+
"updateVectorGroupByLabel",
|
|
291
|
+
getSymbols().quiver_database_update_vector_group_by_label,
|
|
292
|
+
collection,
|
|
293
|
+
group,
|
|
294
|
+
label,
|
|
295
|
+
data,
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
|
|
183
299
|
/** Set-group counterpart of updateVectorGroup. */
|
|
184
300
|
Database.prototype.updateSetGroup = function (
|
|
185
301
|
this: Database,
|
|
@@ -198,3 +314,22 @@ Database.prototype.updateSetGroup = function (
|
|
|
198
314
|
data,
|
|
199
315
|
);
|
|
200
316
|
};
|
|
317
|
+
|
|
318
|
+
/** Label-addressed counterpart of updateSetGroup. */
|
|
319
|
+
Database.prototype.updateSetGroupByLabel = function (
|
|
320
|
+
this: Database,
|
|
321
|
+
collection: string,
|
|
322
|
+
group: string,
|
|
323
|
+
label: string,
|
|
324
|
+
data: GroupColumns,
|
|
325
|
+
): void {
|
|
326
|
+
updateGroupColumns(
|
|
327
|
+
this._handle,
|
|
328
|
+
"updateSetGroupByLabel",
|
|
329
|
+
getSymbols().quiver_database_update_set_group_by_label,
|
|
330
|
+
collection,
|
|
331
|
+
group,
|
|
332
|
+
label,
|
|
333
|
+
data,
|
|
334
|
+
);
|
|
335
|
+
};
|
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();
|
|
@@ -86,10 +97,27 @@ export class Database {
|
|
|
86
97
|
// --- Element CRUD (implemented in create.ts) ---
|
|
87
98
|
declare createElement: (collection: string, data: ElementData) => number;
|
|
88
99
|
declare updateElement: (collection: string, id: number, data: ElementData) => void;
|
|
100
|
+
declare updateElementByLabel: (collection: string, label: string, data: ElementData) => void;
|
|
89
101
|
declare deleteElement: (collection: string, id: number) => void;
|
|
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;
|
|
90
117
|
|
|
91
118
|
// --- Reads (implemented in read.ts) ---
|
|
92
119
|
declare readScalarIntegers: (collection: string, attribute: string) => (number | null)[];
|
|
120
|
+
declare readScalarBooleans: (collection: string, attribute: string) => (boolean | null)[];
|
|
93
121
|
declare readScalarFloats: (collection: string, attribute: string) => (number | null)[];
|
|
94
122
|
declare readScalarStrings: (collection: string, attribute: string) => (string | null)[];
|
|
95
123
|
declare readScalarIntegerById: (
|
|
@@ -97,6 +125,11 @@ export class Database {
|
|
|
97
125
|
attribute: string,
|
|
98
126
|
id: number,
|
|
99
127
|
) => number | null;
|
|
128
|
+
declare readScalarBooleanById: (
|
|
129
|
+
collection: string,
|
|
130
|
+
attribute: string,
|
|
131
|
+
id: number,
|
|
132
|
+
) => boolean | null;
|
|
100
133
|
declare readScalarFloatById: (collection: string, attribute: string, id: number) => number | null;
|
|
101
134
|
declare readScalarStringById: (
|
|
102
135
|
collection: string,
|
|
@@ -106,21 +139,26 @@ export class Database {
|
|
|
106
139
|
declare readElementIds: (collection: string) => number[];
|
|
107
140
|
declare numberOfElements: (collection: string) => number;
|
|
108
141
|
declare readVectorIntegers: (collection: string, attribute: string) => number[][];
|
|
142
|
+
declare readVectorBooleans: (collection: string, attribute: string) => boolean[][];
|
|
109
143
|
declare readVectorFloats: (collection: string, attribute: string) => number[][];
|
|
110
144
|
declare readVectorStrings: (collection: string, attribute: string) => string[][];
|
|
111
145
|
declare readVectorIntegersById: (collection: string, attribute: string, id: number) => number[];
|
|
146
|
+
declare readVectorBooleansById: (collection: string, attribute: string, id: number) => boolean[];
|
|
112
147
|
declare readVectorFloatsById: (collection: string, attribute: string, id: number) => number[];
|
|
113
148
|
declare readVectorStringsById: (collection: string, attribute: string, id: number) => string[];
|
|
114
149
|
declare readSetIntegers: (collection: string, attribute: string) => number[][];
|
|
150
|
+
declare readSetBooleans: (collection: string, attribute: string) => boolean[][];
|
|
115
151
|
declare readSetFloats: (collection: string, attribute: string) => number[][];
|
|
116
152
|
declare readSetStrings: (collection: string, attribute: string) => string[][];
|
|
117
153
|
declare readSetIntegersById: (collection: string, attribute: string, id: number) => number[];
|
|
154
|
+
declare readSetBooleansById: (collection: string, attribute: string, id: number) => boolean[];
|
|
118
155
|
declare readSetFloatsById: (collection: string, attribute: string, id: number) => number[];
|
|
119
156
|
declare readSetStringsById: (collection: string, attribute: string, id: number) => string[];
|
|
120
157
|
|
|
121
158
|
// --- Queries (implemented in query.ts) ---
|
|
122
159
|
declare queryString: (sql: string, parameters?: QueryParam[]) => string | null;
|
|
123
160
|
declare queryInteger: (sql: string, parameters?: QueryParam[]) => number | null;
|
|
161
|
+
declare queryBoolean: (sql: string, parameters?: QueryParam[]) => boolean | null;
|
|
124
162
|
declare queryFloat: (sql: string, parameters?: QueryParam[]) => number | null;
|
|
125
163
|
|
|
126
164
|
// --- Transactions (implemented in transaction.ts) ---
|
|
@@ -156,24 +194,48 @@ export class Database {
|
|
|
156
194
|
id: number,
|
|
157
195
|
data: TimeSeriesData,
|
|
158
196
|
) => void;
|
|
197
|
+
declare updateTimeSeriesGroupByLabel: (
|
|
198
|
+
collection: string,
|
|
199
|
+
group: string,
|
|
200
|
+
label: string,
|
|
201
|
+
data: TimeSeriesData,
|
|
202
|
+
) => void;
|
|
159
203
|
declare updateVectorGroup: (
|
|
160
204
|
collection: string,
|
|
161
205
|
group: string,
|
|
162
206
|
id: number,
|
|
163
207
|
data: GroupColumns,
|
|
164
208
|
) => void;
|
|
209
|
+
declare updateVectorGroupByLabel: (
|
|
210
|
+
collection: string,
|
|
211
|
+
group: string,
|
|
212
|
+
label: string,
|
|
213
|
+
data: GroupColumns,
|
|
214
|
+
) => void;
|
|
165
215
|
declare updateSetGroup: (
|
|
166
216
|
collection: string,
|
|
167
217
|
group: string,
|
|
168
218
|
id: number,
|
|
169
219
|
data: GroupColumns,
|
|
170
220
|
) => void;
|
|
221
|
+
declare updateSetGroupByLabel: (
|
|
222
|
+
collection: string,
|
|
223
|
+
group: string,
|
|
224
|
+
label: string,
|
|
225
|
+
data: GroupColumns,
|
|
226
|
+
) => void;
|
|
171
227
|
declare upsertTimeSeriesRow: (
|
|
172
228
|
collection: string,
|
|
173
229
|
group: string,
|
|
174
230
|
id: number,
|
|
175
231
|
row: Record<string, number | bigint | string>,
|
|
176
232
|
) => void;
|
|
233
|
+
declare upsertTimeSeriesRowByLabel: (
|
|
234
|
+
collection: string,
|
|
235
|
+
group: string,
|
|
236
|
+
label: string,
|
|
237
|
+
row: Record<string, number | bigint | string>,
|
|
238
|
+
) => void;
|
|
177
239
|
declare hasTimeSeriesFiles: (collection: string) => boolean;
|
|
178
240
|
declare listTimeSeriesFilesColumns: (collection: string) => string[];
|
|
179
241
|
declare readTimeSeriesFiles: (collection: string) => Record<string, string | null>;
|
package/src/group-columns.ts
CHANGED
|
@@ -15,13 +15,14 @@ export type GroupColumns = Record<string, (number | string | null)[]>;
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The parallel-array signature every columnar group update C function shares
|
|
18
|
-
* (quiver_database_update_{time_series,vector,set}_group).
|
|
18
|
+
* (quiver_database_update_{time_series,vector,set}_group and their _by_label forms). The 4th
|
|
19
|
+
* argument addresses the element: an id for the by-id forms, a NUL-terminated label otherwise.
|
|
19
20
|
*/
|
|
20
21
|
type ColumnUpdateFn = (
|
|
21
22
|
db: NativePointer,
|
|
22
23
|
collection: Uint8Array,
|
|
23
24
|
group: Uint8Array,
|
|
24
|
-
|
|
25
|
+
key: bigint | Uint8Array,
|
|
25
26
|
names: Uint8Array | null,
|
|
26
27
|
types: Uint8Array | null,
|
|
27
28
|
data: Uint8Array | null,
|
|
@@ -32,8 +33,8 @@ type ColumnUpdateFn = (
|
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Marshal a column-oriented payload and forward it to one of the columnar group update C
|
|
35
|
-
* functions. Shared by updateTimeSeriesGroup / updateVectorGroup / updateSetGroup
|
|
36
|
-
* differ only in which C entry point they call.
|
|
36
|
+
* functions. Shared by updateTimeSeriesGroup / updateVectorGroup / updateSetGroup and their
|
|
37
|
+
* _by_label counterparts: they differ only in which C entry point they call.
|
|
37
38
|
*
|
|
38
39
|
* Pass `{}` (no columns) to clear the group.
|
|
39
40
|
*/
|
|
@@ -43,15 +44,16 @@ export function updateGroupColumns(
|
|
|
43
44
|
update: ColumnUpdateFn,
|
|
44
45
|
collection: string,
|
|
45
46
|
group: string,
|
|
46
|
-
|
|
47
|
+
key: number | string,
|
|
47
48
|
data: GroupColumns,
|
|
48
49
|
): void {
|
|
49
50
|
const collBuf = toCString(collection);
|
|
50
51
|
const grpBuf = toCString(group);
|
|
52
|
+
const keyArg = typeof key === "string" ? toCString(key).buf : BigInt(key);
|
|
51
53
|
const entries = Object.entries(data);
|
|
52
54
|
|
|
53
55
|
if (entries.length === 0) {
|
|
54
|
-
check(update(handle, collBuf.buf, grpBuf.buf,
|
|
56
|
+
check(update(handle, collBuf.buf, grpBuf.buf, keyArg, null, null, null, null, 0n, 0n));
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -149,7 +151,7 @@ export function updateGroupColumns(
|
|
|
149
151
|
handle,
|
|
150
152
|
collBuf.buf,
|
|
151
153
|
grpBuf.buf,
|
|
152
|
-
|
|
154
|
+
keyArg,
|
|
153
155
|
namesTable.buf,
|
|
154
156
|
typesAlloc.buf,
|
|
155
157
|
dataTable.buf,
|
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 },
|
|
@@ -58,7 +59,11 @@ const elementSymbols = {
|
|
|
58
59
|
const crudSymbols = {
|
|
59
60
|
quiver_database_create_element: { args: [P, BUF, P, P], returns: I32 },
|
|
60
61
|
quiver_database_update_element: { args: [P, BUF, I64, P], returns: I32 },
|
|
62
|
+
quiver_database_update_element_by_label: { args: [P, BUF, BUF, P], returns: I32 },
|
|
61
63
|
quiver_database_delete_element: { args: [P, BUF, I64], returns: I32 },
|
|
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 },
|
|
62
67
|
} as const;
|
|
63
68
|
|
|
64
69
|
const readSymbols = {
|
|
@@ -131,18 +136,34 @@ const timeSeriesSymbols = {
|
|
|
131
136
|
args: [P, BUF, BUF, I64, P, P, P, USIZE],
|
|
132
137
|
returns: I32,
|
|
133
138
|
},
|
|
139
|
+
quiver_database_upsert_time_series_row_by_label: {
|
|
140
|
+
args: [P, BUF, BUF, BUF, P, P, P, USIZE],
|
|
141
|
+
returns: I32,
|
|
142
|
+
},
|
|
134
143
|
quiver_database_update_time_series_group: {
|
|
135
144
|
args: [P, BUF, BUF, I64, P, P, P, P, USIZE, USIZE],
|
|
136
145
|
returns: I32,
|
|
137
146
|
},
|
|
147
|
+
quiver_database_update_time_series_group_by_label: {
|
|
148
|
+
args: [P, BUF, BUF, BUF, P, P, P, P, USIZE, USIZE],
|
|
149
|
+
returns: I32,
|
|
150
|
+
},
|
|
138
151
|
quiver_database_update_vector_group: {
|
|
139
152
|
args: [P, BUF, BUF, I64, P, P, P, P, USIZE, USIZE],
|
|
140
153
|
returns: I32,
|
|
141
154
|
},
|
|
155
|
+
quiver_database_update_vector_group_by_label: {
|
|
156
|
+
args: [P, BUF, BUF, BUF, P, P, P, P, USIZE, USIZE],
|
|
157
|
+
returns: I32,
|
|
158
|
+
},
|
|
142
159
|
quiver_database_update_set_group: {
|
|
143
160
|
args: [P, BUF, BUF, I64, P, P, P, P, USIZE, USIZE],
|
|
144
161
|
returns: I32,
|
|
145
162
|
},
|
|
163
|
+
quiver_database_update_set_group_by_label: {
|
|
164
|
+
args: [P, BUF, BUF, BUF, P, P, P, P, USIZE, USIZE],
|
|
165
|
+
returns: I32,
|
|
166
|
+
},
|
|
146
167
|
quiver_database_free_time_series_data: { args: [P, P, P, P, USIZE, USIZE], returns: I32 },
|
|
147
168
|
quiver_database_has_time_series_files: { args: [P, BUF, P], returns: I32 },
|
|
148
169
|
quiver_database_list_time_series_files_columns: { args: [P, BUF, P, P], returns: I32 },
|
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
|
|
@@ -212,7 +224,12 @@ Rules worth knowing:
|
|
|
212
224
|
\`\`\`lua
|
|
213
225
|
local id = db:create_element(collection, element_table) -- returns new integer id
|
|
214
226
|
db:update_element(collection, id, element_table)
|
|
227
|
+
db:update_element_by_label(collection, label, element_table) -- same update, addressed by label
|
|
215
228
|
db:delete_element(collection, id)
|
|
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)
|
|
216
233
|
\`\`\`
|
|
217
234
|
|
|
218
235
|
The element table holds scalar attributes as \`key = value\`, and vector/set attributes as
|
|
@@ -238,6 +255,13 @@ Notes:
|
|
|
238
255
|
- **\`update_element\` / \`delete_element\` require an existing id.** Targeting an id that does not
|
|
239
256
|
exist throws \`Element not found: <id> in collection '<collection>'\` (no silent no-op). Use
|
|
240
257
|
\`read_element_ids\` to get valid ids.
|
|
258
|
+
- **\`update_element_by_label\` / \`delete_element_by_label\` require an existing label**, unique
|
|
259
|
+
*per collection*, not per database — one naming an element of another collection does not
|
|
260
|
+
resolve. A miss throws \`Element not found: label '<label>' in collection '<collection>'\` and
|
|
261
|
+
changes nothing. Passing \`label = "New name"\` in the element table renames the element, after
|
|
262
|
+
which only the new label resolves. Because the label form delegates to the id form, failures
|
|
263
|
+
that validate the *element* (an empty table, a type mismatch) report
|
|
264
|
+
\`Cannot update_element: ...\`.
|
|
241
265
|
- **Empty arrays are skipped.** An attribute whose value is \`{}\` writes no vector/set (the element
|
|
242
266
|
type can't be inferred from an empty array), so it is silently dropped.
|
|
243
267
|
- **No \`nil\` scalar attributes.** In Lua a key set to \`nil\` is dropped from the table, so
|
|
@@ -245,7 +269,18 @@ Notes:
|
|
|
245
269
|
**throws** (\`...must have at least one scalar attribute\` on create, \`...at least one attribute
|
|
246
270
|
to update\` on update). To leave a column unchanged, omit the key — you cannot set a scalar to
|
|
247
271
|
NULL via the element table. (\`nil\` → NULL is only accepted by
|
|
248
|
-
\`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.
|
|
249
284
|
|
|
250
285
|
---
|
|
251
286
|
|
|
@@ -295,6 +330,9 @@ db:update_vector_group("Child", "refs", id, { parent_ref = { 1, 2, 3 } })
|
|
|
295
330
|
db:update_set_group("Child", "parents", id, { parent_ref = { 1, 2 } })
|
|
296
331
|
|
|
297
332
|
db:update_vector_group("Child", "refs", id, {}) -- clears the group
|
|
333
|
+
|
|
334
|
+
db:update_vector_group_by_label("Child", "refs", "Child 1", { parent_ref = { 1, 2 } })
|
|
335
|
+
db:update_set_group_by_label("Child", "parents", "Child 1", { parent_ref = { 1, 2 } })
|
|
298
336
|
\`\`\`
|
|
299
337
|
|
|
300
338
|
Use these instead of routing a group's columns through \`update_element\` whenever a column name is
|
|
@@ -311,7 +349,8 @@ Rules:
|
|
|
311
349
|
are rejected if passed.
|
|
312
350
|
- **Foreign-key columns accept a label string** and resolve it to the referenced id, exactly as in
|
|
313
351
|
\`create_element\` / \`update_element\`.
|
|
314
|
-
- The element id must exist, same as \`update_element\` / \`delete_element
|
|
352
|
+
- The element id must exist, same as \`update_element\` / \`delete_element\`; the \`_by_label\` form
|
|
353
|
+
takes a label in its place, with \`update_element_by_label\`'s resolution and miss semantics.
|
|
315
354
|
|
|
316
355
|
---
|
|
317
356
|
|
|
@@ -372,6 +411,8 @@ db:update_time_series_group("Items", "data", id, {
|
|
|
372
411
|
})
|
|
373
412
|
|
|
374
413
|
db:update_time_series_group("Items", "data", id, {}) -- clears the group
|
|
414
|
+
|
|
415
|
+
db:update_time_series_group_by_label("Items", "data", "Item 1", { date_time = { "2024-01-01T00:00:00" }, value = { 10.5 } })
|
|
375
416
|
\`\`\`
|
|
376
417
|
|
|
377
418
|
A read-modify-write looks like this:
|
|
@@ -401,6 +442,8 @@ column names). Each value of the top-level table must be an **array**, not a sca
|
|
|
401
442
|
table {} to clear the group\`) — only a bare \`{}\` clears.
|
|
402
443
|
- Integer values are accepted for REAL columns (converted on insert). Booleans, functions, and
|
|
403
444
|
other unsupported Lua types throw \`column '...' has unsupported Lua type\`.
|
|
445
|
+
- The element id must exist; the \`_by_label\` form takes a label in its place, with
|
|
446
|
+
\`update_element_by_label\`'s resolution and miss semantics. Every rule above applies to both.
|
|
404
447
|
|
|
405
448
|
### Append/upsert a single row (\`upsert_time_series_row\` — ROW-oriented, the one exception)
|
|
406
449
|
|
|
@@ -412,8 +455,16 @@ db:upsert_time_series_row("Items", "data", id, {
|
|
|
412
455
|
date_time = "2024-01-04T00:00:00",
|
|
413
456
|
value = 40.0,
|
|
414
457
|
})
|
|
458
|
+
|
|
459
|
+
db:upsert_time_series_row_by_label("Items", "data", "Item 1", {
|
|
460
|
+
date_time = "2024-01-04T00:00:00",
|
|
461
|
+
value = 40.0,
|
|
462
|
+
})
|
|
415
463
|
\`\`\`
|
|
416
464
|
|
|
465
|
+
The element id must exist; the \`_by_label\` form takes a label in its place, with
|
|
466
|
+
\`update_element_by_label\`'s resolution and miss semantics.
|
|
467
|
+
|
|
417
468
|
---
|
|
418
469
|
|
|
419
470
|
## Time series files
|
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/time-series.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
toCString,
|
|
19
19
|
} from "./ffi-helpers.ts";
|
|
20
20
|
import { updateGroupColumns } from "./group-columns.ts";
|
|
21
|
-
import { getSymbols } from "./loader.ts";
|
|
21
|
+
import { getSymbols, type NativePointer } from "./loader.ts";
|
|
22
22
|
import {
|
|
23
23
|
type Allocation,
|
|
24
24
|
DATA_TYPE_DATE_TIME,
|
|
@@ -200,16 +200,57 @@ Database.prototype.updateTimeSeriesGroup = function (
|
|
|
200
200
|
);
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
/** Label-addressed counterpart of updateTimeSeriesGroup. */
|
|
204
|
+
Database.prototype.updateTimeSeriesGroupByLabel = function (
|
|
204
205
|
this: Database,
|
|
205
206
|
collection: string,
|
|
206
207
|
group: string,
|
|
207
|
-
|
|
208
|
+
label: string,
|
|
209
|
+
data: TimeSeriesData,
|
|
210
|
+
): void {
|
|
211
|
+
updateGroupColumns(
|
|
212
|
+
this._handle,
|
|
213
|
+
"updateTimeSeriesGroupByLabel",
|
|
214
|
+
getSymbols().quiver_database_update_time_series_group_by_label,
|
|
215
|
+
collection,
|
|
216
|
+
group,
|
|
217
|
+
label,
|
|
218
|
+
data,
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The parallel-array signature both row-oriented time-series upsert C functions share
|
|
224
|
+
* (quiver_database_upsert_time_series_row and its _by_label form). `key` is an id for the by-id
|
|
225
|
+
* form, a NUL-terminated label otherwise.
|
|
226
|
+
*/
|
|
227
|
+
type UpsertRowFn = (
|
|
228
|
+
db: NativePointer,
|
|
229
|
+
collection: Uint8Array,
|
|
230
|
+
group: Uint8Array,
|
|
231
|
+
key: bigint | Uint8Array,
|
|
232
|
+
names: Uint8Array | null,
|
|
233
|
+
types: Uint8Array | null,
|
|
234
|
+
data: Uint8Array | null,
|
|
235
|
+
columnCount: bigint,
|
|
236
|
+
) => number;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Marshal a single row of scalars and forward it to one of the row-oriented time-series upsert
|
|
240
|
+
* C functions. Shared by upsertTimeSeriesRow / upsertTimeSeriesRowByLabel: they differ only in
|
|
241
|
+
* which C entry point they call and whether `key` is an id or a label.
|
|
242
|
+
*/
|
|
243
|
+
function upsertRowColumns(
|
|
244
|
+
handle: NativePointer,
|
|
245
|
+
upsert: UpsertRowFn,
|
|
246
|
+
collection: string,
|
|
247
|
+
group: string,
|
|
248
|
+
key: number | string,
|
|
208
249
|
row: Record<string, number | bigint | string>,
|
|
209
250
|
): void {
|
|
210
|
-
const lib = getSymbols();
|
|
211
251
|
const collBuf = toCString(collection);
|
|
212
252
|
const grpBuf = toCString(group);
|
|
253
|
+
const keyArg = typeof key === "string" ? toCString(key).buf : BigInt(key);
|
|
213
254
|
const entries = Object.entries(row);
|
|
214
255
|
const columnCount = entries.length;
|
|
215
256
|
const keepalive: Allocation[] = [];
|
|
@@ -248,17 +289,52 @@ Database.prototype.upsertTimeSeriesRow = function (
|
|
|
248
289
|
keepalive.push(dataTable);
|
|
249
290
|
|
|
250
291
|
check(
|
|
251
|
-
|
|
252
|
-
|
|
292
|
+
upsert(
|
|
293
|
+
handle,
|
|
253
294
|
collBuf.buf,
|
|
254
295
|
grpBuf.buf,
|
|
255
|
-
|
|
296
|
+
keyArg,
|
|
256
297
|
namesTable.buf,
|
|
257
298
|
typesAlloc.buf,
|
|
258
299
|
dataTable.buf,
|
|
259
300
|
BigInt(columnCount),
|
|
260
301
|
),
|
|
261
302
|
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
Database.prototype.upsertTimeSeriesRow = function (
|
|
306
|
+
this: Database,
|
|
307
|
+
collection: string,
|
|
308
|
+
group: string,
|
|
309
|
+
id: number,
|
|
310
|
+
row: Record<string, number | bigint | string>,
|
|
311
|
+
): void {
|
|
312
|
+
upsertRowColumns(
|
|
313
|
+
this._handle,
|
|
314
|
+
getSymbols().quiver_database_upsert_time_series_row,
|
|
315
|
+
collection,
|
|
316
|
+
group,
|
|
317
|
+
id,
|
|
318
|
+
row,
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/** Label-addressed counterpart of upsertTimeSeriesRow. */
|
|
323
|
+
Database.prototype.upsertTimeSeriesRowByLabel = function (
|
|
324
|
+
this: Database,
|
|
325
|
+
collection: string,
|
|
326
|
+
group: string,
|
|
327
|
+
label: string,
|
|
328
|
+
row: Record<string, number | bigint | string>,
|
|
329
|
+
): void {
|
|
330
|
+
upsertRowColumns(
|
|
331
|
+
this._handle,
|
|
332
|
+
getSymbols().quiver_database_upsert_time_series_row_by_label,
|
|
333
|
+
collection,
|
|
334
|
+
group,
|
|
335
|
+
label,
|
|
336
|
+
row,
|
|
337
|
+
);
|
|
262
338
|
};
|
|
263
339
|
|
|
264
340
|
Database.prototype.hasTimeSeriesFiles = function (this: Database, collection: string): boolean {
|
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 = {
|