@thi.ng/column-store 0.1.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/LICENSE +201 -0
- package/README.md +428 -0
- package/api.d.ts +101 -0
- package/api.js +18 -0
- package/bitmap.d.ts +35 -0
- package/bitmap.js +104 -0
- package/columns/acolumn.d.ts +19 -0
- package/columns/acolumn.js +55 -0
- package/columns/array.d.ts +19 -0
- package/columns/array.js +62 -0
- package/columns/dict-array.d.ts +25 -0
- package/columns/dict-array.js +79 -0
- package/columns/dict.d.ts +25 -0
- package/columns/dict.js +82 -0
- package/columns/plain.d.ts +17 -0
- package/columns/plain.js +42 -0
- package/columns/typedarray.d.ts +22 -0
- package/columns/typedarray.js +84 -0
- package/index.d.ts +5 -0
- package/index.js +4 -0
- package/internal/checks.d.ts +8 -0
- package/internal/checks.js +15 -0
- package/internal/replace.d.ts +5 -0
- package/internal/replace.js +16 -0
- package/internal/serialize.d.ts +7 -0
- package/internal/serialize.js +8 -0
- package/package.json +127 -0
- package/query.d.ts +43 -0
- package/query.js +271 -0
- package/table.d.ts +51 -0
- package/table.js +176 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type TypedArray } from "@thi.ng/api/typedarray";
|
|
2
|
+
import { type IColumn, type NumericType, type SerializedColumn } from "../api.js";
|
|
3
|
+
import type { Table } from "../table.js";
|
|
4
|
+
import { AColumn } from "./acolumn.js";
|
|
5
|
+
export declare class TypedArrayColumn extends AColumn implements IColumn {
|
|
6
|
+
values: TypedArray;
|
|
7
|
+
type: NumericType;
|
|
8
|
+
limit: [number, number];
|
|
9
|
+
readonly isArray = false;
|
|
10
|
+
constructor(id: string, table: Table);
|
|
11
|
+
load(spec: SerializedColumn): void;
|
|
12
|
+
reindex(): void;
|
|
13
|
+
validate(value: any): boolean;
|
|
14
|
+
setRow(i: number, value: any): void;
|
|
15
|
+
getRow(i: number): number;
|
|
16
|
+
removeRow(i: number): void;
|
|
17
|
+
replaceValue(currValue: any, newValue: any): boolean;
|
|
18
|
+
toJSON(): {
|
|
19
|
+
values: number[];
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=typedarray.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { SIZEOF, typedArray } from "@thi.ng/api/typedarray";
|
|
2
|
+
import { isNumber } from "@thi.ng/checks/is-number";
|
|
3
|
+
import { decode as decodeRLE, encode as encodeRLE } from "@thi.ng/rle-pack";
|
|
4
|
+
import {
|
|
5
|
+
FLAG_RLE
|
|
6
|
+
} from "../api.js";
|
|
7
|
+
import { __replaceValue } from "../internal/replace.js";
|
|
8
|
+
import { AColumn } from "./acolumn.js";
|
|
9
|
+
const LIMITS = {
|
|
10
|
+
u8: [0, 255],
|
|
11
|
+
u8c: [0, 255],
|
|
12
|
+
u16: [0, 65535],
|
|
13
|
+
u32: [0, 4294967295],
|
|
14
|
+
i8: [-128, 127],
|
|
15
|
+
i16: [-32768, 32767],
|
|
16
|
+
i32: [-2147483648, 2147483647],
|
|
17
|
+
f32: [-Infinity, Infinity],
|
|
18
|
+
f64: [-Infinity, Infinity]
|
|
19
|
+
};
|
|
20
|
+
class TypedArrayColumn extends AColumn {
|
|
21
|
+
values;
|
|
22
|
+
type;
|
|
23
|
+
limit;
|
|
24
|
+
isArray = false;
|
|
25
|
+
constructor(id, table) {
|
|
26
|
+
super(id, table);
|
|
27
|
+
this.type = table.schema[id].type;
|
|
28
|
+
this.limit = LIMITS[this.type];
|
|
29
|
+
this.values = typedArray(this.type, 8);
|
|
30
|
+
}
|
|
31
|
+
load(spec) {
|
|
32
|
+
if (this.spec.flags & FLAG_RLE) {
|
|
33
|
+
const values = decodeRLE(spec.values);
|
|
34
|
+
this.values = typedArray(this.type, values.buffer);
|
|
35
|
+
} else {
|
|
36
|
+
this.values = typedArray(this.type, spec.values);
|
|
37
|
+
}
|
|
38
|
+
this.reindex();
|
|
39
|
+
}
|
|
40
|
+
reindex() {
|
|
41
|
+
super.updateBitmap(this.values.subarray(0, this.table.length));
|
|
42
|
+
}
|
|
43
|
+
validate(value) {
|
|
44
|
+
return isNumber(value) && (value >= this.limit[0] || value <= this.limit[1]) || value == null && this.spec.default != null;
|
|
45
|
+
}
|
|
46
|
+
setRow(i, value) {
|
|
47
|
+
value = this.ensureValue(value);
|
|
48
|
+
let len = this.values.length;
|
|
49
|
+
if (i >= len) {
|
|
50
|
+
while (i >= len) len <<= 1;
|
|
51
|
+
const tmp = typedArray(this.type, len);
|
|
52
|
+
tmp.set(this.values);
|
|
53
|
+
this.values = tmp;
|
|
54
|
+
}
|
|
55
|
+
const { values, bitmap } = this;
|
|
56
|
+
const old = values[i];
|
|
57
|
+
values[i] = value;
|
|
58
|
+
if (bitmap) {
|
|
59
|
+
bitmap.clearBit(old, i);
|
|
60
|
+
bitmap.setBit(value, i);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
getRow(i) {
|
|
64
|
+
return this.values[i];
|
|
65
|
+
}
|
|
66
|
+
removeRow(i) {
|
|
67
|
+
this.values.copyWithin(i, i + 1, this.table.length);
|
|
68
|
+
this.values[this.table.length - 1] = 0;
|
|
69
|
+
this.bitmap?.removeBit(i);
|
|
70
|
+
}
|
|
71
|
+
replaceValue(currValue, newValue) {
|
|
72
|
+
return __replaceValue(this.bitmap, this.values, currValue, newValue);
|
|
73
|
+
}
|
|
74
|
+
toJSON() {
|
|
75
|
+
let values = this.values.subarray(0, this.table.length);
|
|
76
|
+
if (this.spec.flags & FLAG_RLE) {
|
|
77
|
+
values = encodeRLE(values, values.length, SIZEOF[this.type] * 8);
|
|
78
|
+
}
|
|
79
|
+
return { values: Array.from(values) };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
TypedArrayColumn
|
|
84
|
+
};
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ColumnSpec } from "../api.js";
|
|
2
|
+
/** @internal */
|
|
3
|
+
export declare const __validateValue: (spec: ColumnSpec, x: any) => boolean;
|
|
4
|
+
/** @internal */
|
|
5
|
+
export declare const __validateArrayValue: (spec: ColumnSpec, x: any) => boolean;
|
|
6
|
+
/** @internal */
|
|
7
|
+
export declare const __columnError: (id: string, msg: string) => never;
|
|
8
|
+
//# sourceMappingURL=checks.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isArray } from "@thi.ng/checks/is-array";
|
|
2
|
+
import { isNumber } from "@thi.ng/checks/is-number";
|
|
3
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
4
|
+
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
5
|
+
const __validateValue = (spec, x) => x == null && (spec.cardinality[0] === 0 || spec.default != null) || (spec.type === "str" ? isString(x) : isNumber(x));
|
|
6
|
+
const __validateArrayValue = (spec, x) => {
|
|
7
|
+
const [min, max] = spec.cardinality;
|
|
8
|
+
return x == null && (min === 0 || spec.default != null) || isArray(x) && x.length >= min && x.length <= max && x.every(spec.type === "str" ? isString : isNumber);
|
|
9
|
+
};
|
|
10
|
+
const __columnError = (id, msg) => illegalArgs(msg + ` (column: ${id})`);
|
|
11
|
+
export {
|
|
12
|
+
__columnError,
|
|
13
|
+
__validateArrayValue,
|
|
14
|
+
__validateValue
|
|
15
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Maybe, TypedArray } from "@thi.ng/api";
|
|
2
|
+
import type { BitmapIndex } from "../bitmap.js";
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare const __replaceValue: (bitmap: Maybe<BitmapIndex>, values: any[] | TypedArray, currVal: any, newVal: any) => boolean;
|
|
5
|
+
//# sourceMappingURL=replace.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const __replaceValue = (bitmap, values, currVal, newVal) => {
|
|
2
|
+
const bits = bitmap?.ensure(newVal);
|
|
3
|
+
bitmap?.index.delete(currVal);
|
|
4
|
+
let result = false;
|
|
5
|
+
for (let i = 0; i < values.length; i++) {
|
|
6
|
+
if (values[i] === currVal) {
|
|
7
|
+
values[i] = newVal;
|
|
8
|
+
bits?.setBit(i);
|
|
9
|
+
result = true;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
__replaceValue
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thi.ng/column-store",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"module": "./index.js",
|
|
7
|
+
"typings": "./index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/thi-ng/umbrella.git",
|
|
12
|
+
"directory": "packages/column-store"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://thi.ng/column-store",
|
|
15
|
+
"funding": [
|
|
16
|
+
{
|
|
17
|
+
"type": "github",
|
|
18
|
+
"url": "https://github.com/sponsors/postspectacular"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "patreon",
|
|
22
|
+
"url": "https://patreon.com/thing_umbrella"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "liberapay",
|
|
26
|
+
"url": "https://liberapay.com/thi.ng"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"author": "Karsten Schmidt (https://thi.ng)",
|
|
30
|
+
"license": "Apache-2.0",
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "yarn build:esbuild && yarn build:decl",
|
|
33
|
+
"build:decl": "tsc --declaration --emitDeclarationOnly",
|
|
34
|
+
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
|
|
35
|
+
"clean": "bun ../../tools/src/clean-package.ts",
|
|
36
|
+
"doc": "typedoc --options ../../typedoc.json --out doc src/index.ts",
|
|
37
|
+
"doc:readme": "bun ../../tools/src/module-stats.ts && bun ../../tools/src/readme.ts",
|
|
38
|
+
"pub": "npm publish --access public",
|
|
39
|
+
"test": "bun test",
|
|
40
|
+
"tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@thi.ng/api": "^8.12.14",
|
|
44
|
+
"@thi.ng/bidir-index": "^1.5.0",
|
|
45
|
+
"@thi.ng/checks": "^3.8.4",
|
|
46
|
+
"@thi.ng/errors": "^2.6.3",
|
|
47
|
+
"@thi.ng/rle-pack": "^3.1.118"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"esbuild": "^0.27.2",
|
|
51
|
+
"typedoc": "^0.28.16",
|
|
52
|
+
"typescript": "^5.9.3"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"acceleration",
|
|
56
|
+
"binary",
|
|
57
|
+
"bitmap",
|
|
58
|
+
"column",
|
|
59
|
+
"compression",
|
|
60
|
+
"database",
|
|
61
|
+
"indexed",
|
|
62
|
+
"json",
|
|
63
|
+
"memory",
|
|
64
|
+
"predicate",
|
|
65
|
+
"query",
|
|
66
|
+
"rle",
|
|
67
|
+
"set",
|
|
68
|
+
"typedarray",
|
|
69
|
+
"typescript"
|
|
70
|
+
],
|
|
71
|
+
"publishConfig": {
|
|
72
|
+
"access": "public"
|
|
73
|
+
},
|
|
74
|
+
"browser": {
|
|
75
|
+
"process": false,
|
|
76
|
+
"setTimeout": false
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=18"
|
|
80
|
+
},
|
|
81
|
+
"files": [
|
|
82
|
+
"./*.js",
|
|
83
|
+
"./*.d.ts",
|
|
84
|
+
"columns",
|
|
85
|
+
"internal"
|
|
86
|
+
],
|
|
87
|
+
"exports": {
|
|
88
|
+
".": {
|
|
89
|
+
"default": "./index.js"
|
|
90
|
+
},
|
|
91
|
+
"./api": {
|
|
92
|
+
"default": "./api.js"
|
|
93
|
+
},
|
|
94
|
+
"./bitmap": {
|
|
95
|
+
"default": "./bitmap.js"
|
|
96
|
+
},
|
|
97
|
+
"./columns/acolumn": {
|
|
98
|
+
"default": "./columns/acolumn.js"
|
|
99
|
+
},
|
|
100
|
+
"./columns/array": {
|
|
101
|
+
"default": "./columns/array.js"
|
|
102
|
+
},
|
|
103
|
+
"./columns/enum-array": {
|
|
104
|
+
"default": "./columns/enum-array.js"
|
|
105
|
+
},
|
|
106
|
+
"./columns/enum": {
|
|
107
|
+
"default": "./columns/enum.js"
|
|
108
|
+
},
|
|
109
|
+
"./columns/plain": {
|
|
110
|
+
"default": "./columns/plain.js"
|
|
111
|
+
},
|
|
112
|
+
"./columns/typedarray": {
|
|
113
|
+
"default": "./columns/typedarray.js"
|
|
114
|
+
},
|
|
115
|
+
"./query": {
|
|
116
|
+
"default": "./query.js"
|
|
117
|
+
},
|
|
118
|
+
"./table": {
|
|
119
|
+
"default": "./table.js"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"thi.ng": {
|
|
123
|
+
"status": "alpha",
|
|
124
|
+
"year": 2025
|
|
125
|
+
},
|
|
126
|
+
"gitHead": "4bd1b9d8ae52ba32b90a1b9a55d329c708ca7865\n"
|
|
127
|
+
}
|
package/query.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Predicate } from "@thi.ng/api";
|
|
2
|
+
import type { QueryTerm, QueryTermOpSpec } from "./api.js";
|
|
3
|
+
import type { Table } from "./table.js";
|
|
4
|
+
export declare class Query {
|
|
5
|
+
table: Table;
|
|
6
|
+
terms: QueryTerm[];
|
|
7
|
+
constructor(table: Table, terms?: QueryTerm[]);
|
|
8
|
+
addTerm(term: QueryTerm): this;
|
|
9
|
+
/** Alias for {@link Query.or} */
|
|
10
|
+
where: (column: string, value: any) => this;
|
|
11
|
+
/** Alias for {@link Query.nor} */
|
|
12
|
+
whereNot: (column: string, value: any) => this;
|
|
13
|
+
or(column: string, value: any): this;
|
|
14
|
+
nor(column: string, value: any): this;
|
|
15
|
+
and(column: string, value: any): this;
|
|
16
|
+
nand(column: string, value: any): this;
|
|
17
|
+
matchColumn(column: string, pred: Predicate<any>): this;
|
|
18
|
+
matchPartialRow(columns: string[], pred: Predicate<Record<string, any>>): this;
|
|
19
|
+
matchRow(pred: Predicate<Record<string, any>>): this;
|
|
20
|
+
[Symbol.iterator](): Generator<import("./api.js").Row | undefined, void, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export declare class QueryCtx {
|
|
23
|
+
readonly query: Query;
|
|
24
|
+
readonly table: Table;
|
|
25
|
+
readonly size: number;
|
|
26
|
+
bitmap?: Uint32Array;
|
|
27
|
+
constructor(query: Query);
|
|
28
|
+
makeMask(seed?: Uint32Array): Uint32Array<ArrayBuffer>;
|
|
29
|
+
mergeMask(mask: Uint32Array): void;
|
|
30
|
+
invertMask(mask: Uint32Array): Uint32Array<ArrayBufferLike>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Registers a custom query term operator for given `type` and later use with
|
|
34
|
+
* {@link Query}. Throws an error if `type` is already registered.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* See {@link QueryTerm} & {@link QueryTermOp} for further details.
|
|
38
|
+
*
|
|
39
|
+
* @param type
|
|
40
|
+
* @param spec
|
|
41
|
+
*/
|
|
42
|
+
export declare const registerQueryOp: (type: string, spec: QueryTermOpSpec) => void;
|
|
43
|
+
//# sourceMappingURL=query.d.ts.map
|
package/query.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { isArray } from "@thi.ng/checks/is-array";
|
|
2
|
+
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
3
|
+
import { unsupportedOp } from "@thi.ng/errors/unsupported";
|
|
4
|
+
import { Bitfield } from "./bitmap.js";
|
|
5
|
+
class Query {
|
|
6
|
+
constructor(table, terms = []) {
|
|
7
|
+
this.table = table;
|
|
8
|
+
this.terms = terms;
|
|
9
|
+
}
|
|
10
|
+
terms = [];
|
|
11
|
+
addTerm(term) {
|
|
12
|
+
this.terms.push(term);
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
/** Alias for {@link Query.or} */
|
|
16
|
+
where = (column, value) => this.or(column, value);
|
|
17
|
+
/** Alias for {@link Query.nor} */
|
|
18
|
+
whereNot = (column, value) => this.nor(column, value);
|
|
19
|
+
or(column, value) {
|
|
20
|
+
this.terms.push({ type: "or", column, value });
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
nor(column, value) {
|
|
24
|
+
this.terms.push({ type: "nor", column, value });
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
and(column, value) {
|
|
28
|
+
this.terms.push({ type: "and", column, value });
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
nand(column, value) {
|
|
32
|
+
this.terms.push({ type: "nand", column, value });
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
matchColumn(column, pred) {
|
|
36
|
+
this.terms.push({ type: "matchCol", column, value: pred });
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
matchPartialRow(columns, pred) {
|
|
40
|
+
this.terms.push({
|
|
41
|
+
type: "matchPartialRow",
|
|
42
|
+
params: columns,
|
|
43
|
+
value: pred
|
|
44
|
+
});
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
matchRow(pred) {
|
|
48
|
+
this.terms.push({ type: "matchRow", value: pred });
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
*[Symbol.iterator]() {
|
|
52
|
+
const { table } = this;
|
|
53
|
+
const ctx = new QueryCtx(this);
|
|
54
|
+
for (let term of this.terms) {
|
|
55
|
+
const op = QUERY_OPS[term.type];
|
|
56
|
+
if (!op) unsupportedOp(`query type: ${term.type}`);
|
|
57
|
+
let column;
|
|
58
|
+
if (term.column) {
|
|
59
|
+
column = ctx.table.columns[term.column];
|
|
60
|
+
if (!column) illegalArgs(`column: ${term.column}`);
|
|
61
|
+
} else if (QUERY_OPS[term.type].mode !== "row") {
|
|
62
|
+
illegalArgs(
|
|
63
|
+
`query op: ${term.type} requires a column name given`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
op.fn(ctx, term, column);
|
|
67
|
+
}
|
|
68
|
+
if (ctx.bitmap) {
|
|
69
|
+
for (let i of new Bitfield(ctx.bitmap).ones(table.length))
|
|
70
|
+
yield table.getRow(i);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
class QueryCtx {
|
|
75
|
+
constructor(query) {
|
|
76
|
+
this.query = query;
|
|
77
|
+
this.table = query.table;
|
|
78
|
+
this.size = Math.ceil(this.table.length / 32);
|
|
79
|
+
}
|
|
80
|
+
table;
|
|
81
|
+
size;
|
|
82
|
+
bitmap;
|
|
83
|
+
makeMask(seed) {
|
|
84
|
+
const mask = new Uint32Array(this.size);
|
|
85
|
+
if (seed) mask.set(seed);
|
|
86
|
+
return mask;
|
|
87
|
+
}
|
|
88
|
+
mergeMask(mask) {
|
|
89
|
+
if (this.bitmap) {
|
|
90
|
+
for (let i = 0, n = this.size; i < n; i++)
|
|
91
|
+
this.bitmap[i] &= mask[i];
|
|
92
|
+
} else this.bitmap = mask;
|
|
93
|
+
}
|
|
94
|
+
invertMask(mask) {
|
|
95
|
+
for (let i = 0, n = this.size; i < n; i++) mask[i] ^= -1;
|
|
96
|
+
return mask;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const execBitOr = (ctx, term, column) => {
|
|
100
|
+
const bitmap = column.bitmap;
|
|
101
|
+
const value = column.encode(term.value);
|
|
102
|
+
let mask;
|
|
103
|
+
if (isArray(value)) {
|
|
104
|
+
for (let v of value) {
|
|
105
|
+
const b = bitmap.index.get(v)?.buffer;
|
|
106
|
+
if (!b) continue;
|
|
107
|
+
if (mask) {
|
|
108
|
+
for (let i = 0; i < b.length; i++) mask[i] |= b[i];
|
|
109
|
+
} else mask = ctx.makeMask(b);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
const b = bitmap.index.get(value)?.buffer;
|
|
113
|
+
if (b) mask = ctx.makeMask(b);
|
|
114
|
+
}
|
|
115
|
+
if (mask) {
|
|
116
|
+
if (term.type === "nor") ctx.invertMask(mask);
|
|
117
|
+
ctx.mergeMask(mask);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const execOr = (ctx, term, column) => {
|
|
121
|
+
const n = ctx.table.length;
|
|
122
|
+
const encoded = column.encode(term.value);
|
|
123
|
+
const values = column.values;
|
|
124
|
+
const pred = column.isArray ? (row, v) => row.includes(v) : (row, v) => row === v;
|
|
125
|
+
let mask;
|
|
126
|
+
for (let v of isArray(encoded) ? encoded : [encoded]) {
|
|
127
|
+
for (let i = 0; i < n; i++) {
|
|
128
|
+
if (pred(values[i], v)) {
|
|
129
|
+
if (!mask) mask = ctx.makeMask();
|
|
130
|
+
mask[i >>> 5] |= 1 << (i & 31);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (mask) {
|
|
135
|
+
if (term.type === "nor") ctx.invertMask(mask);
|
|
136
|
+
ctx.mergeMask(mask);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const delegateOr = (ctx, term, column) => {
|
|
140
|
+
(term.value != null && column.bitmap ? execBitOr : execOr)(
|
|
141
|
+
ctx,
|
|
142
|
+
term,
|
|
143
|
+
column
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
const execBitAnd = (ctx, term, column) => {
|
|
147
|
+
const bitmap = column.bitmap;
|
|
148
|
+
const value = column.encode(term.value);
|
|
149
|
+
let mask;
|
|
150
|
+
if (isArray(value)) {
|
|
151
|
+
const colBitmaps = [];
|
|
152
|
+
for (let v of value) {
|
|
153
|
+
const b = bitmap.index.get(v)?.buffer;
|
|
154
|
+
if (!b) {
|
|
155
|
+
if (term.type === "and") ctx.bitmap = void 0;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
colBitmaps.push(b);
|
|
159
|
+
}
|
|
160
|
+
for (let b of colBitmaps) {
|
|
161
|
+
if (mask) {
|
|
162
|
+
for (let i = 0; i < b.length; i++) mask[i] &= b[i];
|
|
163
|
+
mask.fill(0, b.length);
|
|
164
|
+
} else mask = ctx.makeMask(b);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
const b = bitmap.index.get(value)?.buffer;
|
|
168
|
+
if (b) mask = ctx.makeMask(b);
|
|
169
|
+
}
|
|
170
|
+
if (mask) {
|
|
171
|
+
if (term.type === "nand") ctx.invertMask(mask);
|
|
172
|
+
ctx.mergeMask(mask);
|
|
173
|
+
} else {
|
|
174
|
+
ctx.bitmap = void 0;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const execAnd = (ctx, term, column) => {
|
|
178
|
+
const n = ctx.table.length;
|
|
179
|
+
const encoded = column.encode(term.value) ?? null;
|
|
180
|
+
const values = column.values;
|
|
181
|
+
const pred = column.isArray ? (row, v) => row.includes(v) : (row, v) => row === v;
|
|
182
|
+
let mask;
|
|
183
|
+
for (let v of isArray(encoded) ? encoded : [encoded]) {
|
|
184
|
+
let m;
|
|
185
|
+
for (let i = 0; i < n; i++) {
|
|
186
|
+
if (pred(values[i], v)) {
|
|
187
|
+
if (!m) m = ctx.makeMask();
|
|
188
|
+
m[i >>> 5] |= 1 << (i & 31);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (m) {
|
|
192
|
+
if (mask) {
|
|
193
|
+
for (let i = 0; i < n; i++) mask[i] &= m[i];
|
|
194
|
+
} else mask = m;
|
|
195
|
+
} else {
|
|
196
|
+
if (term.type === "and") ctx.bitmap = void 0;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (mask) {
|
|
201
|
+
if (term.type === "nand") ctx.invertMask(mask);
|
|
202
|
+
ctx.mergeMask(mask);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const delegateAnd = (ctx, term, column) => {
|
|
206
|
+
(term.value != null && column.bitmap ? execBitAnd : execAnd)(
|
|
207
|
+
ctx,
|
|
208
|
+
term,
|
|
209
|
+
column
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
const QUERY_OPS = {
|
|
213
|
+
or: { fn: delegateOr },
|
|
214
|
+
nor: { fn: delegateOr },
|
|
215
|
+
and: { fn: delegateAnd },
|
|
216
|
+
nand: { fn: delegateAnd },
|
|
217
|
+
matchCol: {
|
|
218
|
+
fn: (ctx, term, column) => {
|
|
219
|
+
const values = column.values;
|
|
220
|
+
const pred = term.value;
|
|
221
|
+
let mask;
|
|
222
|
+
for (let i = 0, n = ctx.table.length; i < n; i++) {
|
|
223
|
+
if (pred(column.decode(values[i]))) {
|
|
224
|
+
if (!mask) mask = ctx.makeMask();
|
|
225
|
+
mask[i >>> 5] |= 1 << (i & 31);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (mask) ctx.mergeMask(mask);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
matchPartialRow: {
|
|
232
|
+
mode: "row",
|
|
233
|
+
fn: (ctx, term) => {
|
|
234
|
+
const table = ctx.table;
|
|
235
|
+
const columns = term.params;
|
|
236
|
+
const pred = term.value;
|
|
237
|
+
let mask;
|
|
238
|
+
for (let i = 0, n = ctx.table.length; i < n; i++) {
|
|
239
|
+
if (pred(table.getPartialRow(i, columns, false))) {
|
|
240
|
+
if (!mask) mask = ctx.makeMask();
|
|
241
|
+
mask[i >>> 5] |= 1 << (i & 31);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (mask) ctx.mergeMask(mask);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
matchRow: {
|
|
248
|
+
mode: "row",
|
|
249
|
+
fn: (ctx, term) => {
|
|
250
|
+
const table = ctx.table;
|
|
251
|
+
const pred = term.value;
|
|
252
|
+
let mask;
|
|
253
|
+
for (let i = 0, n = ctx.table.length; i < n; i++) {
|
|
254
|
+
if (pred(table.getRow(i, false))) {
|
|
255
|
+
if (!mask) mask = ctx.makeMask();
|
|
256
|
+
mask[i >>> 5] |= 1 << (i & 31);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (mask) ctx.mergeMask(mask);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const registerQueryOp = (type, spec) => {
|
|
264
|
+
if (QUERY_OPS[type]) illegalArgs(`query op ${type} already registered`);
|
|
265
|
+
QUERY_OPS[type] = spec;
|
|
266
|
+
};
|
|
267
|
+
export {
|
|
268
|
+
Query,
|
|
269
|
+
QueryCtx,
|
|
270
|
+
registerQueryOp
|
|
271
|
+
};
|
package/table.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ColumnSchema, type ColumnSpec, type ColumnTypeSpec, type IColumn, type QueryTerm, type Row, type SerializedTable } from "./api.js";
|
|
2
|
+
import { Query } from "./query.js";
|
|
3
|
+
/**
|
|
4
|
+
* Placeholder only. Unused so far.
|
|
5
|
+
*/
|
|
6
|
+
export interface TableOpts {
|
|
7
|
+
}
|
|
8
|
+
export declare class Table {
|
|
9
|
+
opts: TableOpts;
|
|
10
|
+
schema: ColumnSchema;
|
|
11
|
+
columns: Record<string, IColumn>;
|
|
12
|
+
length: number;
|
|
13
|
+
static load(serialized: SerializedTable, opts?: Partial<TableOpts>): Table;
|
|
14
|
+
constructor(schema: Record<string, Partial<ColumnSpec> & {
|
|
15
|
+
type: ColumnSpec["type"];
|
|
16
|
+
}>, opts?: Partial<TableOpts>);
|
|
17
|
+
query(terms?: QueryTerm[]): Query;
|
|
18
|
+
addColumn(id: string, spec: Partial<ColumnSpec> & {
|
|
19
|
+
type: ColumnSpec["type"];
|
|
20
|
+
}): void;
|
|
21
|
+
removeColumn(id: string): boolean;
|
|
22
|
+
[Symbol.iterator](): Generator<Row | undefined, void, unknown>;
|
|
23
|
+
reindex(): void;
|
|
24
|
+
addRow(row: Row): void;
|
|
25
|
+
addRows(rows: Iterable<Row>): void;
|
|
26
|
+
updateRow(i: number, row: Row): void;
|
|
27
|
+
removeRow(i: number): void;
|
|
28
|
+
getRow(i: number, safe?: boolean): Row | undefined;
|
|
29
|
+
getPartialRow(i: number, columns: string[], safe?: boolean): Row | undefined;
|
|
30
|
+
validateRow(row: Row): void;
|
|
31
|
+
validateColumnSpec(id: string, spec: ColumnSpec): void;
|
|
32
|
+
toJSON(): {
|
|
33
|
+
schema: ColumnSchema;
|
|
34
|
+
columns: Record<string, IColumn>;
|
|
35
|
+
length: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Registry of column type definitions and their factory functions. See
|
|
40
|
+
* {@link registerColumnType}.
|
|
41
|
+
*/
|
|
42
|
+
export declare const COLUMN_TYPES: Record<string, ColumnTypeSpec>;
|
|
43
|
+
/**
|
|
44
|
+
* Registers a custom column type for given `type` ID. Throws an error if `type`
|
|
45
|
+
* is already registered.
|
|
46
|
+
*
|
|
47
|
+
* @param type
|
|
48
|
+
* @param spec
|
|
49
|
+
*/
|
|
50
|
+
export declare const registerColumnType: (type: string, spec: ColumnTypeSpec) => void;
|
|
51
|
+
//# sourceMappingURL=table.d.ts.map
|