quiverdb 0.12.5 → 0.12.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.
- package/README.md +6 -3
- 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/mod.ts +1 -13
- package/package.json +1 -1
- package/src/create.ts +21 -23
- package/src/group-columns.ts +48 -19
- package/src/index.ts +5 -0
- package/src/query.ts +5 -0
- package/src/types.ts +2 -1
package/README.md
CHANGED
|
@@ -135,8 +135,8 @@ bun run example.ts
|
|
|
135
135
|
- `queryBoolean(sql, parameters?)` -- Query returning an INTEGER-backed boolean or null
|
|
136
136
|
- `queryFloat(sql, parameters?)` -- Query returning float or null
|
|
137
137
|
|
|
138
|
-
Parameters are passed as an array of `number | boolean | string | null` (a `
|
|
139
|
-
INTEGER 1 or 0).
|
|
138
|
+
Parameters are passed as an array of `number | bigint | boolean | string | null` (a `bigint` binds
|
|
139
|
+
as an exact INTEGER, a `boolean` as the INTEGER 1 or 0).
|
|
140
140
|
|
|
141
141
|
### Transaction
|
|
142
142
|
|
|
@@ -182,12 +182,15 @@ Exported types available for TypeScript consumers:
|
|
|
182
182
|
- `ArrayValue` -- `number[] | bigint[] | string[]`
|
|
183
183
|
- `Value` -- `ScalarValue | ArrayValue`
|
|
184
184
|
- `ElementData` -- `Record<string, Value | undefined>`
|
|
185
|
-
- `QueryParam` -- `number | string | null`
|
|
185
|
+
- `QueryParam` -- `number | bigint | boolean | string | null`
|
|
186
186
|
- `QuiverError` -- Error class for all Quiver operations
|
|
187
187
|
- `ScalarMetadata` -- Scalar attribute metadata
|
|
188
188
|
- `GroupMetadata` -- Vector/set/time series group metadata
|
|
189
189
|
- `TimeSeriesData` -- Time series column data
|
|
190
190
|
- `CsvOptions` -- CSV import/export options
|
|
191
|
+
- `DatabaseOptions` -- `{ readOnly?: boolean; consoleLevel?: number }` (a `LOG_LEVEL_*` constant)
|
|
192
|
+
- `LOG_LEVEL_DEBUG | INFO | WARN | ERROR | OFF`, `DATA_TYPE_INTEGER | FLOAT | STRING | DATE_TIME | NULL`
|
|
193
|
+
-- constants for `DatabaseOptions.consoleLevel` and metadata `dataType`
|
|
191
194
|
|
|
192
195
|
## Development
|
|
193
196
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/mod.ts
CHANGED
|
@@ -11,16 +11,4 @@
|
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
export
|
|
15
|
-
ArrayValue,
|
|
16
|
-
CsvOptions,
|
|
17
|
-
ElementData,
|
|
18
|
-
GroupColumns,
|
|
19
|
-
GroupMetadata,
|
|
20
|
-
QueryParam,
|
|
21
|
-
ScalarMetadata,
|
|
22
|
-
ScalarValue,
|
|
23
|
-
TimeSeriesData,
|
|
24
|
-
Value,
|
|
25
|
-
} from "./src/index.ts";
|
|
26
|
-
export { Database, LUA_DB_API_REFERENCE, LuaRunner, QuiverError } from "./src/index.ts";
|
|
14
|
+
export * from "./src/index.ts";
|
package/package.json
CHANGED
package/src/create.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
readPtrOut,
|
|
9
9
|
toCString,
|
|
10
10
|
} from "./ffi-helpers.ts";
|
|
11
|
-
import { type GroupColumns, updateGroupColumns } from "./group-columns.ts";
|
|
11
|
+
import { type GroupColumns, numericCells, updateGroupColumns } from "./group-columns.ts";
|
|
12
12
|
import { getSymbols, type NativePointer } from "./loader.ts";
|
|
13
13
|
import type { ElementData, Value } from "./types.ts";
|
|
14
14
|
|
|
@@ -17,6 +17,7 @@ type Symbols = ReturnType<typeof getSymbols>;
|
|
|
17
17
|
function setElementArray(
|
|
18
18
|
lib: Symbols,
|
|
19
19
|
elemPtr: NativePointer,
|
|
20
|
+
caller: string,
|
|
20
21
|
name: string,
|
|
21
22
|
values: unknown[],
|
|
22
23
|
): void {
|
|
@@ -39,27 +40,18 @@ function setElementArray(
|
|
|
39
40
|
);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
if (typeof first === "bigint") {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (typeof first === "boolean") {
|
|
49
|
-
const arr = allocNativeInt64((values as boolean[]).map((v) => (v ? 1 : 0)));
|
|
50
|
-
check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.buf, values.length, null));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (typeof first === "number") {
|
|
55
|
-
const allIntegers = (values as number[]).every((v) => Number.isInteger(v));
|
|
56
|
-
if (allIntegers) {
|
|
57
|
-
const arr = allocNativeInt64(values as number[]);
|
|
43
|
+
if (typeof first === "number" || typeof first === "boolean" || typeof first === "bigint") {
|
|
44
|
+
// The null check above leaves no null cell, so the cast only narrows.
|
|
45
|
+
const cells = numericCells(caller, name, values) as (number | bigint)[];
|
|
46
|
+
if (cells.every((v) => typeof v === "bigint" || Number.isInteger(v))) {
|
|
47
|
+
const arr = allocNativeInt64(cells);
|
|
58
48
|
check(
|
|
59
49
|
lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.buf, values.length, null),
|
|
60
50
|
);
|
|
61
51
|
} else {
|
|
62
|
-
|
|
52
|
+
// A fractional cell makes the array FLOAT; a bigint cell then follows the int-for-REAL rule
|
|
53
|
+
// through Number(), exact up to 2^53 like any JS number.
|
|
54
|
+
const arr = allocNativeFloat64(cells.map(Number));
|
|
63
55
|
check(lib.quiver_element_set_array_float(elemPtr, nameBuf.buf, arr.buf, values.length, null));
|
|
64
56
|
}
|
|
65
57
|
return;
|
|
@@ -76,7 +68,13 @@ function setElementArray(
|
|
|
76
68
|
throw new QuiverError(`Unsupported array element type for '${name}': ${typeof first}`);
|
|
77
69
|
}
|
|
78
70
|
|
|
79
|
-
function setElementField(
|
|
71
|
+
function setElementField(
|
|
72
|
+
lib: Symbols,
|
|
73
|
+
elemPtr: NativePointer,
|
|
74
|
+
caller: string,
|
|
75
|
+
name: string,
|
|
76
|
+
value: Value,
|
|
77
|
+
): void {
|
|
80
78
|
const nameBuf = toCString(name);
|
|
81
79
|
|
|
82
80
|
if (value === null) {
|
|
@@ -110,7 +108,7 @@ function setElementField(lib: Symbols, elemPtr: NativePointer, name: string, val
|
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
if (Array.isArray(value)) {
|
|
113
|
-
setElementArray(lib, elemPtr, name, value);
|
|
111
|
+
setElementArray(lib, elemPtr, caller, name, value);
|
|
114
112
|
return;
|
|
115
113
|
}
|
|
116
114
|
|
|
@@ -132,7 +130,7 @@ Database.prototype.createElement = function (
|
|
|
132
130
|
try {
|
|
133
131
|
for (const [key, value] of Object.entries(data)) {
|
|
134
132
|
if (value === undefined) continue;
|
|
135
|
-
setElementField(lib, elemPtr, key, value);
|
|
133
|
+
setElementField(lib, elemPtr, "createElement", key, value);
|
|
136
134
|
}
|
|
137
135
|
|
|
138
136
|
const outIdBuf = new Uint8Array(8);
|
|
@@ -160,7 +158,7 @@ Database.prototype.updateElement = function (
|
|
|
160
158
|
try {
|
|
161
159
|
for (const [key, value] of Object.entries(data)) {
|
|
162
160
|
if (value === undefined) continue;
|
|
163
|
-
setElementField(lib, elemPtr, key, value);
|
|
161
|
+
setElementField(lib, elemPtr, "updateElement", key, value);
|
|
164
162
|
}
|
|
165
163
|
const collBuf = toCString(collection);
|
|
166
164
|
check(lib.quiver_database_update_element(handle, collBuf.buf, BigInt(id), elemPtr));
|
|
@@ -186,7 +184,7 @@ Database.prototype.updateElementByLabel = function (
|
|
|
186
184
|
try {
|
|
187
185
|
for (const [key, value] of Object.entries(data)) {
|
|
188
186
|
if (value === undefined) continue;
|
|
189
|
-
setElementField(lib, elemPtr, key, value);
|
|
187
|
+
setElementField(lib, elemPtr, "updateElementByLabel", key, value);
|
|
190
188
|
}
|
|
191
189
|
const collBuf = toCString(collection);
|
|
192
190
|
const labelBuf = toCString(label);
|
package/src/group-columns.ts
CHANGED
|
@@ -24,12 +24,16 @@ import {
|
|
|
24
24
|
DATA_TYPE_STRING,
|
|
25
25
|
} from "./types.ts";
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Column-oriented group payload: one array of cells per column name, `null` for SQL NULL. A
|
|
29
|
+
* `bigint` cell is written as an exact int64 (like createElement); a `boolean` as INTEGER 1/0.
|
|
30
|
+
*/
|
|
31
|
+
export type GroupColumns = Record<string, (number | bigint | string | boolean | null)[]>;
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
34
|
* Column-oriented group read result: one array of cells per column name, `null` for SQL NULL.
|
|
32
|
-
* The read-side twin of GroupColumns; no reader produces a boolean, so it admits
|
|
35
|
+
* The read-side twin of GroupColumns; no reader produces a boolean or a bigint, so it admits
|
|
36
|
+
* neither.
|
|
33
37
|
*/
|
|
34
38
|
export type TimeSeriesData = Record<string, (number | string | null)[]>;
|
|
35
39
|
|
|
@@ -51,6 +55,34 @@ type ColumnUpdateFn = (
|
|
|
51
55
|
rowCount: bigint,
|
|
52
56
|
) => number;
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Check and normalize the cells of a numeric column: one whose first non-null cell is a number, a
|
|
60
|
+
* bigint or a boolean. Shared by updateGroupColumns and setElementArray (create.ts) so the group
|
|
61
|
+
* writers and the element arrays agree on every cell. String columns are never passed here.
|
|
62
|
+
*
|
|
63
|
+
* - A boolean becomes INTEGER 1/0, since SQLite has no boolean type. Per cell, because mapping the
|
|
64
|
+
* whole column from a leading boolean truthiness-mapped the rest and rewrote [true, 5] as
|
|
65
|
+
* [1, 1]. Before the INTEGER/FLOAT choice, because Number.isInteger(true) is false.
|
|
66
|
+
* - A bigint stays a bigint, so an INTEGER column keeps it exact; the caller maps it through
|
|
67
|
+
* Number() only if the column turns out FLOAT.
|
|
68
|
+
* - Any other non-null cell throws, naming the column and the cell. It would fail
|
|
69
|
+
* Number.isInteger, tag the column FLOAT and reach DataView.setFloat64, which converts it with no
|
|
70
|
+
* error: "abc" to NaN, which SQLite stores as NULL, and "2" to 2.
|
|
71
|
+
*/
|
|
72
|
+
export function numericCells(
|
|
73
|
+
caller: string,
|
|
74
|
+
name: string,
|
|
75
|
+
values: readonly unknown[],
|
|
76
|
+
): (number | bigint | null)[] {
|
|
77
|
+
return values.map((v, r) => {
|
|
78
|
+
if (v === null || typeof v === "number" || typeof v === "bigint") return v;
|
|
79
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
80
|
+
throw new QuiverError(
|
|
81
|
+
`Cannot ${caller}: numeric column '${name}' has unsupported value type ${typeof v} in cell ${r}`,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
54
86
|
/**
|
|
55
87
|
* Marshal a column-oriented payload and forward it to one of the columnar group update C
|
|
56
88
|
* functions. Shared by updateTimeSeriesGroup / updateVectorGroup / updateSetGroup and their
|
|
@@ -114,16 +146,7 @@ export function updateGroupColumns(
|
|
|
114
146
|
const maskPtrs: (Pointer | null)[] = [];
|
|
115
147
|
|
|
116
148
|
for (let c = 0; c < columnCount; c++) {
|
|
117
|
-
const [colName,
|
|
118
|
-
// SQLite has no boolean type: a boolean is INTEGER 1/0, as in setElementField and
|
|
119
|
-
// marshalParams. Normalizing per cell before the dispatch (rather than adding a boolean
|
|
120
|
-
// branch after it) is what makes a mixed [true, 5] column write 1 and 5 instead of
|
|
121
|
-
// truthiness-mapping every cell, and matches Python's per-cell `int(v)`. A string column is
|
|
122
|
-
// left alone so normalizing cannot change what a mixed ['a', true] column already wrote.
|
|
123
|
-
const isStringColumn = typeof rawValues.find((v) => v !== null) === "string";
|
|
124
|
-
const values = isStringColumn
|
|
125
|
-
? rawValues
|
|
126
|
-
: rawValues.map((v) => (typeof v === "boolean" ? (v ? 1 : 0) : v));
|
|
149
|
+
const [colName, values] = entries[c];
|
|
127
150
|
const first = values.find((v) => v !== null);
|
|
128
151
|
|
|
129
152
|
// Mask via direct indexing — never a DataView, to avoid the documented
|
|
@@ -141,23 +164,29 @@ export function updateGroupColumns(
|
|
|
141
164
|
keepalive.push(p);
|
|
142
165
|
dataPtrs.push(p.ptr);
|
|
143
166
|
} else if (typeof first === "string") {
|
|
167
|
+
// Not checked by numericCells: a mixed ['a', true] column still writes what it always wrote.
|
|
144
168
|
typesDv.setInt32(c * 4, DATA_TYPE_STRING, true);
|
|
145
169
|
const { table, keepalive: strPtrs } = allocNativeStringArray(
|
|
146
170
|
values.map((v) => (v === null ? null : (v as string))),
|
|
147
171
|
);
|
|
148
172
|
keepalive.push(table, ...strPtrs);
|
|
149
173
|
dataPtrs.push(table.ptr);
|
|
150
|
-
} else if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
} else if (
|
|
175
|
+
typeof first === "number" ||
|
|
176
|
+
typeof first === "boolean" ||
|
|
177
|
+
typeof first === "bigint"
|
|
178
|
+
) {
|
|
179
|
+
const cells = numericCells(caller, colName, values);
|
|
180
|
+
if (cells.every((v) => v === null || typeof v === "bigint" || Number.isInteger(v))) {
|
|
154
181
|
typesDv.setInt32(c * 4, DATA_TYPE_INTEGER, true);
|
|
155
|
-
const p = allocNativeInt64(
|
|
182
|
+
const p = allocNativeInt64(cells.map((v) => v ?? 0));
|
|
156
183
|
keepalive.push(p);
|
|
157
184
|
dataPtrs.push(p.ptr);
|
|
158
185
|
} else {
|
|
186
|
+
// A fractional cell makes the column FLOAT; a bigint cell then follows the int-for-REAL
|
|
187
|
+
// rule through Number(), exact up to 2^53 like any JS number.
|
|
159
188
|
typesDv.setInt32(c * 4, DATA_TYPE_FLOAT, true);
|
|
160
|
-
const p = allocNativeFloat64(
|
|
189
|
+
const p = allocNativeFloat64(cells.map((v) => (v === null ? 0 : Number(v))));
|
|
161
190
|
keepalive.push(p);
|
|
162
191
|
dataPtrs.push(p.ptr);
|
|
163
192
|
}
|
package/src/index.ts
CHANGED
package/src/query.ts
CHANGED
|
@@ -50,6 +50,11 @@ function marshalParams(parameters?: QueryParam[]): {
|
|
|
50
50
|
const native = allocNativeInt64([p ? 1 : 0]);
|
|
51
51
|
keepalive.push(native);
|
|
52
52
|
valuesDv.setBigInt64(i * 8, nativeAddress(native.ptr), true);
|
|
53
|
+
} else if (typeof p === "bigint") {
|
|
54
|
+
typesDv.setInt32(i * 4, DATA_TYPE_INTEGER, true);
|
|
55
|
+
const native = allocNativeInt64([p]);
|
|
56
|
+
keepalive.push(native);
|
|
57
|
+
valuesDv.setBigInt64(i * 8, nativeAddress(native.ptr), true);
|
|
53
58
|
} else if (typeof p === "number") {
|
|
54
59
|
if (Number.isInteger(p)) {
|
|
55
60
|
typesDv.setInt32(i * 4, DATA_TYPE_INTEGER, true);
|
package/src/types.ts
CHANGED
|
@@ -26,7 +26,8 @@ export type ScalarValue = number | bigint | boolean | string | null;
|
|
|
26
26
|
export type ArrayValue = number[] | bigint[] | boolean[] | string[];
|
|
27
27
|
export type Value = ScalarValue | ArrayValue;
|
|
28
28
|
export type ElementData = Record<string, Value | undefined>;
|
|
29
|
-
|
|
29
|
+
/** A `bigint` binds as an exact INTEGER; a `boolean` as INTEGER 1/0. */
|
|
30
|
+
export type QueryParam = number | bigint | boolean | string | null;
|
|
30
31
|
|
|
31
32
|
/** Native memory allocation result. Callers MUST hold `buf` in scope to prevent GC. */
|
|
32
33
|
export type Allocation = {
|