quiverdb 0.9.5 → 0.9.6

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.
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.9.5",
3
+ "version": "0.9.6",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/composites.ts CHANGED
@@ -1,4 +1,11 @@
1
1
  import { Database } from "./database.ts";
2
+ import { QuiverError } from "./errors.ts";
3
+ import {
4
+ DATA_TYPE_DATE_TIME,
5
+ DATA_TYPE_FLOAT,
6
+ DATA_TYPE_INTEGER,
7
+ DATA_TYPE_STRING,
8
+ } from "./types.ts";
2
9
 
3
10
  Database.prototype.readScalarsById = function (
4
11
  this: Database,
@@ -10,18 +17,18 @@ Database.prototype.readScalarsById = function (
10
17
 
11
18
  for (const attr of attributes) {
12
19
  switch (attr.dataType) {
13
- case 0: // INTEGER
20
+ case DATA_TYPE_INTEGER:
14
21
  result[attr.name] = this.readScalarIntegerById(collection, attr.name, id);
15
22
  break;
16
- case 1: // FLOAT
23
+ case DATA_TYPE_FLOAT:
17
24
  result[attr.name] = this.readScalarFloatById(collection, attr.name, id);
18
25
  break;
19
- case 2: // STRING
20
- case 3: // DATE_TIME
26
+ case DATA_TYPE_STRING:
27
+ case DATA_TYPE_DATE_TIME:
21
28
  result[attr.name] = this.readScalarStringById(collection, attr.name, id);
22
29
  break;
23
30
  default:
24
- throw new Error(`Unknown data type ${attr.dataType} for attribute '${attr.name}'`);
31
+ throw new QuiverError(`Unknown data type ${attr.dataType} for attribute '${attr.name}'`);
25
32
  }
26
33
  }
27
34
 
@@ -39,18 +46,18 @@ Database.prototype.readVectorsById = function (
39
46
  for (const group of groups) {
40
47
  for (const col of group.valueColumns) {
41
48
  switch (col.dataType) {
42
- case 0: // INTEGER
49
+ case DATA_TYPE_INTEGER:
43
50
  result[col.name] = this.readVectorIntegersById(collection, col.name, id);
44
51
  break;
45
- case 1: // FLOAT
52
+ case DATA_TYPE_FLOAT:
46
53
  result[col.name] = this.readVectorFloatsById(collection, col.name, id);
47
54
  break;
48
- case 2: // STRING
49
- case 3: // DATE_TIME
55
+ case DATA_TYPE_STRING:
56
+ case DATA_TYPE_DATE_TIME:
50
57
  result[col.name] = this.readVectorStringsById(collection, col.name, id);
51
58
  break;
52
59
  default:
53
- throw new Error(`Unknown data type ${col.dataType} for column '${col.name}'`);
60
+ throw new QuiverError(`Unknown data type ${col.dataType} for column '${col.name}'`);
54
61
  }
55
62
  }
56
63
  }
@@ -69,18 +76,18 @@ Database.prototype.readSetsById = function (
69
76
  for (const group of groups) {
70
77
  for (const col of group.valueColumns) {
71
78
  switch (col.dataType) {
72
- case 0: // INTEGER
79
+ case DATA_TYPE_INTEGER:
73
80
  result[col.name] = this.readSetIntegersById(collection, col.name, id);
74
81
  break;
75
- case 1: // FLOAT
82
+ case DATA_TYPE_FLOAT:
76
83
  result[col.name] = this.readSetFloatsById(collection, col.name, id);
77
84
  break;
78
- case 2: // STRING
79
- case 3: // DATE_TIME
85
+ case DATA_TYPE_STRING:
86
+ case DATA_TYPE_DATE_TIME:
80
87
  result[col.name] = this.readSetStringsById(collection, col.name, id);
81
88
  break;
82
89
  default:
83
- throw new Error(`Unknown data type ${col.dataType} for column '${col.name}'`);
90
+ throw new QuiverError(`Unknown data type ${col.dataType} for column '${col.name}'`);
84
91
  }
85
92
  }
86
93
  }
package/src/create.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { ptr } from "bun:ffi";
2
1
  import { Database } from "./database.ts";
3
2
  import { check, QuiverError } from "./errors.ts";
4
3
  import {
@@ -30,8 +29,8 @@ function setElementArray(
30
29
  const first = values[0];
31
30
 
32
31
  if (typeof first === "bigint") {
33
- const arr = allocNativeInt64((values as bigint[]).map(Number));
34
- check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.ptr, values.length));
32
+ const arr = allocNativeInt64(values as bigint[]);
33
+ check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.buf, values.length));
35
34
  return;
36
35
  }
37
36
 
@@ -39,17 +38,17 @@ function setElementArray(
39
38
  const allIntegers = (values as number[]).every((v) => Number.isInteger(v));
40
39
  if (allIntegers) {
41
40
  const arr = allocNativeInt64(values as number[]);
42
- check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.ptr, values.length));
41
+ check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.buf, values.length));
43
42
  } else {
44
43
  const arr = allocNativeFloat64(values as number[]);
45
- check(lib.quiver_element_set_array_float(elemPtr, nameBuf.buf, arr.ptr, values.length));
44
+ check(lib.quiver_element_set_array_float(elemPtr, nameBuf.buf, arr.buf, values.length));
46
45
  }
47
46
  return;
48
47
  }
49
48
 
50
49
  if (typeof first === "string") {
51
50
  const { table, keepalive: _keepalive } = allocNativeStringArray(values as string[]);
52
- check(lib.quiver_element_set_array_string(elemPtr, nameBuf.buf, table.ptr, values.length));
51
+ check(lib.quiver_element_set_array_string(elemPtr, nameBuf.buf, table.buf, values.length));
53
52
  return;
54
53
  }
55
54
 
