quiverdb 0.10.1 → 0.10.2

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 CHANGED
@@ -71,6 +71,8 @@ bun run example.ts
71
71
  - `createElement(collection, data)` -- Create element, returns numeric ID
72
72
  - `updateElement(collection, id, data)` -- Update element by ID
73
73
  - `deleteElement(collection, id)` -- Delete element by ID
74
+ - `updateElementByLabel(collection, label, data)` -- Update element by label
75
+ - `deleteElementByLabel(collection, label)` -- Delete element by label
74
76
 
75
77
  ### Read (bulk)
76
78
 
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.10.1",
3
+ "version": "0.10.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/create.ts CHANGED
@@ -148,12 +148,51 @@ Database.prototype.updateElement = function (
148
148
  }
149
149
  };
150
150
 
151
+ /** Label-addressed counterpart of updateElement. */
152
+ Database.prototype.updateElementByLabel = function (
153
+ this: Database,
154
+ collection: string,
155
+ label: string,
156
+ data: ElementData,
157
+ ): void {
158
+ const lib = getSymbols();
159
+ const handle = this._handle;
160
+
161
+ const outElem = allocPtrOut();
162
+ check(lib.quiver_element_create(outElem.buf));
163
+ const elemPtr = readPtrOut(outElem);
164
+
165
+ try {
166
+ for (const [key, value] of Object.entries(data)) {
167
+ if (value === undefined) continue;
168
+ setElementField(lib, elemPtr, key, value);
169
+ }
170
+ const collBuf = toCString(collection);
171
+ const labelBuf = toCString(label);
172
+ check(lib.quiver_database_update_element_by_label(handle, collBuf.buf, labelBuf.buf, elemPtr));
173
+ } finally {
174
+ lib.quiver_element_destroy(elemPtr);
175
+ }
176
+ };
177
+
151
178
  Database.prototype.deleteElement = function (this: Database, collection: string, id: number): void {
152
179
  const lib = getSymbols();
153
180
  const collBuf = toCString(collection);
154
181
  check(lib.quiver_database_delete_element(this._handle, collBuf.buf, BigInt(id)));
155
182
  };
156
183
 
184
+ /** Label-addressed counterpart of deleteElement. */
185
+ Database.prototype.deleteElementByLabel = function (
186
+ this: Database,
187
+ collection: string,
188
+ label: string,
189
+ ): void {
190
+ const lib = getSymbols();
191
+ const collBuf = toCString(collection);
192
+ const labelBuf = toCString(label);
193
+ check(lib.quiver_database_delete_element_by_label(this._handle, collBuf.buf, labelBuf.buf));
194
+ };
195
+
157
196
  /**
158
197
  * Replace all of an element's rows in one *named* vector group, from column arrays keyed by name.
159
198
  *
@@ -180,6 +219,25 @@ Database.prototype.updateVectorGroup = function (
180
219
  );
181
220
  };
182
221
 
222
+ /** Label-addressed counterpart of updateVectorGroup. */
223
+ Database.prototype.updateVectorGroupByLabel = function (
224
+ this: Database,
225
+ collection: string,
226
+ group: string,
227
+ label: string,
228
+ data: GroupColumns,
229
+ ): void {
230
+ updateGroupColumns(
231
+ this._handle,
232
+ "updateVectorGroupByLabel",
233
+ getSymbols().quiver_database_update_vector_group_by_label,
234
+ collection,
235
+ group,
236
+ label,
237
+ data,
238
+ );
239
+ };
240
+
183
241
  /** Set-group counterpart of updateVectorGroup. */
184
242
  Database.prototype.updateSetGroup = function (
185
243
  this: Database,
@@ -198,3 +256,22 @@ Database.prototype.updateSetGroup = function (
198
256
  data,
199
257
  );
200
258
  };
