@thi.ng/column-store 0.7.0 → 0.8.0
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 +13 -3
- package/api.d.ts +2 -2
- package/columns/acolumn.d.ts +2 -1
- package/columns/acolumn.js +3 -0
- package/columns/vector.d.ts +1 -1
- package/internal/serialize.d.ts +1 -1
- package/package.json +2 -2
- package/query.d.ts +5 -0
- package/query.js +54 -38
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
- [Serialization options](#serialization-options)
|
|
22
22
|
- [Custom column types](#custom-column-types)
|
|
23
23
|
- [Cardinality](#cardinality)
|
|
24
|
+
- [Differences between tuple and vector column types](#differences-between-tuple-and-vector-column-types)
|
|
24
25
|
- [Default values](#default-values)
|
|
25
26
|
- [Flags](#flags)
|
|
26
27
|
- [FLAG_BITMAP](#flag_bitmap)
|
|
@@ -143,10 +144,11 @@ Note: Booleans and `BigInt`s are still unsupported, but being worked on...
|
|
|
143
144
|
| `i16` | 16bit signed int | ❌ | ✅ |
|
|
144
145
|
| `u32` | 32bit unsigned int | ❌ | ✅ |
|
|
145
146
|
| `i32` | 32bit signed int | ❌ | ✅ |
|
|
146
|
-
| `f32` | 32bit float | ❌ |
|
|
147
|
-
| `f64` | 64bit float | ❌ |
|
|
147
|
+
| `f32` | 32bit float | ❌ | ✅ <sup>(2)</sup> |
|
|
148
|
+
| `f64` | 64bit float | ❌ | ✅ <sup>(2)</sup> |
|
|
148
149
|
|
|
149
150
|
- <sup>(1)</sup> only if max. cardinality is 1, [further information](#flag_rle)
|
|
151
|
+
- <sup>(2)</sup> simple RLE only, [further information](#flag_rle)
|
|
150
152
|
|
|
151
153
|
### Vector column types
|
|
152
154
|
|
|
@@ -223,6 +225,14 @@ key of the column spec. The following presets are provided:
|
|
|
223
225
|
| `ONE_PLUS` | `[1, (2**32)-1]` | One or more values (always expects tuples) |
|
|
224
226
|
| `ZERO_PLUS` | `[0, (2**32)-1]` | Zero or more values (always expects tuples) |
|
|
225
227
|
|
|
228
|
+
#### Differences between tuple and vector column types
|
|
229
|
+
|
|
230
|
+
| **Feature** | **Tuples** | **Vectors** |
|
|
231
|
+
|----------------------------------|-----------------------|-------------|
|
|
232
|
+
| Optional (without default value) | ✅ | ❌ |
|
|
233
|
+
| Flexible size | ✅ | ❌ |
|
|
234
|
+
| Query ops match... | Individual components | Full vector |
|
|
235
|
+
|
|
226
236
|
### Default values
|
|
227
237
|
|
|
228
238
|
Default values can be specified for columns with a minimum cardinality of 1 or
|
|
@@ -448,7 +458,7 @@ For Node.js REPL:
|
|
|
448
458
|
const cs = await import("@thi.ng/column-store");
|
|
449
459
|
```
|
|
450
460
|
|
|
451
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 5.
|
|
461
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 5.71 KB
|
|
452
462
|
|
|
453
463
|
## Dependencies
|
|
454
464
|
|
package/api.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ export declare const FLAG_UNIQUE: number;
|
|
|
84
84
|
export declare const FLAG_RLE: number;
|
|
85
85
|
/** @internal */
|
|
86
86
|
export declare const LIMITS: Record<NumericType, [number, number]>;
|
|
87
|
-
export interface IColumn {
|
|
87
|
+
export interface IColumn extends Iterable<any> {
|
|
88
88
|
bitmap?: BitmapIndex;
|
|
89
89
|
readonly isArray: boolean;
|
|
90
90
|
load(spec: SerializedColumn): void;
|
|
@@ -180,7 +180,7 @@ export interface QueryTerm<T extends Row> {
|
|
|
180
180
|
value: any;
|
|
181
181
|
params?: any;
|
|
182
182
|
}
|
|
183
|
-
export type QueryTermOp = Fn3<QueryCtx<any>, QueryTerm<any>, Maybe<IColumn>,
|
|
183
|
+
export type QueryTermOp = Fn3<QueryCtx<any>, QueryTerm<any>, Maybe<IColumn>, boolean>;
|
|
184
184
|
export interface QueryTermOpSpec {
|
|
185
185
|
/**
|
|
186
186
|
* Default mode is: "col";
|
package/columns/acolumn.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ export declare abstract class AColumn<T extends Row = Row> implements IColumn {
|
|
|
11
11
|
dict?: BidirIndex<any>;
|
|
12
12
|
abstract isArray: boolean;
|
|
13
13
|
constructor(id: ColumnID<T>, table: Table<T>);
|
|
14
|
-
|
|
14
|
+
[Symbol.iterator](): Generator<any, void, unknown>;
|
|
15
15
|
reindex(): void;
|
|
16
|
+
abstract load(spec: SerializedColumn): void;
|
|
16
17
|
abstract validate(value: any): boolean;
|
|
17
18
|
abstract setRow(i: number, value: any): void;
|
|
18
19
|
abstract removeRow(i: number): void;
|
package/columns/acolumn.js
CHANGED
package/columns/vector.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare class VectorColumn<T extends Row = Row> extends AColumn<T> {
|
|
|
14
14
|
validate(value: any): boolean;
|
|
15
15
|
ensureRows(): void;
|
|
16
16
|
setRow(i: number, value: any): void;
|
|
17
|
-
getRow(i: number):
|
|
17
|
+
getRow(i: number): Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike>;
|
|
18
18
|
getRowKey(i: number): string;
|
|
19
19
|
valueKey(value: any): string | string[];
|
|
20
20
|
removeRow(i: number): void;
|
package/internal/serialize.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export declare const __serializeTyped: ($values: NumericArray, spec: ColumnSpec,
|
|
|
11
11
|
values: any[];
|
|
12
12
|
};
|
|
13
13
|
/** @internal */
|
|
14
|
-
export declare const __deserializeTyped: (type: Type, flags: number, values: number[]) =>
|
|
14
|
+
export declare const __deserializeTyped: (type: Type, flags: number, values: number[]) => Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike>;
|
|
15
15
|
//# sourceMappingURL=serialize.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/column-store",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "In-memory column store database with customizable column types, extensible query engine, bitfield indexing for query acceleration, JSON serialization with optional RLE compression",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"status": "alpha",
|
|
127
127
|
"year": 2025
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "63ca02b09367c07f6cb785642bc3f2b7df2e5804\n"
|
|
130
130
|
}
|
package/query.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export declare class QueryCtx<T extends Row> {
|
|
|
27
27
|
readonly size: number;
|
|
28
28
|
bitmap?: Uint32Array;
|
|
29
29
|
constructor(query: Query<T>);
|
|
30
|
+
/**
|
|
31
|
+
* If a bitmap is already present, yield iterator of only currently selected
|
|
32
|
+
* row IDs, otherwise yields row IDs in `[0,table.length)` range.
|
|
33
|
+
*/
|
|
34
|
+
[Symbol.iterator](): Generator<number, void, unknown>;
|
|
30
35
|
clear(): void;
|
|
31
36
|
makeMask(seed?: Uint32Array): Uint32Array<ArrayBuffer>;
|
|
32
37
|
/**
|
package/query.js
CHANGED
|
@@ -78,11 +78,10 @@ class Query {
|
|
|
78
78
|
`query op: ${term.type} requires a column name given`
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
|
-
op.fn(ctx, term, column);
|
|
81
|
+
if (!op.fn(ctx, term, column)) return;
|
|
82
82
|
}
|
|
83
83
|
if (ctx.bitmap) {
|
|
84
|
-
for (let i of
|
|
85
|
-
yield table.getRow(i, false, true);
|
|
84
|
+
for (let i of ctx) yield table.getRow(i, false, true);
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
87
|
}
|
|
@@ -95,6 +94,18 @@ class QueryCtx {
|
|
|
95
94
|
table;
|
|
96
95
|
size;
|
|
97
96
|
bitmap;
|
|
97
|
+
/**
|
|
98
|
+
* If a bitmap is already present, yield iterator of only currently selected
|
|
99
|
+
* row IDs, otherwise yields row IDs in `[0,table.length)` range.
|
|
100
|
+
*/
|
|
101
|
+
*[Symbol.iterator]() {
|
|
102
|
+
const n = this.table.length;
|
|
103
|
+
if (this.bitmap) {
|
|
104
|
+
yield* new Bitfield(this.bitmap).ones(n);
|
|
105
|
+
} else {
|
|
106
|
+
for (let i = 0; i < n; i++) yield i;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
98
109
|
clear() {
|
|
99
110
|
this.bitmap = void 0;
|
|
100
111
|
}
|
|
@@ -148,7 +159,7 @@ const execBitOr = (ctx, term, column) => {
|
|
|
148
159
|
const b = bitmap.index.get(k)?.buffer;
|
|
149
160
|
if (!b) continue;
|
|
150
161
|
if (mask) {
|
|
151
|
-
for (let i = 0
|
|
162
|
+
for (let i = 0, n = b.length; i < n; i++) mask[i] |= b[i];
|
|
152
163
|
} else mask = ctx.makeMask(b);
|
|
153
164
|
}
|
|
154
165
|
} else {
|
|
@@ -157,15 +168,16 @@ const execBitOr = (ctx, term, column) => {
|
|
|
157
168
|
}
|
|
158
169
|
if (mask) {
|
|
159
170
|
term.type === "nor" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
171
|
+
return true;
|
|
160
172
|
}
|
|
173
|
+
return false;
|
|
161
174
|
};
|
|
162
175
|
const execOr = (ctx, term, column) => {
|
|
163
|
-
const n = ctx.table.length;
|
|
164
176
|
const key = column.valueKey(term.value);
|
|
165
177
|
const pred = column.isArray ? (row, k) => row.includes(k) : (row, k) => row === k;
|
|
166
178
|
let mask;
|
|
167
179
|
for (let k of isArray(key) ? key : [key]) {
|
|
168
|
-
for (let i
|
|
180
|
+
for (let i of ctx) {
|
|
169
181
|
if (pred(column.getRowKey(i), k)) {
|
|
170
182
|
if (!mask) mask = ctx.makeMask();
|
|
171
183
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
@@ -174,15 +186,15 @@ const execOr = (ctx, term, column) => {
|
|
|
174
186
|
}
|
|
175
187
|
if (mask) {
|
|
176
188
|
term.type === "nor" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
189
|
+
return true;
|
|
177
190
|
}
|
|
191
|
+
return false;
|
|
178
192
|
};
|
|
179
|
-
const delegateOr = (ctx, term, column) =>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
);
|
|
185
|
-
};
|
|
193
|
+
const delegateOr = (ctx, term, column) => (term.value != null && column.bitmap ? execBitOr : execOr)(
|
|
194
|
+
ctx,
|
|
195
|
+
term,
|
|
196
|
+
column
|
|
197
|
+
);
|
|
186
198
|
const execBitAnd = (ctx, term, column) => {
|
|
187
199
|
const bitmap = column.bitmap;
|
|
188
200
|
const key = column.valueKey(term.value);
|
|
@@ -191,16 +203,14 @@ const execBitAnd = (ctx, term, column) => {
|
|
|
191
203
|
const colBitmaps = [];
|
|
192
204
|
for (let k of key) {
|
|
193
205
|
const b = bitmap.index.get(k)?.buffer;
|
|
194
|
-
if (!b)
|
|
195
|
-
if (term.type === "and") ctx.clear();
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
206
|
+
if (!b) return false;
|
|
198
207
|
colBitmaps.push(b);
|
|
199
208
|
}
|
|
200
209
|
for (let b of colBitmaps) {
|
|
201
210
|
if (mask) {
|
|
202
|
-
|
|
203
|
-
|
|
211
|
+
const n = b.length;
|
|
212
|
+
for (let i = 0; i < n; i++) mask[i] &= b[i];
|
|
213
|
+
mask.fill(0, n);
|
|
204
214
|
} else mask = ctx.makeMask(b);
|
|
205
215
|
}
|
|
206
216
|
} else {
|
|
@@ -209,7 +219,9 @@ const execBitAnd = (ctx, term, column) => {
|
|
|
209
219
|
}
|
|
210
220
|
if (mask) {
|
|
211
221
|
term.type === "nand" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
212
|
-
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
213
225
|
};
|
|
214
226
|
const execAnd = (ctx, term, column) => {
|
|
215
227
|
const n = ctx.table.length;
|
|
@@ -218,7 +230,7 @@ const execAnd = (ctx, term, column) => {
|
|
|
218
230
|
let mask;
|
|
219
231
|
for (let k of isArray(key) ? key : [key]) {
|
|
220
232
|
let m;
|
|
221
|
-
for (let i
|
|
233
|
+
for (let i of ctx) {
|
|
222
234
|
if (pred(column.getRowKey(i), k)) {
|
|
223
235
|
if (!m) m = ctx.makeMask();
|
|
224
236
|
m[i >>> 5] |= 1 << (i & 31);
|
|
@@ -229,21 +241,20 @@ const execAnd = (ctx, term, column) => {
|
|
|
229
241
|
for (let i = 0; i < n; i++) mask[i] &= m[i];
|
|
230
242
|
} else mask = m;
|
|
231
243
|
} else {
|
|
232
|
-
|
|
233
|
-
return;
|
|
244
|
+
return false;
|
|
234
245
|
}
|
|
235
246
|
}
|
|
236
247
|
if (mask) {
|
|
237
248
|
term.type === "nand" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
249
|
+
return true;
|
|
238
250
|
}
|
|
251
|
+
return false;
|
|
239
252
|
};
|
|
240
|
-
const delegateAnd = (ctx, term, column) =>
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
);
|
|
246
|
-
};
|
|
253
|
+
const delegateAnd = (ctx, term, column) => (term.value != null && column.bitmap ? execBitAnd : execAnd)(
|
|
254
|
+
ctx,
|
|
255
|
+
term,
|
|
256
|
+
column
|
|
257
|
+
);
|
|
247
258
|
const QUERY_OPS = {
|
|
248
259
|
or: { fn: delegateOr },
|
|
249
260
|
nor: { fn: delegateOr },
|
|
@@ -253,13 +264,14 @@ const QUERY_OPS = {
|
|
|
253
264
|
fn: (ctx, term, column) => {
|
|
254
265
|
const pred = term.value;
|
|
255
266
|
let mask;
|
|
256
|
-
for (let i
|
|
267
|
+
for (let i of ctx) {
|
|
257
268
|
if (pred(column.getRow(i))) {
|
|
258
269
|
if (!mask) mask = ctx.makeMask();
|
|
259
270
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
260
271
|
}
|
|
261
272
|
}
|
|
262
273
|
if (mask) ctx.mergeMask(mask);
|
|
274
|
+
return !!mask;
|
|
263
275
|
}
|
|
264
276
|
},
|
|
265
277
|
matchPartialRow: {
|
|
@@ -269,13 +281,14 @@ const QUERY_OPS = {
|
|
|
269
281
|
const columns = term.params;
|
|
270
282
|
const pred = term.value;
|
|
271
283
|
let mask;
|
|
272
|
-
for (let i
|
|
284
|
+
for (let i of ctx) {
|
|
273
285
|
if (pred(table.getPartialRow(i, columns, false))) {
|
|
274
286
|
if (!mask) mask = ctx.makeMask();
|
|
275
287
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
276
288
|
}
|
|
277
289
|
}
|
|
278
290
|
if (mask) ctx.mergeMask(mask);
|
|
291
|
+
return !!mask;
|
|
279
292
|
}
|
|
280
293
|
},
|
|
281
294
|
matchRow: {
|
|
@@ -284,13 +297,14 @@ const QUERY_OPS = {
|
|
|
284
297
|
const table = ctx.table;
|
|
285
298
|
const pred = term.value;
|
|
286
299
|
let mask;
|
|
287
|
-
for (let i
|
|
300
|
+
for (let i of ctx) {
|
|
288
301
|
if (pred(table.getRow(i, false))) {
|
|
289
302
|
if (!mask) mask = ctx.makeMask();
|
|
290
303
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
291
304
|
}
|
|
292
305
|
}
|
|
293
306
|
if (mask) ctx.mergeMask(mask);
|
|
307
|
+
return !!mask;
|
|
294
308
|
}
|
|
295
309
|
},
|
|
296
310
|
valueRange: {
|
|
@@ -302,7 +316,7 @@ const QUERY_OPS = {
|
|
|
302
316
|
if (start != null) $start = column.findIndex((x) => x >= start);
|
|
303
317
|
if (end != null)
|
|
304
318
|
$end = column.findLastIndex((x) => x <= end, $start);
|
|
305
|
-
__fillMask(ctx, $start, $end >= 0 ? $end + 1 : $end);
|
|
319
|
+
return __fillMask(ctx, $start, $end >= 0 ? $end + 1 : $end, max);
|
|
306
320
|
}
|
|
307
321
|
},
|
|
308
322
|
rowRange: {
|
|
@@ -311,17 +325,19 @@ const QUERY_OPS = {
|
|
|
311
325
|
const max = ctx.table.length;
|
|
312
326
|
let { start = 0, end = max } = term.value;
|
|
313
327
|
[start, end] = __clampRange(max, start, end);
|
|
314
|
-
__fillMask(ctx, start, end);
|
|
328
|
+
return __fillMask(ctx, start, end, max);
|
|
315
329
|
}
|
|
316
330
|
}
|
|
317
331
|
};
|
|
318
|
-
const __fillMask = (ctx, start, end, fill = 1) => {
|
|
319
|
-
if (start >= 0 && end >= 0) {
|
|
332
|
+
const __fillMask = (ctx, start, end, max, fill = 1) => {
|
|
333
|
+
if (start >= 0 && start < max && end >= 0) {
|
|
320
334
|
const mask = ctx.makeMask();
|
|
321
335
|
const bitmap = new Bitfield(mask);
|
|
322
336
|
bitmap.fill(fill, start, end);
|
|
323
337
|
ctx.mergeMask(mask);
|
|
324
|
-
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
325
341
|
};
|
|
326
342
|
const registerQueryOp = (type, spec) => {
|
|
327
343
|
if (QUERY_OPS[type]) illegalArgs(`query op ${type} already registered`);
|