@@ -101,7 +100,7 @@ Database.prototype.createElement = function (
101
100
  const handle = this._handle;
102
101
 
103
102
  const outElem = allocPtrOut();
104
- check(lib.quiver_element_create(outElem.ptr));
103
+ check(lib.quiver_element_create(outElem.buf));
105
104
  const elemPtr = readPtrOut(outElem);
106
105
 
107
106
  try {
@@ -111,9 +110,8 @@ Database.prototype.createElement = function (
111
110
  }
112
111
 
113
112
  const outIdBuf = new Uint8Array(8);
114
- const outIdPtr = ptr(outIdBuf);
115
113
  const collBuf = toCString(collection);
116
- check(lib.quiver_database_create_element(handle, collBuf.buf, elemPtr, outIdPtr));
114
+ check(lib.quiver_database_create_element(handle, collBuf.buf, elemPtr, outIdBuf));
117
115
  return Number(new DataView(outIdBuf.buffer).getBigInt64(0, true));
118
116
  } finally {
119
117
  lib.quiver_element_destroy(elemPtr);
@@ -130,7 +128,7 @@ Database.prototype.updateElement = function (
130
128
  const handle = this._handle;
131
129
 
132
130
  const outElem = allocPtrOut();
133
- check(lib.quiver_element_create(outElem.ptr));
131
+ check(lib.quiver_element_create(outElem.buf));
134
132
  const elemPtr = readPtrOut(outElem);
135
133
 
136
134
  try {
package/src/csv.ts CHANGED
@@ -96,7 +96,7 @@ Database.prototype.exportCsv = function (
96
96
  const pathBuf = toCString(filePath);
97
97
  const [optsBuf, _keepalive] = buildCsvOptionsBuffer(options);
98
98
  check(
99
- lib.quiver_database_export_csv(this._handle, collBuf.buf, grpBuf.buf, pathBuf.buf, optsBuf.ptr),
99
+ lib.quiver_database_export_csv(this._handle, collBuf.buf, grpBuf.buf, pathBuf.buf, optsBuf.buf),
100
100
  );
101
101
  };
102
102
 
@@ -113,6 +113,6 @@ Database.prototype.importCsv = function (
113
113
  const pathBuf = toCString(filePath);
114
114
  const [optsBuf, _keepalive] = buildCsvOptionsBuffer(options);
115
115
  check(
116
- lib.quiver_database_import_csv(this._handle, collBuf.buf, grpBuf.buf, pathBuf.buf, optsBuf.ptr),
116
+ lib.quiver_database_import_csv(this._handle, collBuf.buf, grpBuf.buf, pathBuf.buf, optsBuf.buf),
117
117
  );
118
118
  };
package/src/database.ts CHANGED
@@ -5,7 +5,7 @@ import type { NativePointer } from "./loader.ts";
5
5
  import { getSymbols } from "./loader.ts";
6
6
  import type { GroupMetadata, ScalarMetadata } from "./metadata.ts";
7
7
  import type { TimeSeriesData } from "./time-series.ts";
8
- import type { ElementData, QueryParam } from "./types.ts";
8
+ import type { DatabaseOptions, ElementData, QueryParam } from "./types.ts";
9
9
 
10
10
  export class Database {
11
11
  private _ptr: NativePointer;
@@ -15,41 +15,50 @@ export class Database {
15
15
  this._ptr = nativePtr;
16
16
  }
17
17
 
18
- static fromSchema(dbPath: string, schemaPath: string): Database {
18
+ static fromSchema(dbPath: string, schemaPath: string, options?: DatabaseOptions): Database {
19
19
  const lib = getSymbols();
20
- const options = makeDefaultOptions();
20
+ const optionsBuf = makeDefaultOptions(options);
21
21
  const outDb = allocPtrOut();
22
22
  const dbPathBuf = toCString(dbPath);
23
23
  const schemaPathBuf = toCString(schemaPath);
24
24
 
25
25
  check(
26
- lib.quiver_database_from_schema(dbPathBuf.buf, schemaPathBuf.buf, options.buf, outDb.ptr),
26
+ lib.quiver_database_from_schema(dbPathBuf.buf, schemaPathBuf.buf, optionsBuf.buf, outDb.buf),
27
27
  );
28
28
 
29
29
  return new Database(readPtrOut(outDb));
30
30
  }
31
31
 
32
- static fromMigrations(dbPath: string, migrationsPath: string): Database {
32
+ static fromMigrations(
33
+ dbPath: string,
34
+ migrationsPath: string,
35
+ options?: DatabaseOptions,
36
+ ): Database {
33
37
  const lib = getSymbols();
34
- const options = makeDefaultOptions();
38
+ const optionsBuf = makeDefaultOptions(options);
35
39
  const outDb = allocPtrOut();
36
40
  const dbPathBuf = toCString(dbPath);
37
41
  const migrPathBuf = toCString(migrationsPath);
38
42
 
39
43
  check(
40
- lib.quiver_database_from_migrations(dbPathBuf.buf, migrPathBuf.buf, options.buf, outDb.ptr),
44
+ lib.quiver_database_from_migrations(
45
+ dbPathBuf.buf,
46
+ migrPathBuf.buf,
47
+ optionsBuf.buf,
48
+ outDb.buf,
49
+ ),
41
50
  );
42
51
 
43
52
  return new Database(readPtrOut(outDb));
44
53
  }
45
54
 
46
- static open(dbPath: string): Database {
55
+ static open(dbPath: string, options?: DatabaseOptions): Database {
47
56
  const lib = getSymbols();
48
- const options = makeDefaultOptions();
57
+ const optionsBuf = makeDefaultOptions(options);
49
58
  const outDb = allocPtrOut();
50
59
  const dbPathBuf = toCString(dbPath);
51
60
 
52
- check(lib.quiver_database_open(dbPathBuf.buf, options.buf, outDb.ptr));
61
+ check(lib.quiver_database_open(dbPathBuf.buf, optionsBuf.buf, outDb.buf));
53
62
 
54
63
  return new Database(readPtrOut(outDb));
55
64
  }
@@ -130,12 +139,24 @@ export class Database {
130
139
 
131
140
  // --- Time series (implemented in time-series.ts) ---
132
141
  declare readTimeSeriesGroup: (collection: string, group: string, id: number) => TimeSeriesData;
142
+ declare readTimeSeriesRow: (
143
+ collection: string,
144
+ group: string,
145
+ attribute: string,
146
+ dateTime: string,
147
+ ) => (number | string | null)[];
133
148
  declare updateTimeSeriesGroup: (
134
149
  collection: string,
135
150
  group: string,
136
151
  id: number,
137
152
  data: TimeSeriesData,
138
153
  ) => void;
154
+ declare addTimeSeriesRow: (
155
+ collection: string,
156
+ group: string,
157
+ id: number,
158
+ row: Record<string, number | bigint | string>,
159
+ ) => void;
139
160
  declare hasTimeSeriesFiles: (collection: string) => boolean;
140
161
  declare listTimeSeriesFilesColumns: (collection: string) => string[];
141
162
  declare readTimeSeriesFiles: (collection: string) => Record<string, string | null>;
@@ -1,5 +1,5 @@
1
1
  import { CString, type Pointer, ptr, read, toArrayBuffer } from "bun:ffi";
2
- import type { Allocation } from "./types.ts";
2
+ import { type Allocation, type DatabaseOptions, LOG_LEVEL_INFO } from "./types.ts";
3
3
 
4
4
  const encoder = new TextEncoder();
5
5
 
@@ -7,11 +7,11 @@ const encoder = new TextEncoder();
7
7
  * Construct the 8-byte quiver_database_options_t struct as an Allocation.
8
8
  * Layout: offset 0 = int32 read_only (default 0), offset 4 = int32 console_level (default 1 = QUIVER_LOG_INFO).
9
9
  */
10
- export function makeDefaultOptions(): Allocation {
10
+ export function makeDefaultOptions(options?: DatabaseOptions): Allocation {
11
11
  const buf = new Uint8Array(8);
12
12
  const dv = new DataView(buf.buffer);
13
- dv.setInt32(0, 0, true); // read_only = false
14
- dv.setInt32(4, 1, true); // console_level = QUIVER_LOG_INFO
13
+ dv.setInt32(0, options?.readOnly ? 1 : 0, true);
14
+ dv.setInt32(4, options?.consoleLevel ?? LOG_LEVEL_INFO, true);
15
15
  return { ptr: ptr(buf), buf };
16
16
  }
17
17
 
@@ -102,7 +102,7 @@ export function toCString(str: string): Allocation {
102
102
  }
103
103
 
104
104
  /** Allocate native memory for an array of int64 values. */
105
- export function allocNativeInt64(values: number[]): Allocation {
105
+ export function allocNativeInt64(values: (number | bigint)[]): Allocation {
106
106
  const buf = new Uint8Array(values.length * 8);
107
107
  const dv = new DataView(buf.buffer);
108
108
  for (let i = 0; i < values.length; i++) {
package/src/index.ts CHANGED
@@ -14,4 +14,18 @@ export { QuiverError } from "./errors.ts";
14
14
  export { LuaRunner } from "./lua-runner.ts";
15
15
  export type { GroupMetadata, ScalarMetadata } from "./metadata.ts";
16
16
  export type { TimeSeriesData } from "./time-series.ts";
17
- export type { ArrayValue, ElementData, QueryParam, ScalarValue, Value } from "./types.ts";
17
+ export {
18
+ LOG_LEVEL_DEBUG,
19
+ LOG_LEVEL_ERROR,
20
+ LOG_LEVEL_INFO,
21
+ LOG_LEVEL_OFF,
22
+ LOG_LEVEL_WARN,
23
+ } from "./types.ts";
24
+ export type {
25
+ ArrayValue,
26
+ DatabaseOptions,
27
+ ElementData,
28
+ QueryParam,
29
+ ScalarValue,
30
+ Value,
31
+ } from "./types.ts";
@@ -1,29 +1,26 @@
1
- import { ptr } from "bun:ffi";
2
1
  import { Database } from "./database.ts";
3
2
  import { check } from "./errors.ts";
4
- import { allocPtrOut, decodeStringFromBuf, toCString } from "./ffi-helpers.ts";
3
+ import { allocPtrOut, decodeStringFromBuf } from "./ffi-helpers.ts";
5
4
  import { getSymbols } from "./loader.ts";
6
5
 
7
6
  Database.prototype.isHealthy = function (this: Database): boolean {
8
7
  const lib = getSymbols();
9
8
  const outBuf = new Uint8Array(4);
10
- const outPtr = ptr(outBuf);
11
- check(lib.quiver_database_is_healthy(this._handle, outPtr));
9
+ check(lib.quiver_database_is_healthy(this._handle, outBuf));
12
10
  return new DataView(outBuf.buffer).getInt32(0, true) !== 0;
13
11
  };
14
12
 
15
13
  Database.prototype.currentVersion = function (this: Database): number {
16
14
  const lib = getSymbols();
17
15
  const outBuf = new Uint8Array(8);
18
- const outPtr = ptr(outBuf);
19
- check(lib.quiver_database_current_version(this._handle, outPtr));
16
+ check(lib.quiver_database_current_version(this._handle, outBuf));
20
17
  return Number(new DataView(outBuf.buffer).getBigInt64(0, true));
21
18
  };
22
19
 
23
20
  Database.prototype.path = function (this: Database): string {
24
21
  const lib = getSymbols();
25
22
  const outPath = allocPtrOut();
26
- check(lib.quiver_database_path(this._handle, outPath.ptr));
23
+ check(lib.quiver_database_path(this._handle, outPath.buf));
27
24
  return decodeStringFromBuf(outPath);
28
25
  };
29
26
 
package/src/loader.ts CHANGED
@@ -13,7 +13,6 @@ const I32 = "i32" as const;
13
13
  const I64 = "i64" as const;
14
14
  const USIZE = "usize" as const;
15
15
  const F64 = "f64" as const;
16
- const V = "void" as const;
17
16
 
18
17
  // Platform constants. `suffix` from bun:ffi is the platform-correct shared
19
18
  // library extension ("dll" | "dylib" | "so").
@@ -30,7 +29,6 @@ export type NativePointer = Pointer | null;
30
29
  const lifecycleSymbols = {
31
30
  quiver_version: { args: [], returns: P },
32
31
  quiver_get_last_error: { args: [], returns: P },
33
- quiver_clear_last_error: { args: [], returns: V },
34
32
  // quiver_database_options_default is intentionally omitted: it returns a
35
33
  // struct by value, which Bun FFI does not support (oven-sh/bun#6139). It was
36
34
  // never called -- makeDefaultOptions() in ffi-helpers.ts builds the options
@@ -116,6 +114,8 @@ const metadataSymbols = {
116
114
 
117
115
  const timeSeriesSymbols = {
118
116
  quiver_database_read_time_series_group: { args: [P, BUF, BUF, I64, P, P, P, P, P], returns: I32 },
117
+ quiver_database_read_time_series_row: { args: [P, BUF, BUF, BUF, BUF, P, P, P], returns: I32 },
118
+ quiver_database_add_time_series_row: { args: [P, BUF, BUF, I64, P, P, P, USIZE], returns: I32 },
119
119
  quiver_database_update_time_series_group: {
120
120
  args: [P, BUF, BUF, I64, P, P, P, USIZE, USIZE],
121
121
  returns: I32,
@@ -149,7 +149,6 @@ const luaSymbols = {
149
149
  quiver_lua_runner_new: { args: [P, P], returns: I32 },
150
150
  quiver_lua_runner_free: { args: [P], returns: I32 },
151
151
  quiver_lua_runner_run: { args: [P, BUF], returns: I32 },
152
- quiver_lua_runner_get_error: { args: [P, P], returns: I32 },
153
152
  } as const;
154
153
 
155
154
  // Combined symbol map for dlopen.
@@ -281,5 +280,3 @@ export function loadLibrary(): QuiverLib {
281
280
  export function getSymbols() {
282
281
  return loadLibrary().symbols;
283
282
  }
284
-
285
- export type Symbols = ReturnType<typeof getSymbols>;
package/src/lua-runner.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Database } from "./database.ts";
2
2
  import { check, QuiverError } from "./errors.ts";
3
- import { allocPtrOut, decodeStringFromBuf, readPtrOut, toCString } from "./ffi-helpers.ts";
3
+ import { allocPtrOut, readPtrOut, toCString } from "./ffi-helpers.ts";
4
4
  import type { NativePointer } from "./loader.ts";
5
5
  import { getSymbols } from "./loader.ts";
6
6
 
@@ -13,7 +13,7 @@ export class LuaRunner {
13
13
  this._db = db;
14
14
  const lib = getSymbols();
15
15
  const outRunner = allocPtrOut();
16
- check(lib.quiver_lua_runner_new(db._handle, outRunner.ptr));
16
+ check(lib.quiver_lua_runner_new(db._handle, outRunner.buf));
17
17
  this._ptr = readPtrOut(outRunner);
18
18
  }
19
19
 
@@ -21,16 +21,7 @@ export class LuaRunner {
21
21
  this.ensureOpen();
22
22
  const lib = getSymbols();
23
23
  const scriptBuf = toCString(script);
24
- const err = lib.quiver_lua_runner_run(this._ptr, scriptBuf.buf);
25
- if (err !== 0) {
26
- const outError = allocPtrOut();
27
- const getErr = lib.quiver_lua_runner_get_error(this._ptr, outError.ptr);
28
- if (getErr === 0 && readPtrOut(outError)) {
29
- const msg = decodeStringFromBuf(outError);
30
- if (msg) throw new QuiverError(msg);
31
- }
32
- check(err);
33
- }
24
+ check(lib.quiver_lua_runner_run(this._ptr, scriptBuf.buf));
34
25
  }
35
26
 
36
27
  close(): void {
package/src/metadata.ts CHANGED
@@ -79,10 +79,9 @@ Database.prototype.getScalarMetadata = function (
79
79
  const collBuf = toCString(collection);
80
80
  const attrBuf = toCString(attribute);
81
81
  const outBuf = new Uint8Array(SCALAR_METADATA_SIZE);
82
- const outPtr = ptr(outBuf);
83
- check(lib.quiver_database_get_scalar_metadata(this._handle, collBuf.buf, attrBuf.buf, outPtr));
84
- const result = readScalarMetadataAt(outPtr, 0);
85
- lib.quiver_database_free_scalar_metadata(outPtr);
82
+ check(lib.quiver_database_get_scalar_metadata(this._handle, collBuf.buf, attrBuf.buf, outBuf));
83
+ const result = readScalarMetadataAt(ptr(outBuf), 0);
84
+ lib.quiver_database_free_scalar_metadata(outBuf);
86
85
  return result;
87
86
  };
88
87
 
@@ -95,10 +94,9 @@ Database.prototype.getVectorMetadata = function (
95
94
  const collBuf = toCString(collection);
96
95
  const groupBuf = toCString(groupName);
97
96
  const outBuf = new Uint8Array(GROUP_METADATA_SIZE);
98
- const outPtr = ptr(outBuf);
99
- check(lib.quiver_database_get_vector_metadata(this._handle, collBuf.buf, groupBuf.buf, outPtr));
100
- const result = readGroupMetadataAt(outPtr, 0);
101
- lib.quiver_database_free_group_metadata(outPtr);
97
+ check(lib.quiver_database_get_vector_metadata(this._handle, collBuf.buf, groupBuf.buf, outBuf));
98
+ const result = readGroupMetadataAt(ptr(outBuf), 0);
99
+ lib.quiver_database_free_group_metadata(outBuf);
102
100
  return result;
103
101
  };
104
102
 
@@ -111,10 +109,9 @@ Database.prototype.getSetMetadata = function (
111
109
  const collBuf = toCString(collection);
112
110
  const groupBuf = toCString(groupName);
113
111
  const outBuf = new Uint8Array(GROUP_METADATA_SIZE);
114
- const outPtr = ptr(outBuf);
115
- check(lib.quiver_database_get_set_metadata(this._handle, collBuf.buf, groupBuf.buf, outPtr));
116
- const result = readGroupMetadataAt(outPtr, 0);
117
- lib.quiver_database_free_group_metadata(outPtr);
112
+ check(lib.quiver_database_get_set_metadata(this._handle, collBuf.buf, groupBuf.buf, outBuf));
113
+ const result = readGroupMetadataAt(ptr(outBuf), 0);
114
+ lib.quiver_database_free_group_metadata(outBuf);
118
115
  return result;
119
116
  };
120
117
 
@@ -127,12 +124,11 @@ Database.prototype.getTimeSeriesMetadata = function (
127
124
  const collBuf = toCString(collection);
128
125
  const groupBuf = toCString(groupName);
129
126
  const outBuf = new Uint8Array(GROUP_METADATA_SIZE);
130
- const outPtr = ptr(outBuf);
131
127
  check(
132
- lib.quiver_database_get_time_series_metadata(this._handle, collBuf.buf, groupBuf.buf, outPtr),
128
+ lib.quiver_database_get_time_series_metadata(this._handle, collBuf.buf, groupBuf.buf, outBuf),
133
129
  );
134
- const result = readGroupMetadataAt(outPtr, 0);
135
- lib.quiver_database_free_group_metadata(outPtr);
130
+ const result = readGroupMetadataAt(ptr(outBuf), 0);
131
+ lib.quiver_database_free_group_metadata(outBuf);
136
132
  return result;
137
133
  };
138
134
 
@@ -148,7 +144,7 @@ function listMetadata<T>(
148
144
  const collBuf = toCString(collection);
149
145
  const outArray = allocPtrOut();
150
146
  const outCount = allocUint64Out();
151
- check((lib as Record<string, Function>)[fn](handle, collBuf.buf, outArray.ptr, outCount.ptr));
147
+ check((lib as Record<string, Function>)[fn](handle, collBuf.buf, outArray.buf, outCount.buf));
152
148
  const count = readUint64Out(outCount);
153
149
  if (count === 0) return [];
154
150
  const arrPtr = readPtrOut(outArray);
package/src/query.ts CHANGED
@@ -12,12 +12,14 @@ import {
12
12
  toCString,
13
13
  } from "./ffi-helpers.ts";
14
14
  import { getSymbols } from "./loader.ts";
15
- import type { Allocation, QueryParam } from "./types.ts";
16
-
17
- const DATA_TYPE_INTEGER = 0;
18
- const DATA_TYPE_FLOAT = 1;
19
- const DATA_TYPE_STRING = 2;
20
- const DATA_TYPE_NULL = 4;
15
+ import {
16
+ type Allocation,
17
+ DATA_TYPE_FLOAT,
18
+ DATA_TYPE_INTEGER,
19
+ DATA_TYPE_NULL,
20
+ DATA_TYPE_STRING,
21
+ type QueryParam,
22
+ } from "./types.ts";
21
23
 
22
24
  function marshalParams(parameters: QueryParam[]): {
23
25
  types: Allocation;
package/src/read.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { ptr } from "bun:ffi";
2
1
  import { Database } from "./database.ts";
3
2
  import { check } from "./errors.ts";
4
3
  import {
@@ -33,8 +32,8 @@ Database.prototype.readScalarIntegers = function (
33
32
  this._handle,
34
33
  collBuf.buf,
35
34
  attrBuf.buf,
36
- outValues.ptr,
37
- outCount.ptr,
35
+ outValues.buf,
36
+ outCount.buf,
38
37
  ),
39
38
  );
40
39
  const count = readUint64Out(outCount);
@@ -60,8 +59,8 @@ Database.prototype.readScalarFloats = function (
60
59
  this._handle,
61
60
  collBuf.buf,
62
61
  attrBuf.buf,
63
- outValues.ptr,
64
- outCount.ptr,
62
+ outValues.buf,
63
+ outCount.buf,
65
64
  ),
66
65
  );
67
66
  const count = readUint64Out(outCount);
@@ -87,8 +86,8 @@ Database.prototype.readScalarStrings = function (
87
86
  this._handle,
88
87
  collBuf.buf,
89
88
  attrBuf.buf,
90
- outValues.ptr,
91
- outCount.ptr,
89
+ outValues.buf,
90
+ outCount.buf,
92
91
  ),
93
92
  );
94
93
  const count = readUint64Out(outCount);
@@ -111,17 +110,15 @@ Database.prototype.readScalarIntegerById = function (
111
110
  const collBuf = toCString(collection);
112
111
  const attrBuf = toCString(attribute);
113
112
  const outValBuf = new Uint8Array(8);
114
- const outValPtr = ptr(outValBuf);
115
113
  const outHasBuf = new Uint8Array(4);
116
- const outHasPtr = ptr(outHasBuf);
117
114
  check(
118
115
  lib.quiver_database_read_scalar_integer_by_id(
119
116
  this._handle,
120
117
  collBuf.buf,
121
118
  attrBuf.buf,
122
119
  BigInt(id),
123
- outValPtr,
124
- outHasPtr,
120
+ outValBuf,
121
+ outHasBuf,
125
122
  ),
126
123
  );
127
124
  if (new DataView(outHasBuf.buffer).getInt32(0, true) === 0) return null;
@@ -138,17 +135,15 @@ Database.prototype.readScalarFloatById = function (
138
135
  const collBuf = toCString(collection);
139
136
  const attrBuf = toCString(attribute);
140
137
  const outValBuf = new Uint8Array(8);
141
- const outValPtr = ptr(outValBuf);
142
138
  const outHasBuf = new Uint8Array(4);
143
- const outHasPtr = ptr(outHasBuf);
144
139
  check(
145
140
  lib.quiver_database_read_scalar_float_by_id(
146
141
  this._handle,
147
142
  collBuf.buf,
148
143
  attrBuf.buf,
149
144
  BigInt(id),
150
- outValPtr,
151
- outHasPtr,
145
+ outValBuf,
146
+ outHasBuf,
152
147
  ),
153
148
  );
154
149
  if (new DataView(outHasBuf.buffer).getInt32(0, true) === 0) return null;
@@ -166,15 +161,14 @@ Database.prototype.readScalarStringById = function (
166
161
  const attrBuf = toCString(attribute);
167
162
  const outValue = allocPtrOut();
168
163
  const outHasBuf = new Uint8Array(4);
169
- const outHasPtr = ptr(outHasBuf);
170
164
  check(
171
165
  lib.quiver_database_read_scalar_string_by_id(
172
166
  this._handle,
173
167
  collBuf.buf,
174
168
  attrBuf.buf,
175
169
  BigInt(id),
176
- outValue.ptr,
177
- outHasPtr,
170
+ outValue.buf,
171
+ outHasBuf,
178
172
  ),
179
173
  );
180
174
  if (new DataView(outHasBuf.buffer).getInt32(0, true) === 0) return null;
@@ -190,7 +184,7 @@ Database.prototype.readElementIds = function (this: Database, collection: string
190
184
  const collBuf = toCString(collection);
191
185
  const outIds = allocPtrOut();
192
186
  const outCount = allocUint64Out();
193
- check(lib.quiver_database_read_element_ids(this._handle, collBuf.buf, outIds.ptr, outCount.ptr));
187
+ check(lib.quiver_database_read_element_ids(this._handle, collBuf.buf, outIds.buf, outCount.buf));
194
188
  const count = readUint64Out(outCount);
195
189
  if (count === 0) return [];
196
190
  const arrPtr = readPtrOut(outIds);
@@ -218,9 +212,9 @@ function readBulkIntegers(
218
212
  handle,
219
213
  collBuf.buf,
220
214
  attrBuf.buf,
221
- outVectors.ptr,
222
- outSizes.ptr,
223
- outCount.ptr,
215
+ outVectors.buf,
216
+ outSizes.buf,
217
+ outCount.buf,
224
218
  ),
225
219
  );
226
220
  const count = readUint64Out(outCount);
@@ -254,9 +248,9 @@ function readBulkFloats(
254
248
  handle,
255
249
  collBuf.buf,
256
250
  attrBuf.buf,
257
- outVectors.ptr,
258
- outSizes.ptr,
259
- outCount.ptr,
251
+ outVectors.buf,
252
+ outSizes.buf,
253
+ outCount.buf,
260
254
  ),
261
255
  );
262
256
  const count = readUint64Out(outCount);
@@ -290,9 +284,9 @@ function readBulkStrings(
290
284
  handle,
291
285
  collBuf.buf,
292
286
  attrBuf.buf,
293
- outVectors.ptr,
294
- outSizes.ptr,
295
- outCount.ptr,
287
+ outVectors.buf,
288
+ outSizes.buf,
289
+ outCount.buf,
296
290
  ),
297
291
  );
298
292
  const count = readUint64Out(outCount);
@@ -408,8 +402,8 @@ function readByIdIntegers(
408
402
  collBuf.buf,
409
403
  attrBuf.buf,
410
404
  BigInt(id),
411
- outValues.ptr,
412
- outCount.ptr,
405
+ outValues.buf,
406
+ outCount.buf,
413
407
  ),
414
408
  );
415
409
  const count = readUint64Out(outCount);
@@ -438,8 +432,8 @@ function readByIdFloats(
438
432
  collBuf.buf,
439
433
  attrBuf.buf,
440
434
  BigInt(id),
441
- outValues.ptr,
442
- outCount.ptr,
435
+ outValues.buf,
436
+ outCount.buf,
443
437
  ),
444
438
  );
445
439
  const count = readUint64Out(outCount);
@@ -468,8 +462,8 @@ function readByIdStrings(
468
462
  collBuf.buf,
469
463
  attrBuf.buf,
470
464
  BigInt(id),
471
- outValues.ptr,
472
- outCount.ptr,
465
+ outValues.buf,
466
+ outCount.buf,
473
467
  ),
474
468
  );
475
469
  const count = readUint64Out(outCount);
@@ -1,6 +1,6 @@
1
1
  import { CString, type Pointer, ptr, read, toArrayBuffer } from "bun:ffi";
2
2
  import { Database } from "./database.ts";
3
- import { check } from "./errors.ts";
3
+ import { check, QuiverError } from "./errors.ts";
4
4
  import {
5
5
  allocNativeFloat64,
6
6
  allocNativeInt64,
@@ -18,15 +18,16 @@ import {
18
18
  toCString,
19
19
  } from "./ffi-helpers.ts";
20
20
  import { getSymbols } from "./loader.ts";
21
- import type { Allocation } from "./types.ts";
21
+ import {
22
+ type Allocation,
23
+ DATA_TYPE_DATE_TIME,
24
+ DATA_TYPE_FLOAT,
25
+ DATA_TYPE_INTEGER,
26
+ DATA_TYPE_STRING,
27
+ } from "./types.ts";
22
28
 
23
29
  export type TimeSeriesData = Record<string, (number | string)[]>;
24
30
 
25
- const DATA_TYPE_INTEGER = 0;
26
- const DATA_TYPE_FLOAT = 1;
27
- const DATA_TYPE_STRING = 2;
28
- const DATA_TYPE_DATE_TIME = 3;
29
-
30
31
  Database.prototype.readTimeSeriesGroup = function (
31
32
  this: Database,
32
33
  collection: string,
@@ -48,11 +49,11 @@ Database.prototype.readTimeSeriesGroup = function (
48
49
  collBuf.buf,
49
50
  grpBuf.buf,
50
51
  BigInt(id),
51
- outNames.ptr,
52
- outTypes.ptr,
53
- outData.ptr,
54
- outColCount.ptr,
55
- outRowCount.ptr,
52
+ outNames.buf,
53
+ outTypes.buf,
54
+ outData.buf,
55
+ outColCount.buf,
56
+ outRowCount.buf,
56
57
  ),
57
58
  );
58
59
 
@@ -96,6 +97,64 @@ Database.prototype.readTimeSeriesGroup = function (
96
97
  return result;
97
98
  };
98
99
 
100
+ Database.prototype.readTimeSeriesRow = function (
101
+ this: Database,
102
+ collection: string,
103
+ group: string,
104
+ attribute: string,
105
+ dateTime: string,
106
+ ): (number | string | null)[] {
107
+ const lib = getSymbols();
108
+ const collBuf = toCString(collection);
109
+ const grpBuf = toCString(group);
110
+ const attrBuf = toCString(attribute);
111
+ const dtBuf = toCString(dateTime);
112
+ const outDataType = new Uint8Array(4);
113
+ const outValues = allocPtrOut();
114
+ const outCount = allocUint64Out();
115
+
116
+ check(
117
+ lib.quiver_database_read_time_series_row(
118
+ this._handle,
119
+ collBuf.buf,
120
+ grpBuf.buf,
121
+ attrBuf.buf,
122
+ dtBuf.buf,
123
+ outDataType,
124
+ outValues.buf,
125
+ outCount.buf,
126
+ ),
127
+ );
128
+
129
+ const count = readUint64Out(outCount);
130
+ const valuesPtr = readPtrOut(outValues);
131
+ if (count === 0 || !valuesPtr) return [];
132
+
133
+ const dataType = new DataView(outDataType.buffer).getInt32(0, true);
134
+ switch (dataType) {
135
+ case DATA_TYPE_INTEGER: {
136
+ const result = decodeInt64Array(valuesPtr, count);
137
+ lib.quiver_database_free_integer_array(valuesPtr);
138
+ return result;
139
+ }
140
+ case DATA_TYPE_FLOAT: {
141
+ const result = decodeFloat64Array(valuesPtr, count);
142
+ lib.quiver_database_free_float_array(valuesPtr);
143
+ return result;
144
+ }
145
+ default: {
146
+ // STRING or DATE_TIME; NULL entries mark elements with no data
147
+ const result: (string | null)[] = new Array(count);
148
+ for (let i = 0; i < count; i++) {
149
+ const strPtr = read.ptr(valuesPtr as Pointer, i * 8);
150
+ result[i] = strPtr === 0 ? null : new CString(strPtr as Pointer).toString();
151
+ }
152
+ lib.quiver_database_free_string_array(valuesPtr, BigInt(count));
153
+ return result;
154
+ }
155
+ }
156
+ };
157
+
99
158
  Database.prototype.updateTimeSeriesGroup = function (
100
159
  this: Database,
101
160
  collection: string,
@@ -127,6 +186,26 @@ Database.prototype.updateTimeSeriesGroup = function (
127
186
 
128
187
  const columnCount = entries.length;
129
188
  const rowCount = entries[0][1].length;
189
+
190
+ // Validate before marshalling: a jagged column would desync the parallel
191
+ // arrays the C API reads against row_count, and a zero-length column (with
192
+ // columns present) would otherwise marshal a null data pointer that the C API
193
+ // dereferences. Named-but-empty columns are a caller mistake -- pass {} to
194
+ // clear the group instead.
195
+ for (const [name, values] of entries) {
196
+ if (values.length !== rowCount) {
197
+ throw new QuiverError(
198
+ `Cannot updateTimeSeriesGroup: column '${name}' has length ${values.length} but expected ${rowCount}`,
199
+ );
200
+ }
201
+ }
202
+ if (rowCount === 0) {
203
+ const names = entries.map(([name]) => name).join(", ");
204
+ throw new QuiverError(
205
+ `Cannot updateTimeSeriesGroup: columns [${names}] contain no rows; pass {} to clear the group`,
206
+ );
207
+ }
208
+
130
209
  const keepalive: Allocation[] = [];
131
210
 
132
211
  // Build column names as native string array
@@ -142,10 +221,7 @@ Database.prototype.updateTimeSeriesGroup = function (
142
221
  for (let c = 0; c < columnCount; c++) {
143
222
  const values = entries[c][1];
144
223
 
145
- if (values.length === 0) {
146
- typesDv.setInt32(c * 4, DATA_TYPE_STRING, true);
147
- dataPtrs.push(null);
148
- } else if (typeof values[0] === "string") {
224
+ if (typeof values[0] === "string") {
149
225
  typesDv.setInt32(c * 4, DATA_TYPE_STRING, true);
150
226
  const { table, keepalive: strPtrs } = allocNativeStringArray(values as string[]);
151
227
  keepalive.push(table, ...strPtrs);
@@ -177,15 +253,76 @@ Database.prototype.updateTimeSeriesGroup = function (
177
253
  collBuf.buf,
178
254
  grpBuf.buf,
179
255
  BigInt(id),
180
- namesTable.ptr,
181
- typesAlloc.ptr,
182
- dataTable.ptr,
256
+ namesTable.buf,
257
+ typesAlloc.buf,
258
+ dataTable.buf,
183
259
  BigInt(columnCount),
184
260
  BigInt(rowCount),
185
261
  ),
186
262
  );
187
263
  };
188
264
 
265
+ Database.prototype.addTimeSeriesRow = function (
266
+ this: Database,
267
+ collection: string,
268
+ group: string,
269
+ id: number,
270
+ row: Record<string, number | bigint | string>,
271
+ ): void {
272
+ const lib = getSymbols();
273
+ const collBuf = toCString(collection);
274
+ const grpBuf = toCString(group);
275
+ const entries = Object.entries(row);
276
+ const columnCount = entries.length;
277
+ const keepalive: Allocation[] = [];
278
+
279
+ const colNames = entries.map(([name]) => name);
280
+ const { table: namesTable, keepalive: namesPtrs } = allocNativeStringArray(colNames);
281
+ keepalive.push(namesTable, ...namesPtrs);
282
+
283
+ const typesBuf = new Uint8Array(columnCount * 4);
284
+ const typesDv = new DataView(typesBuf.buffer);
285
+ const dataPtrs: (Pointer | null)[] = [];
286
+
287
+ for (let c = 0; c < columnCount; c++) {
288
+ const value = entries[c][1];
289
+ if (typeof value === "string") {
290
+ typesDv.setInt32(c * 4, DATA_TYPE_STRING, true);
291
+ const { table, keepalive: strPtrs } = allocNativeStringArray([value]);
292
+ keepalive.push(table, ...strPtrs);
293
+ dataPtrs.push(table.ptr);
294
+ } else if (typeof value === "bigint" || Number.isInteger(value)) {
295
+ typesDv.setInt32(c * 4, DATA_TYPE_INTEGER, true);
296
+ const p = allocNativeInt64([value]);
297
+ keepalive.push(p);
298
+ dataPtrs.push(p.ptr);
299
+ } else {
300
+ typesDv.setInt32(c * 4, DATA_TYPE_FLOAT, true);
301
+ const p = allocNativeFloat64([value as number]);
302
+ keepalive.push(p);
303
+ dataPtrs.push(p.ptr);
304
+ }
305
+ }
306
+
307
+ const typesAlloc: Allocation = { ptr: ptr(typesBuf), buf: typesBuf };
308
+ keepalive.push(typesAlloc);
309
+ const dataTable = allocNativePtrTable(dataPtrs);
310
+ keepalive.push(dataTable);
311
+
312
+ check(
313
+ lib.quiver_database_add_time_series_row(
314
+ this._handle,
315
+ collBuf.buf,
316
+ grpBuf.buf,
317
+ BigInt(id),
318
+ namesTable.buf,
319
+ typesAlloc.buf,
320
+ dataTable.buf,
321
+ BigInt(columnCount),
322
+ ),
323
+ );
324
+ };
325
+
189
326
  Database.prototype.hasTimeSeriesFiles = function (this: Database, collection: string): boolean {
190
327
  const lib = getSymbols();
191
328
  const collBuf = toCString(collection);
@@ -206,8 +343,8 @@ Database.prototype.listTimeSeriesFilesColumns = function (
206
343
  lib.quiver_database_list_time_series_files_columns(
207
344
  this._handle,
208
345
  collBuf.buf,
209
- outColumns.ptr,
210
- outCount.ptr,
346
+ outColumns.buf,
347
+ outCount.buf,
211
348
  ),
212
349
  );
213
350
  const count = readUint64Out(outCount);
@@ -231,9 +368,9 @@ Database.prototype.readTimeSeriesFiles = function (
231
368
  lib.quiver_database_read_time_series_files(
232
369
  this._handle,
233
370
  collBuf.buf,
234
- outColumns.ptr,
235
- outPaths.ptr,
236
- outCount.ptr,
371
+ outColumns.buf,
372
+ outPaths.buf,
373
+ outCount.buf,
237
374
  ),
238
375
  );
239
376
  const count = readUint64Out(outCount);
@@ -283,8 +420,8 @@ Database.prototype.updateTimeSeriesFiles = function (
283
420
  lib.quiver_database_update_time_series_files(
284
421
  this._handle,
285
422
  collBuf.buf,
286
- colsTable.ptr,
287
- pathsTable.ptr,
423
+ colsTable.buf,
424
+ pathsTable.buf,
288
425
  BigInt(entries.length),
289
426
  ),
290
427
  );
@@ -1,4 +1,3 @@
1
- import { ptr } from "bun:ffi";
2
1
  import { Database } from "./database.ts";
3
2
  import { check } from "./errors.ts";
4
3
  import { getSymbols } from "./loader.ts";
@@ -21,7 +20,6 @@ Database.prototype.rollback = function (this: Database): void {
21
20
  Database.prototype.inTransaction = function (this: Database): boolean {
22
21
  const lib = getSymbols();
23
22
  const outBuf = new Uint8Array(4);
24
- const outPtr = ptr(outBuf);
25
- check(lib.quiver_database_in_transaction(this._handle, outPtr));
23
+ check(lib.quiver_database_in_transaction(this._handle, outBuf));
26
24
  return new DataView(outBuf.buffer).getInt32(0, true) !== 0;
27
25
  };
package/src/types.ts CHANGED
@@ -1,5 +1,26 @@
1
1
  import type { Pointer } from "bun:ffi";
2
2
 
3
+ // Mirrors quiver_data_type_t in the C API
4
+ export const DATA_TYPE_INTEGER = 0;
5
+ export const DATA_TYPE_FLOAT = 1;
6
+ export const DATA_TYPE_STRING = 2;
7
+ export const DATA_TYPE_DATE_TIME = 3;
8
+ export const DATA_TYPE_NULL = 4;
9
+
10
+ // Mirrors quiver_log_level_t in the C API
11
+ export const LOG_LEVEL_DEBUG = 0;
12
+ export const LOG_LEVEL_INFO = 1;
13
+ export const LOG_LEVEL_WARN = 2;
14
+ export const LOG_LEVEL_ERROR = 3;
15
+ export const LOG_LEVEL_OFF = 4;
16
+
17
+ /** Options for opening or creating a database. */
18
+ export type DatabaseOptions = {
19
+ readOnly?: boolean;
20
+ /** A LOG_LEVEL_* constant; defaults to LOG_LEVEL_INFO. */
21
+ consoleLevel?: number;
22
+ };
23
+
3
24
  export type ScalarValue = number | bigint | string | null;
4
25
  export type ArrayValue = number[] | bigint[] | string[];
5
26
  export type Value = ScalarValue | ArrayValue;