259
+
260
+ /** Label-addressed counterpart of updateSetGroup. */
261
+ Database.prototype.updateSetGroupByLabel = function (
262
+ this: Database,
263
+ collection: string,
264
+ group: string,
265
+ label: string,
266
+ data: GroupColumns,
267
+ ): void {
268
+ updateGroupColumns(
269
+ this._handle,
270
+ "updateSetGroupByLabel",
271
+ getSymbols().quiver_database_update_set_group_by_label,
272
+ collection,
273
+ group,
274
+ label,
275
+ data,
276
+ );
277
+ };
package/src/database.ts CHANGED
@@ -86,7 +86,9 @@ export class Database {
86
86
  // --- Element CRUD (implemented in create.ts) ---
87
87
  declare createElement: (collection: string, data: ElementData) => number;
88
88
  declare updateElement: (collection: string, id: number, data: ElementData) => void;
89
+ declare updateElementByLabel: (collection: string, label: string, data: ElementData) => void;
89
90
  declare deleteElement: (collection: string, id: number) => void;
91
+ declare deleteElementByLabel: (collection: string, label: string) => void;
90
92
 
91
93
  // --- Reads (implemented in read.ts) ---
92
94
  declare readScalarIntegers: (collection: string, attribute: string) => (number | null)[];
@@ -156,24 +158,48 @@ export class Database {
156
158
  id: number,
157
159
  data: TimeSeriesData,
158
160
  ) => void;
161
+ declare updateTimeSeriesGroupByLabel: (
162
+ collection: string,
163
+ group: string,
164
+ label: string,
165
+ data: TimeSeriesData,
166
+ ) => void;
159
167
  declare updateVectorGroup: (
160
168
  collection: string,
161
169
  group: string,
162
170
  id: number,
163
171
  data: GroupColumns,
164
172
  ) => void;
173
+ declare updateVectorGroupByLabel: (
174
+ collection: string,
175
+ group: string,
176
+ label: string,
177
+ data: GroupColumns,
178
+ ) => void;
165
179
  declare updateSetGroup: (
166
180
  collection: string,
167
181
  group: string,
168
182
  id: number,
169
183
  data: GroupColumns,
170
184
  ) => void;
185
+ declare updateSetGroupByLabel: (
186
+ collection: string,
187
+ group: string,
188
+ label: string,
189
+ data: GroupColumns,
190
+ ) => void;
171
191
  declare upsertTimeSeriesRow: (
172
192
  collection: string,
173
193
  group: string,
174
194
  id: number,
175
195
  row: Record<string, number | bigint | string>,
176
196
  ) => void;
197
+ declare upsertTimeSeriesRowByLabel: (
198
+ collection: string,
199
+ group: string,
200
+ label: string,
201
+ row: Record<string, number | bigint | string>,
202
+ ) => void;
177
203
  declare hasTimeSeriesFiles: (collection: string) => boolean;
178
204
  declare listTimeSeriesFilesColumns: (collection: string) => string[];
179
205
  declare readTimeSeriesFiles: (collection: string) => Record<string, string | null>;
@@ -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
- id: bigint,
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: the three
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
- id: number,
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, BigInt(id), null, null, null, null, 0n, 0n));
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
- BigInt(id),
154
+ keyArg,
153
155
  namesTable.buf,
154
156
  typesAlloc.buf,
155
157
  dataTable.buf,
