@thi.ng/column-store 0.11.4 → 0.13.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 +20 -2
- package/api.d.ts +19 -0
- package/bitmap.js +2 -2
- package/columns/acolumn.js +1 -1
- package/columns/dict-tuple.js +12 -5
- package/columns/dict.js +3 -2
- package/columns/tuple.js +3 -3
- package/columns/vector.d.ts +1 -1
- package/internal/frequencies.d.ts +26 -0
- package/internal/frequencies.js +33 -0
- package/internal/indexof.d.ts +1 -1
- package/internal/serialize.d.ts +1 -1
- package/package.json +3 -3
- package/query.d.ts +11 -2
- package/query.js +82 -41
- package/table.js +2 -2
package/README.md
CHANGED
|
@@ -337,6 +337,24 @@ for(let result of query) { ... }
|
|
|
337
337
|
const results = [...query];
|
|
338
338
|
```
|
|
339
339
|
|
|
340
|
+
Alternatively, queries can also be eagerly executed via
|
|
341
|
+
[`.exec()`](https://docs.thi.ng/umbrella/column-store/classes/Query.html#exec).
|
|
342
|
+
This will also include metadata alongside the results, useful for paging
|
|
343
|
+
results.
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
// predefine query
|
|
347
|
+
const query = table.query().or("name", ["alice", "bob"]).limit(10);
|
|
348
|
+
|
|
349
|
+
const results = query.exec();
|
|
350
|
+
// {
|
|
351
|
+
// results: [{...}, ...],
|
|
352
|
+
// total: 123,
|
|
353
|
+
// offset: 0,
|
|
354
|
+
// limit: 10
|
|
355
|
+
// }
|
|
356
|
+
```
|
|
357
|
+
|
|
340
358
|
#### Optimized row iteration
|
|
341
359
|
|
|
342
360
|
The query engine works by applying a number of [query
|
|
@@ -475,7 +493,7 @@ TODO
|
|
|
475
493
|
|
|
476
494
|
## Status
|
|
477
495
|
|
|
478
|
-
**
|
|
496
|
+
**BETA** - possibly breaking changes forthcoming
|
|
479
497
|
|
|
480
498
|
[Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bcolumn-store%5D+in%3Atitle)
|
|
481
499
|
|
|
@@ -505,7 +523,7 @@ For Node.js REPL:
|
|
|
505
523
|
const cs = await import("@thi.ng/column-store");
|
|
506
524
|
```
|
|
507
525
|
|
|
508
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 6.
|
|
526
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 6.48 KB
|
|
509
527
|
|
|
510
528
|
## Dependencies
|
|
511
529
|
|
package/api.d.ts
CHANGED
|
@@ -192,6 +192,25 @@ export interface QueryTermOpSpec {
|
|
|
192
192
|
*/
|
|
193
193
|
fn: QueryTermOp;
|
|
194
194
|
}
|
|
195
|
+
export interface QueryResult<T extends Row> {
|
|
196
|
+
/**
|
|
197
|
+
* Array of result rows (each incl. `__row` index)
|
|
198
|
+
*/
|
|
199
|
+
results: RowWithMeta<T>[];
|
|
200
|
+
/**
|
|
201
|
+
* Total number of query results (might be more than in
|
|
202
|
+
* {@link QueryResult.results}).
|
|
203
|
+
*/
|
|
204
|
+
total: number;
|
|
205
|
+
/**
|
|
206
|
+
* Page info. Configured result offset (via {@link Query.limit}).
|
|
207
|
+
*/
|
|
208
|
+
offset: number;
|
|
209
|
+
/**
|
|
210
|
+
* Page info. Configured result limit (via {@link Query.limit}).
|
|
211
|
+
*/
|
|
212
|
+
limit: number;
|
|
213
|
+
}
|
|
195
214
|
/**
|
|
196
215
|
* Initial capacity for typedarray and vector columns
|
|
197
216
|
*
|
package/bitmap.js
CHANGED
|
@@ -35,11 +35,11 @@ class BitmapIndex {
|
|
|
35
35
|
* @param id
|
|
36
36
|
*/
|
|
37
37
|
removeBit(rowID) {
|
|
38
|
-
for (
|
|
38
|
+
for (const bitmap of this.index.values()) bitmap.removeBit(rowID);
|
|
39
39
|
}
|
|
40
40
|
toJSON() {
|
|
41
41
|
const res = {};
|
|
42
|
-
for (
|
|
42
|
+
for (const [k, bits] of this.index) {
|
|
43
43
|
if (bits.buffer) res[k] = Array.from(bits.buffer);
|
|
44
44
|
}
|
|
45
45
|
return res;
|
package/columns/acolumn.js
CHANGED
package/columns/dict-tuple.js
CHANGED
|
@@ -2,6 +2,10 @@ import { BidirIndex } from "@thi.ng/bidir-index";
|
|
|
2
2
|
import { isArray } from "@thi.ng/checks/is-array";
|
|
3
3
|
import { FLAG_UNIQUE } from "../api.js";
|
|
4
4
|
import { __validateArrayValue } from "../internal/checks.js";
|
|
5
|
+
import {
|
|
6
|
+
__frequenciesTuples,
|
|
7
|
+
__frequencyIndex
|
|
8
|
+
} from "../internal/frequencies.js";
|
|
5
9
|
import { __indexOfTuple, __lastIndexOfTuple } from "../internal/indexof.js";
|
|
6
10
|
import { __serializeDict } from "../internal/serialize.js";
|
|
7
11
|
import { AColumn } from "./acolumn.js";
|
|
@@ -21,9 +25,12 @@ class DictTupleColumn extends AColumn {
|
|
|
21
25
|
}
|
|
22
26
|
reindex() {
|
|
23
27
|
const dict = this.dict;
|
|
24
|
-
const newDict =
|
|
28
|
+
const newDict = __frequencyIndex(
|
|
29
|
+
dict,
|
|
30
|
+
__frequenciesTuples(this.values)
|
|
31
|
+
);
|
|
25
32
|
this.values = this.values.map(
|
|
26
|
-
(ids) => ids ? newDict.
|
|
33
|
+
(ids) => ids ? newDict.getAll(dict.getAllIDs(ids)) : null
|
|
27
34
|
);
|
|
28
35
|
this.dict = newDict;
|
|
29
36
|
super.updateBitmap();
|
|
@@ -48,7 +55,7 @@ class DictTupleColumn extends AColumn {
|
|
|
48
55
|
values.length = n;
|
|
49
56
|
values.fill($value, 0, n);
|
|
50
57
|
if (bitmap && $value) {
|
|
51
|
-
for (
|
|
58
|
+
for (const x of $value) bitmap.ensure(x).fill(1, 0, n);
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
61
|
setRow(i, value) {
|
|
@@ -57,8 +64,8 @@ class DictTupleColumn extends AColumn {
|
|
|
57
64
|
const old = values[i];
|
|
58
65
|
const encoded = values[i] = value != null ? this.table.schema[this.id].flags & FLAG_UNIQUE ? [...dict.addAllUnique(value)] : dict.addAll(value) : null;
|
|
59
66
|
if (bitmap) {
|
|
60
|
-
if (old) for (
|
|
61
|
-
if (encoded) for (
|
|
67
|
+
if (old) for (const x of old) bitmap.clearBit(x, i);
|
|
68
|
+
if (encoded) for (const x of encoded) bitmap.setBit(x, i);
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
getRow(i) {
|
package/columns/dict.js
CHANGED
|
@@ -4,6 +4,7 @@ import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
|
|
|
4
4
|
import { decodeSimple, encodeSimple } from "@thi.ng/rle-pack/simple";
|
|
5
5
|
import { FLAG_RLE } from "../api.js";
|
|
6
6
|
import { __validateValue } from "../internal/checks.js";
|
|
7
|
+
import { __frequencies, __frequencyIndex } from "../internal/frequencies.js";
|
|
7
8
|
import { __indexOfSingle, __lastIndexOfSingle } from "../internal/indexof.js";
|
|
8
9
|
import { __serializeDict } from "../internal/serialize.js";
|
|
9
10
|
import { AColumn } from "./acolumn.js";
|
|
@@ -23,9 +24,9 @@ class DictColumn extends AColumn {
|
|
|
23
24
|
}
|
|
24
25
|
reindex() {
|
|
25
26
|
const dict = this.dict;
|
|
26
|
-
const newDict =
|
|
27
|
+
const newDict = __frequencyIndex(dict, __frequencies(this.values));
|
|
27
28
|
this.values = this.values.map(
|
|
28
|
-
(x) => x != null ? newDict.
|
|
29
|
+
(x) => x != null ? newDict.get(dict.getID(x)) : null
|
|
29
30
|
);
|
|
30
31
|
this.dict = newDict;
|
|
31
32
|
super.updateBitmap();
|
package/columns/tuple.js
CHANGED
|
@@ -30,7 +30,7 @@ class TupleColumn extends AColumn {
|
|
|
30
30
|
values.length = n;
|
|
31
31
|
values.fill(value ?? null, 0, n);
|
|
32
32
|
if (bitmap && value) {
|
|
33
|
-
for (
|
|
33
|
+
for (const x of value) bitmap.ensure(x).fill(1, 0, n);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
setRow(i, value) {
|
|
@@ -39,8 +39,8 @@ class TupleColumn extends AColumn {
|
|
|
39
39
|
const old = values[i];
|
|
40
40
|
const row = values[i] = value != null ? this.table.schema[this.id].flags & FLAG_UNIQUE ? [...new Set(value)] : value : null;
|
|
41
41
|
if (bitmap) {
|
|
42
|
-
if (old) for (
|
|
43
|
-
if (row) for (
|
|
42
|
+
if (old) for (const x of old) bitmap.clearBit(x, i);
|
|
43
|
+
if (row) for (const x of row) bitmap.setBit(x, i);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
getRow(i) {
|
package/columns/vector.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare class VectorColumn<T extends Row = Row> extends AColumn<T> {
|
|
|
15
15
|
validate(value: any): boolean;
|
|
16
16
|
ensureRows(): void;
|
|
17
17
|
setRow(i: number, value: any): void;
|
|
18
|
-
getRow(i: number): Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike
|
|
18
|
+
getRow(i: number): Uint32Array<ArrayBufferLike> | Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike>;
|
|
19
19
|
getRowKey(i: number): string;
|
|
20
20
|
valueKey(value: any): string | string[];
|
|
21
21
|
removeRow(i: number): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TypedArray } from "@thi.ng/api";
|
|
2
|
+
import { BidirIndex } from "@thi.ng/bidir-index";
|
|
3
|
+
/**
|
|
4
|
+
* Constructs a new bidir index, sorted by key frequency, based on given index
|
|
5
|
+
* and pre-computed histogram.
|
|
6
|
+
*
|
|
7
|
+
* @param dict
|
|
8
|
+
* @param bins
|
|
9
|
+
*/
|
|
10
|
+
export declare const __frequencyIndex: (dict: BidirIndex<any>, bins: Map<number, number>) => BidirIndex<unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Computes histogram of `values`, sorted frequency. Returns array of
|
|
13
|
+
* `[value,count]` bins. Nullish values in
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare const __frequencies: (rows: TypedArray | (number | null)[]) => Map<number, number>;
|
|
18
|
+
/**
|
|
19
|
+
* Same as {@link __frequencies}, but for tuple-based columns. Computes
|
|
20
|
+
* histogram for unique component values inside tuples, not for the tuples
|
|
21
|
+
* themselves.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export declare const __frequenciesTuples: (rows: (number[] | null)[]) => Map<number, number>;
|
|
26
|
+
//# sourceMappingURL=frequencies.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BidirIndex } from "@thi.ng/bidir-index";
|
|
2
|
+
const __frequencyIndex = (dict, bins) => {
|
|
3
|
+
const newDict = new BidirIndex();
|
|
4
|
+
for (const bin of [...bins].sort((a, b) => b[1] - a[1])) {
|
|
5
|
+
newDict.add(dict.getID(bin[0]));
|
|
6
|
+
}
|
|
7
|
+
return newDict;
|
|
8
|
+
};
|
|
9
|
+
const __frequencies = (rows) => {
|
|
10
|
+
const bins = /* @__PURE__ */ new Map();
|
|
11
|
+
for (const row of rows) {
|
|
12
|
+
if (row == null) continue;
|
|
13
|
+
const n = bins.get(row);
|
|
14
|
+
bins.set(row, n != null ? n + 1 : 1);
|
|
15
|
+
}
|
|
16
|
+
return bins;
|
|
17
|
+
};
|
|
18
|
+
const __frequenciesTuples = (rows) => {
|
|
19
|
+
const bins = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const row of rows) {
|
|
21
|
+
if (row == null) continue;
|
|
22
|
+
for (const v of row) {
|
|
23
|
+
const n = bins.get(v);
|
|
24
|
+
bins.set(v, n != null ? n + 1 : 1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return bins;
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
__frequencies,
|
|
31
|
+
__frequenciesTuples,
|
|
32
|
+
__frequencyIndex
|
|
33
|
+
};
|
package/internal/indexof.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export declare const __lastIndexOfTuple: (needle: ArrayLike<any> | null, values:
|
|
|
11
11
|
/** @internal */
|
|
12
12
|
export declare const __clamp: (x: number, a: number, b: number) => number;
|
|
13
13
|
/** @internal */
|
|
14
|
-
export declare const __clampRange: (max: number, start: number, end?: number) => number
|
|
14
|
+
export declare const __clampRange: (max: number, start: number, end?: number) => [number, number];
|
|
15
15
|
//# sourceMappingURL=indexof.d.ts.map
|
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[]) => Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike
|
|
14
|
+
export declare const __deserializeTyped: (type: Type, flags: number, values: number[]) => Uint32Array<ArrayBufferLike> | Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<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.13.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",
|
|
@@ -127,8 +127,8 @@
|
|
|
127
127
|
}
|
|
128
128
|
},
|
|
129
129
|
"thi.ng": {
|
|
130
|
-
"status": "
|
|
130
|
+
"status": "beta",
|
|
131
131
|
"year": 2025
|
|
132
132
|
},
|
|
133
|
-
"gitHead": "
|
|
133
|
+
"gitHead": "7b9fc19fe963ac68bfe9d04c63f8a91cc3cfff5c\n"
|
|
134
134
|
}
|
package/query.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Comparator, Predicate } from "@thi.ng/api";
|
|
2
|
-
import type { ColumnID, QueryTerm, QueryTermOpSpec, Row, RowWithMeta } from "./api.js";
|
|
2
|
+
import type { ColumnID, QueryResult, QueryTerm, QueryTermOpSpec, Row, RowWithMeta } from "./api.js";
|
|
3
3
|
import type { Table } from "./table.js";
|
|
4
4
|
export declare class Query<T extends Row> {
|
|
5
5
|
readonly table: Table<T>;
|
|
@@ -7,8 +7,9 @@ export declare class Query<T extends Row> {
|
|
|
7
7
|
protected _cmp?: Comparator<any>;
|
|
8
8
|
protected _limit: number;
|
|
9
9
|
protected _offset: number;
|
|
10
|
-
constructor(table: Table<T>, terms?: QueryTerm<T
|
|
10
|
+
constructor(table: Table<T>, terms?: Iterable<QueryTerm<T>>);
|
|
11
11
|
addTerm(term: QueryTerm<T>): this;
|
|
12
|
+
addTerms(terms: Iterable<QueryTerm<T>>): void;
|
|
12
13
|
limit(limit: number, offset?: number): this;
|
|
13
14
|
/**
|
|
14
15
|
* Constructs a comparator for query results based on given sort criteria,
|
|
@@ -35,6 +36,12 @@ export declare class Query<T extends Row> {
|
|
|
35
36
|
matchRow(pred: Predicate<T>): this;
|
|
36
37
|
valueRange(column: ColumnID<T>, start: any, end?: any): this;
|
|
37
38
|
rowRange(start?: number, end?: number): this;
|
|
39
|
+
/**
|
|
40
|
+
* Eagerly executes query with current terms and returns results as array in
|
|
41
|
+
* an object including total number of results and selected paging details
|
|
42
|
+
* (i.e. offset & limit).
|
|
43
|
+
*/
|
|
44
|
+
exec(): QueryResult<T>;
|
|
38
45
|
[Symbol.iterator](): Generator<RowWithMeta<T>, void, unknown>;
|
|
39
46
|
}
|
|
40
47
|
export declare class QueryCtx<T extends Row> {
|
|
@@ -43,11 +50,13 @@ export declare class QueryCtx<T extends Row> {
|
|
|
43
50
|
readonly size: number;
|
|
44
51
|
bitmap?: Uint32Array;
|
|
45
52
|
constructor(query: Query<T>);
|
|
53
|
+
exec(): this;
|
|
46
54
|
/**
|
|
47
55
|
* If a bitmap is already present, yield iterator of only currently selected
|
|
48
56
|
* row IDs, otherwise yields row IDs in `[0,table.length)` range.
|
|
49
57
|
*/
|
|
50
58
|
[Symbol.iterator](): Generator<number, void, unknown>;
|
|
59
|
+
rows(): Generator<RowWithMeta<T>, void, unknown>;
|
|
51
60
|
clear(): void;
|
|
52
61
|
makeMask(seed?: number | Uint32Array): Uint32Array<ArrayBuffer>;
|
|
53
62
|
/**
|
package/query.js
CHANGED
|
@@ -12,9 +12,9 @@ import { Bitfield } from "./bitmap.js";
|
|
|
12
12
|
import { __columnError } from "./internal/checks.js";
|
|
13
13
|
import { __clampRange } from "./internal/indexof.js";
|
|
14
14
|
class Query {
|
|
15
|
-
constructor(table, terms
|
|
15
|
+
constructor(table, terms) {
|
|
16
16
|
this.table = table;
|
|
17
|
-
|
|
17
|
+
if (terms) this.addTerms(terms);
|
|
18
18
|
}
|
|
19
19
|
terms = [];
|
|
20
20
|
_cmp;
|
|
@@ -25,6 +25,9 @@ class Query {
|
|
|
25
25
|
this.terms.push(term);
|
|
26
26
|
return this;
|
|
27
27
|
}
|
|
28
|
+
addTerms(terms) {
|
|
29
|
+
for (const term of terms) this.addTerm(term);
|
|
30
|
+
}
|
|
28
31
|
limit(limit, offset = 0) {
|
|
29
32
|
this._limit = limit;
|
|
30
33
|
this._offset = offset;
|
|
@@ -100,42 +103,60 @@ class Query {
|
|
|
100
103
|
this.terms.push({ type: "rowRange", value: { start, end } });
|
|
101
104
|
return this;
|
|
102
105
|
}
|
|
103
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Eagerly executes query with current terms and returns results as array in
|
|
108
|
+
* an object including total number of results and selected paging details
|
|
109
|
+
* (i.e. offset & limit).
|
|
110
|
+
*/
|
|
111
|
+
exec() {
|
|
104
112
|
const { table, _limit, _offset } = this;
|
|
105
|
-
const ctx = new QueryCtx(this);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
const ctx = new QueryCtx(this).exec();
|
|
114
|
+
let rows = [];
|
|
115
|
+
const result = {
|
|
116
|
+
results: rows,
|
|
117
|
+
total: 0,
|
|
118
|
+
offset: _offset,
|
|
119
|
+
limit: _limit
|
|
120
|
+
};
|
|
121
|
+
if (!ctx.bitmap) return result;
|
|
122
|
+
if (this._cmp) {
|
|
123
|
+
rows = [...ctx.rows()].sort(this._cmp);
|
|
124
|
+
result.total = rows.length;
|
|
125
|
+
result.results = rows.slice(
|
|
126
|
+
...__clampRange(rows.length, _offset, _offset + _limit)
|
|
127
|
+
);
|
|
128
|
+
} else {
|
|
129
|
+
let n = 0;
|
|
130
|
+
const max = _offset + _limit;
|
|
131
|
+
for (const i of ctx) {
|
|
132
|
+
if (n >= _offset && n < max) {
|
|
133
|
+
rows.push(table.getRow(i, false, true));
|
|
134
|
+
}
|
|
135
|
+
n++;
|
|
116
136
|
}
|
|
117
|
-
|
|
137
|
+
result.total = n;
|
|
118
138
|
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
*[Symbol.iterator]() {
|
|
142
|
+
const { table, _limit, _offset } = this;
|
|
143
|
+
const ctx = new QueryCtx(this).exec();
|
|
119
144
|
if (!ctx.bitmap) return;
|
|
120
145
|
if (this._cmp) {
|
|
121
|
-
const rows = [];
|
|
122
|
-
|
|
123
|
-
rows.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (j >= _offset) {
|
|
135
|
-
if (j >= n) return;
|
|
136
|
-
yield table.getRow(i, false, true);
|
|
146
|
+
const rows = [...ctx.rows()].sort(this._cmp);
|
|
147
|
+
yield* rows.slice(
|
|
148
|
+
...__clampRange(rows.length, _offset, _offset + _limit)
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
let j = 0;
|
|
152
|
+
const n = _offset + _limit;
|
|
153
|
+
for (const i of ctx) {
|
|
154
|
+
if (j >= _offset) {
|
|
155
|
+
if (j >= n) return;
|
|
156
|
+
yield table.getRow(i, false, true);
|
|
157
|
+
}
|
|
158
|
+
j++;
|
|
137
159
|
}
|
|
138
|
-
j++;
|
|
139
160
|
}
|
|
140
161
|
}
|
|
141
162
|
}
|
|
@@ -148,6 +169,22 @@ class QueryCtx {
|
|
|
148
169
|
table;
|
|
149
170
|
size;
|
|
150
171
|
bitmap;
|
|
172
|
+
exec() {
|
|
173
|
+
for (const term of this.query.terms) {
|
|
174
|
+
const op = QUERY_OPS[term.type];
|
|
175
|
+
let column;
|
|
176
|
+
if (term.column) {
|
|
177
|
+
column = this.table.columns[term.column];
|
|
178
|
+
if (!column) illegalArgs(`column: ${String(term.column)}`);
|
|
179
|
+
} else if (QUERY_OPS[term.type].mode !== "row") {
|
|
180
|
+
illegalArgs(
|
|
181
|
+
`query op: ${term.type} requires a column name given`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (!op.fn(this, term, column)) break;
|
|
185
|
+
}
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
151
188
|
/**
|
|
152
189
|
* If a bitmap is already present, yield iterator of only currently selected
|
|
153
190
|
* row IDs, otherwise yields row IDs in `[0,table.length)` range.
|
|
@@ -160,6 +197,10 @@ class QueryCtx {
|
|
|
160
197
|
for (let i = 0; i < n; i++) yield i;
|
|
161
198
|
}
|
|
162
199
|
}
|
|
200
|
+
*rows() {
|
|
201
|
+
const table = this.query.table;
|
|
202
|
+
for (const i of this) yield table.getRow(i, false, true);
|
|
203
|
+
}
|
|
163
204
|
clear() {
|
|
164
205
|
this.bitmap = void 0;
|
|
165
206
|
}
|
|
@@ -211,7 +252,7 @@ const execBitOr = (ctx, term, column) => {
|
|
|
211
252
|
const key = column.valueKey(term.value);
|
|
212
253
|
let mask;
|
|
213
254
|
if (isArray(key)) {
|
|
214
|
-
for (
|
|
255
|
+
for (const k of key) {
|
|
215
256
|
const b = bitmap.index.get(k)?.buffer;
|
|
216
257
|
if (!b) continue;
|
|
217
258
|
if (mask) {
|
|
@@ -228,8 +269,8 @@ const execOr = (ctx, term, column) => {
|
|
|
228
269
|
const key = column.valueKey(term.value);
|
|
229
270
|
const pred = column.isArray ? (row, k) => row.includes(k) : (row, k) => row === k;
|
|
230
271
|
let mask;
|
|
231
|
-
for (
|
|
232
|
-
for (
|
|
272
|
+
for (const k of isArray(key) ? key : [key]) {
|
|
273
|
+
for (const i of ctx) {
|
|
233
274
|
if (pred(column.getRowKey(i), k)) {
|
|
234
275
|
if (!mask) mask = ctx.makeMask();
|
|
235
276
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
@@ -249,7 +290,7 @@ const execBitAnd = (ctx, term, column) => {
|
|
|
249
290
|
const isNeg = term.type === "nand";
|
|
250
291
|
let mask;
|
|
251
292
|
if (isArray(key)) {
|
|
252
|
-
for (
|
|
293
|
+
for (const k of key) {
|
|
253
294
|
const b = bitmap.index.get(k)?.buffer;
|
|
254
295
|
if (!b) {
|
|
255
296
|
if (isNeg) {
|
|
@@ -276,9 +317,9 @@ const execAnd = (ctx, term, column) => {
|
|
|
276
317
|
const pred = column.isArray ? (row, v) => row.includes(v) : (row, v) => row === v;
|
|
277
318
|
const isNeg = term.type === "nand";
|
|
278
319
|
let mask;
|
|
279
|
-
for (
|
|
320
|
+
for (const k of isArray(key) ? key : [key]) {
|
|
280
321
|
let m;
|
|
281
|
-
for (
|
|
322
|
+
for (const i of ctx) {
|
|
282
323
|
if (pred(column.getRowKey(i), k)) {
|
|
283
324
|
if (!m) m = ctx.makeMask();
|
|
284
325
|
m[i >>> 5] |= 1 << (i & 31);
|
|
@@ -321,7 +362,7 @@ const QUERY_OPS = {
|
|
|
321
362
|
fn: (ctx, term, column) => {
|
|
322
363
|
const pred = term.value;
|
|
323
364
|
let mask;
|
|
324
|
-
for (
|
|
365
|
+
for (const i of ctx) {
|
|
325
366
|
if (pred(column.getRow(i))) {
|
|
326
367
|
if (!mask) mask = ctx.makeMask();
|
|
327
368
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
@@ -338,7 +379,7 @@ const QUERY_OPS = {
|
|
|
338
379
|
const columns = term.params;
|
|
339
380
|
const pred = term.value;
|
|
340
381
|
let mask;
|
|
341
|
-
for (
|
|
382
|
+
for (const i of ctx) {
|
|
342
383
|
if (pred(table.getPartialRow(i, columns, false))) {
|
|
343
384
|
if (!mask) mask = ctx.makeMask();
|
|
344
385
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
@@ -354,7 +395,7 @@ const QUERY_OPS = {
|
|
|
354
395
|
const table = ctx.table;
|
|
355
396
|
const pred = term.value;
|
|
356
397
|
let mask;
|
|
357
|
-
for (
|
|
398
|
+
for (const i of ctx) {
|
|
358
399
|
if (pred(table.getRow(i, false))) {
|
|
359
400
|
if (!mask) mask = ctx.makeMask();
|
|
360
401
|
mask[i >>> 5] |= 1 << (i & 31);
|
package/table.js
CHANGED
|
@@ -86,7 +86,7 @@ class Table {
|
|
|
86
86
|
this.length++;
|
|
87
87
|
}
|
|
88
88
|
addRows(rows) {
|
|
89
|
-
for (
|
|
89
|
+
for (const row of rows) this.addRow(row);
|
|
90
90
|
}
|
|
91
91
|
updateRow(i, row) {
|
|
92
92
|
if (i < 0 || i >= this.length) illegalArgs(`row ID: ${i}`);
|
|
@@ -113,7 +113,7 @@ class Table {
|
|
|
113
113
|
getPartialRow(i, columns, safe = true, includeID = false) {
|
|
114
114
|
if (safe && (i < 0 || i >= this.length)) return;
|
|
115
115
|
const row = includeID ? { __row: i } : {};
|
|
116
|
-
for (
|
|
116
|
+
for (const id of columns) {
|
|
117
117
|
row[id] = this.columns[id]?.getRow(i);
|
|
118
118
|
}
|
|
119
119
|
return row;
|