package/src/loader.ts CHANGED
@@ -58,7 +58,9 @@ const elementSymbols = {
58
58
  const crudSymbols = {
59
59
  quiver_database_create_element: { args: [P, BUF, P, P], returns: I32 },
60
60
  quiver_database_update_element: { args: [P, BUF, I64, P], returns: I32 },
61
+ quiver_database_update_element_by_label: { args: [P, BUF, BUF, P], returns: I32 },
61
62
  quiver_database_delete_element: { args: [P, BUF, I64], returns: I32 },
63
+ quiver_database_delete_element_by_label: { args: [P, BUF, BUF], returns: I32 },
62
64
  } as const;
63
65
 
64
66
  const readSymbols = {
@@ -131,18 +133,34 @@ const timeSeriesSymbols = {
131
133
  args: [P, BUF, BUF, I64, P, P, P, USIZE],
132
134
  returns: I32,
133
135
  },
136
+ quiver_database_upsert_time_series_row_by_label: {
137
+ args: [P, BUF, BUF, BUF, P, P, P, USIZE],
138
+ returns: I32,
139
+ },
134
140
  quiver_database_update_time_series_group: {
135
141
  args: [P, BUF, BUF, I64, P, P, P, P, USIZE, USIZE],
136
142
  returns: I32,
137
143
  },
144
+ quiver_database_update_time_series_group_by_label: {
145
+ args: [P, BUF, BUF, BUF, P, P, P, P, USIZE, USIZE],
146
+ returns: I32,
147
+ },
138
148
  quiver_database_update_vector_group: {
139
149
  args: [P, BUF, BUF, I64, P, P, P, P, USIZE, USIZE],
140
150
  returns: I32,
141
151
  },
152
+ quiver_database_update_vector_group_by_label: {
153
+ args: [P, BUF, BUF, BUF, P, P, P, P, USIZE, USIZE],
154
+ returns: I32,
155
+ },
142
156
  quiver_database_update_set_group: {
143
157
  args: [P, BUF, BUF, I64, P, P, P, P, USIZE, USIZE],
144
158
  returns: I32,
145
159
  },
160
+ quiver_database_update_set_group_by_label: {
161
+ args: [P, BUF, BUF, BUF, P, P, P, P, USIZE, USIZE],
162
+ returns: I32,
163
+ },
146
164
  quiver_database_free_time_series_data: { args: [P, P, P, P, USIZE, USIZE], returns: I32 },
147
165
  quiver_database_has_time_series_files: { args: [P, BUF, P], returns: I32 },
148
166
  quiver_database_list_time_series_files_columns: { args: [P, BUF, P, P], returns: I32 },
package/src/lua-api.ts CHANGED
@@ -212,7 +212,9 @@ Rules worth knowing:
212
212
  \`\`\`lua
213
213
  local id = db:create_element(collection, element_table) -- returns new integer id
214
214
  db:update_element(collection, id, element_table)
215
+ db:update_element_by_label(collection, label, element_table) -- same update, addressed by label
215
216
  db:delete_element(collection, id)
217
+ db:delete_element_by_label(collection, label) -- same delete, addressed by label
216
218
  \`\`\`
217
219
 
218
220
  The element table holds scalar attributes as \`key = value\`, and vector/set attributes as
@@ -238,6 +240,13 @@ Notes:
238
240
  - **\`update_element\` / \`delete_element\` require an existing id.** Targeting an id that does not
239
241
  exist throws \`Element not found: <id> in collection '<collection>'\` (no silent no-op). Use
240
242
  \`read_element_ids\` to get valid ids.
243
+ - **\`update_element_by_label\` / \`delete_element_by_label\` require an existing label**, unique
244
+ *per collection*, not per database — one naming an element of another collection does not
245
+ resolve. A miss throws \`Element not found: label '<label>' in collection '<collection>'\` and
246
+ changes nothing. Passing \`label = "New name"\` in the element table renames the element, after
247
+ which only the new label resolves. Because the label form delegates to the id form, failures
248
+ that validate the *element* (an empty table, a type mismatch) report
249
+ \`Cannot update_element: ...\`.
241
250
  - **Empty arrays are skipped.** An attribute whose value is \`{}\` writes no vector/set (the element
242
251
  type can't be inferred from an empty array), so it is silently dropped.
243
252
  - **No \`nil\` scalar attributes.** In Lua a key set to \`nil\` is dropped from the table, so
@@ -295,6 +304,9 @@ db:update_vector_group("Child", "refs", id, { parent_ref = { 1, 2, 3 } })
295
304
  db:update_set_group("Child", "parents", id, { parent_ref = { 1, 2 } })
296
305
 
297
306
  db:update_vector_group("Child", "refs", id, {}) -- clears the group
307
+
308
+ db:update_vector_group_by_label("Child", "refs", "Child 1", { parent_ref = { 1, 2 } })
309
+ db:update_set_group_by_label("Child", "parents", "Child 1", { parent_ref = { 1, 2 } })
298
310
  \`\`\`
299
311
 
300
312
  Use these instead of routing a group's columns through \`update_element\` whenever a column name is
@@ -311,7 +323,8 @@ Rules:
311
323
  are rejected if passed.
312
324
  - **Foreign-key columns accept a label string** and resolve it to the referenced id, exactly as in
313
325
  \`create_element\` / \`update_element\`.
314
- - The element id must exist, same as \`update_element\` / \`delete_element\`.
326
+ - The element id must exist, same as \`update_element\` / \`delete_element\`; the \`_by_label\` form
327
+ takes a label in its place, with \`update_element_by_label\`'s resolution and miss semantics.
315
328
 
316
329
  ---
317
330
 
@@ -372,6 +385,8 @@ db:update_time_series_group("Items", "data", id, {
372
385
  })
373
386
 
374
387
  db:update_time_series_group("Items", "data", id, {}) -- clears the group
388
+
389
+ db:update_time_series_group_by_label("Items", "data", "Item 1", { date_time = { "2024-01-01T00:00:00" }, value = { 10.5 } })
375
390
  \`\`\`
376
391
 
377
392
  A read-modify-write looks like this:
@@ -401,6 +416,8 @@ column names). Each value of the top-level table must be an **array**, not a sca
401
416
  table {} to clear the group\`) — only a bare \`{}\` clears.
402
417
  - Integer values are accepted for REAL columns (converted on insert). Booleans, functions, and
403
418
  other unsupported Lua types throw \`column '...' has unsupported Lua type\`.
419
+ - The element id must exist; the \`_by_label\` form takes a label in its place, with
420
+ \`update_element_by_label\`'s resolution and miss semantics. Every rule above applies to both.
404
421
 
405
422
  ### Append/upsert a single row (\`upsert_time_series_row\` — ROW-oriented, the one exception)
406
423
 
@@ -412,8 +429,16 @@ db:upsert_time_series_row("Items", "data", id, {
412
429
  date_time = "2024-01-04T00:00:00",
413
430
  value = 40.0,
414
431
  })
432
+
433
+ db:upsert_time_series_row_by_label("Items", "data", "Item 1", {
434
+ date_time = "2024-01-04T00:00:00",
435
+ value = 40.0,
436
+ })
415
437
  \`\`\`
416
438
 
439
+ The element id must exist; the \`_by_label\` form takes a label in its place, with
440
+ \`update_element_by_label\`'s resolution and miss semantics.
441
+
417
442
  ---
418
443
 
419
444
  ## Time series files
@@ -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
- Database.prototype.upsertTimeSeriesRow = function (
203
+ /** Label-addressed counterpart of updateTimeSeriesGroup. */
204
+ Database.prototype.updateTimeSeriesGroupByLabel = function (
204
205
  this: Database,
205
206
  collection: string,
206
207
  group: string,
207
- id: number,
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
- lib.quiver_database_upsert_time_series_row(
252
- this._handle,
292
+ upsert(
293
+ handle,
253
294
  collBuf.buf,
254
295
  grpBuf.buf,
255
- BigInt(id),
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 {