@thi.ng/column-store 0.1.1 → 0.1.3
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 +57 -3
- package/columns/acolumn.d.ts +1 -0
- package/columns/acolumn.js +3 -3
- package/columns/{dict-array.d.ts → dict-tuple.d.ts} +2 -2
- package/columns/{dict-array.js → dict-tuple.js} +2 -2
- package/columns/{array.d.ts → tuple.d.ts} +2 -2
- package/columns/{array.js → tuple.js} +2 -2
- package/package.json +7 -7
- package/query.d.ts +20 -0
- package/query.js +31 -10
- package/table.js +3 -3
package/README.md
CHANGED
|
@@ -49,6 +49,60 @@ In-memory column store database with customizable column types, extensible query
|
|
|
49
49
|
|
|
50
50
|
## Column storage
|
|
51
51
|
|
|
52
|
+
As the name indicates, data is stored in different columns, where each column
|
|
53
|
+
manages its own schema, value validation, backing storage, indexing,
|
|
54
|
+
serialization etc.
|
|
55
|
+
|
|
56
|
+
From a user's POV, columns are not (usually) used directly, but via table, which
|
|
57
|
+
acts as facade to the various configured columns. Data items are added as JS
|
|
58
|
+
objects to a table, which then pulls out related values, validates them and the
|
|
59
|
+
delegates them to the columns.
|
|
60
|
+
|
|
61
|
+
An example table definition looks like this (explanation of column types in next
|
|
62
|
+
section below):
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { Table, FLAG_DICT, FLAG_UNIQUE } from "@thi.ng/column-store";
|
|
66
|
+
|
|
67
|
+
// define a table with the given columns
|
|
68
|
+
const table = new Table({
|
|
69
|
+
|
|
70
|
+
// column of single numeric values
|
|
71
|
+
// (default cardinality makes values required)
|
|
72
|
+
id: { type: "num" },
|
|
73
|
+
|
|
74
|
+
// column of required string values
|
|
75
|
+
name: { type: "str" },
|
|
76
|
+
|
|
77
|
+
// optional tuples of max. 3 string values
|
|
78
|
+
// (if no value is given, the column stores null)
|
|
79
|
+
aliases: { type: "str", cardinality: [0, 3] },
|
|
80
|
+
|
|
81
|
+
// required fixed size tuples (aka vectors) of numbers
|
|
82
|
+
latlon: { type: "num", cardinality: [2, 2] },
|
|
83
|
+
|
|
84
|
+
// optional tuples of max. 10 strings, with default
|
|
85
|
+
// the given flags (explained further below) are triggering:
|
|
86
|
+
// - dictionary-based encoding
|
|
87
|
+
// - unique values (per tuple)
|
|
88
|
+
tags: {
|
|
89
|
+
type: "str",
|
|
90
|
+
cardinality: [0, 10],
|
|
91
|
+
default: ["todo"],
|
|
92
|
+
flags: FLAG_DICT | FLAG_UNIQUE
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// add data
|
|
97
|
+
table.addRow({
|
|
98
|
+
id: 1,
|
|
99
|
+
name: "karsten",
|
|
100
|
+
aliases: ["toxi"],
|
|
101
|
+
latlon: [47.421, 10.984],
|
|
102
|
+
tags: ["person", "opensource"],
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
52
106
|
### Column types
|
|
53
107
|
|
|
54
108
|
The current built-in column types only support numeric or string values, though
|
|
@@ -62,8 +116,8 @@ Note: Booleans and `BigInt`s are still unsupported, but being worked on...
|
|
|
62
116
|
|
|
63
117
|
| **Column type** | **Description** | **Tuples supported** | **RLE serialization** |
|
|
64
118
|
|-----------------|---------------------|----------------------|-----------------------|
|
|
65
|
-
| `num` | JS numbers | ✅ | ✅ <sup>(1)</sup>
|
|
66
|
-
| `str` | JS strings (UTF-16) | ✅ | ✅ <sup>(1)</sup>
|
|
119
|
+
| `num` | JS numbers | ✅ | ✅ <sup>(1)</sup> |
|
|
120
|
+
| `str` | JS strings (UTF-16) | ✅ | ✅ <sup>(1)</sup> |
|
|
67
121
|
| `u8` | 8bit unsigned int | ❌ | ✅ |
|
|
68
122
|
| `i8` | 8bit signed int | ❌ | ✅ |
|
|
69
123
|
| `u16` | 16bit unsigned int | ❌ | ✅ |
|
|
@@ -298,7 +352,7 @@ For Node.js REPL:
|
|
|
298
352
|
const cs = await import("@thi.ng/column-store");
|
|
299
353
|
```
|
|
300
354
|
|
|
301
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 4.
|
|
355
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 4.15 KB
|
|
302
356
|
|
|
303
357
|
## Dependencies
|
|
304
358
|
|
package/columns/acolumn.d.ts
CHANGED
package/columns/acolumn.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isArray } from "@thi.ng/checks";
|
|
2
1
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
3
2
|
import { FLAG_BITMAP } from "../api.js";
|
|
4
3
|
import { BitmapIndex } from "../bitmap.js";
|
|
@@ -33,12 +32,13 @@ class AColumn {
|
|
|
33
32
|
}
|
|
34
33
|
updateBitmap(rows) {
|
|
35
34
|
this.ensureBitmap();
|
|
36
|
-
const { bitmap } = this;
|
|
35
|
+
const { bitmap, isArray } = this;
|
|
37
36
|
if (!bitmap) return;
|
|
38
37
|
bitmap.clear();
|
|
39
38
|
for (let i = 0; i < rows.length; i++) {
|
|
40
39
|
const value = rows[i];
|
|
41
|
-
if (
|
|
40
|
+
if (value == null) continue;
|
|
41
|
+
if (isArray) {
|
|
42
42
|
for (let x of value) bitmap.setBit(x, i);
|
|
43
43
|
} else bitmap.setBit(value, i);
|
|
44
44
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BidirIndex } from "@thi.ng/bidir-index";
|
|
2
2
|
import { type IColumn, type SerializedColumn } from "../api.js";
|
|
3
3
|
import { AColumn } from "./acolumn.js";
|
|
4
|
-
export declare class
|
|
4
|
+
export declare class DictTupleColumn extends AColumn implements IColumn {
|
|
5
5
|
values: (number[] | null)[];
|
|
6
6
|
dict: BidirIndex<any>;
|
|
7
7
|
readonly isArray = true;
|
|
@@ -22,4 +22,4 @@ export declare class DictArrayColumn extends AColumn implements IColumn {
|
|
|
22
22
|
values: (number[] | null)[];
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
//# sourceMappingURL=dict-
|
|
25
|
+
//# sourceMappingURL=dict-tuple.d.ts.map
|
|
@@ -4,7 +4,7 @@ import { FLAG_UNIQUE } from "../api.js";
|
|
|
4
4
|
import { __validateArrayValue } from "../internal/checks.js";
|
|
5
5
|
import { __serializeDict } from "../internal/serialize.js";
|
|
6
6
|
import { AColumn } from "./acolumn.js";
|
|
7
|
-
class
|
|
7
|
+
class DictTupleColumn extends AColumn {
|
|
8
8
|
values = [];
|
|
9
9
|
dict = new BidirIndex();
|
|
10
10
|
isArray = true;
|
|
@@ -75,5 +75,5 @@ class DictArrayColumn extends AColumn {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
export {
|
|
78
|
-
|
|
78
|
+
DictTupleColumn
|
|
79
79
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Nullable } from "@thi.ng/api";
|
|
2
2
|
import { type IColumn, type SerializedColumn } from "../api.js";
|
|
3
3
|
import { AColumn } from "./acolumn.js";
|
|
4
|
-
export declare class
|
|
4
|
+
export declare class TupleColumn extends AColumn implements IColumn {
|
|
5
5
|
values: Nullable<number[]>[];
|
|
6
6
|
readonly isArray = true;
|
|
7
7
|
load(spec: SerializedColumn): void;
|
|
@@ -16,4 +16,4 @@ export declare class ArrayColumn extends AColumn implements IColumn {
|
|
|
16
16
|
values: Nullable<number[]>[];
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
//# sourceMappingURL=
|
|
19
|
+
//# sourceMappingURL=tuple.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { isArray } from "@thi.ng/checks/is-array";
|
|
|
2
2
|
import { FLAG_UNIQUE } from "../api.js";
|
|
3
3
|
import { __validateArrayValue } from "../internal/checks.js";
|
|
4
4
|
import { AColumn } from "./acolumn.js";
|
|
5
|
-
class
|
|
5
|
+
class TupleColumn extends AColumn {
|
|
6
6
|
values = [];
|
|
7
7
|
isArray = true;
|
|
8
8
|
load(spec) {
|
|
@@ -58,5 +58,5 @@ class ArrayColumn extends AColumn {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
export {
|
|
61
|
-
|
|
61
|
+
TupleColumn
|
|
62
62
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/column-store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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",
|
|
@@ -97,11 +97,8 @@
|
|
|
97
97
|
"./columns/acolumn": {
|
|
98
98
|
"default": "./columns/acolumn.js"
|
|
99
99
|
},
|
|
100
|
-
"./columns/
|
|
101
|
-
"default": "./columns/
|
|
102
|
-
},
|
|
103
|
-
"./columns/dict-array": {
|
|
104
|
-
"default": "./columns/dict-array.js"
|
|
100
|
+
"./columns/dict-tuple": {
|
|
101
|
+
"default": "./columns/dict-tuple.js"
|
|
105
102
|
},
|
|
106
103
|
"./columns/dict": {
|
|
107
104
|
"default": "./columns/dict.js"
|
|
@@ -109,6 +106,9 @@
|
|
|
109
106
|
"./columns/plain": {
|
|
110
107
|
"default": "./columns/plain.js"
|
|
111
108
|
},
|
|
109
|
+
"./columns/tuple": {
|
|
110
|
+
"default": "./columns/tuple.js"
|
|
111
|
+
},
|
|
112
112
|
"./columns/typedarray": {
|
|
113
113
|
"default": "./columns/typedarray.js"
|
|
114
114
|
},
|
|
@@ -123,5 +123,5 @@
|
|
|
123
123
|
"status": "alpha",
|
|
124
124
|
"year": 2025
|
|
125
125
|
},
|
|
126
|
-
"gitHead": "
|
|
126
|
+
"gitHead": "634df92bac82036b7f8c861fbf63210abdef3117\n"
|
|
127
127
|
}
|
package/query.d.ts
CHANGED
|
@@ -26,7 +26,27 @@ export declare class QueryCtx {
|
|
|
26
26
|
bitmap?: Uint32Array;
|
|
27
27
|
constructor(query: Query);
|
|
28
28
|
makeMask(seed?: Uint32Array): Uint32Array<ArrayBuffer>;
|
|
29
|
+
/**
|
|
30
|
+
* Combines the `mask` with the context's mask (combined using bitwise AND).
|
|
31
|
+
* If the context mask is still undefined, assigns `mask` as the initial
|
|
32
|
+
* context mask.
|
|
33
|
+
*
|
|
34
|
+
* @param mask
|
|
35
|
+
*/
|
|
29
36
|
mergeMask(mask: Uint32Array): void;
|
|
37
|
+
/**
|
|
38
|
+
* Combines the bitwise inverted `mask` with the context's mask (combined
|
|
39
|
+
* using bitwise AND). If the context mask is still undefined, first inverts
|
|
40
|
+
* `mask` in place and uses result as the initial context mask.
|
|
41
|
+
*
|
|
42
|
+
* @param mask
|
|
43
|
+
*/
|
|
44
|
+
mergeInvMask(mask: Uint32Array): void;
|
|
45
|
+
/**
|
|
46
|
+
* Bitwise inverts `mask` in place and then returns it.
|
|
47
|
+
*
|
|
48
|
+
* @param mask
|
|
49
|
+
*/
|
|
30
50
|
invertMask(mask: Uint32Array): Uint32Array<ArrayBufferLike>;
|
|
31
51
|
}
|
|
32
52
|
/**
|
package/query.js
CHANGED
|
@@ -5,10 +5,11 @@ import { Bitfield } from "./bitmap.js";
|
|
|
5
5
|
class Query {
|
|
6
6
|
constructor(table, terms = []) {
|
|
7
7
|
this.table = table;
|
|
8
|
-
|
|
8
|
+
for (let term of terms) this.addTerm(term);
|
|
9
9
|
}
|
|
10
10
|
terms = [];
|
|
11
11
|
addTerm(term) {
|
|
12
|
+
if (!QUERY_OPS[term.type]) unsupportedOp(`query type: ${term.type}`);
|
|
12
13
|
this.terms.push(term);
|
|
13
14
|
return this;
|
|
14
15
|
}
|
|
@@ -53,7 +54,6 @@ class Query {
|
|
|
53
54
|
const ctx = new QueryCtx(this);
|
|
54
55
|
for (let term of this.terms) {
|
|
55
56
|
const op = QUERY_OPS[term.type];
|
|
56
|
-
if (!op) unsupportedOp(`query type: ${term.type}`);
|
|
57
57
|
let column;
|
|
58
58
|
if (term.column) {
|
|
59
59
|
column = ctx.table.columns[term.column];
|
|
@@ -85,12 +85,37 @@ class QueryCtx {
|
|
|
85
85
|
if (seed) mask.set(seed);
|
|
86
86
|
return mask;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Combines the `mask` with the context's mask (combined using bitwise AND).
|
|
90
|
+
* If the context mask is still undefined, assigns `mask` as the initial
|
|
91
|
+
* context mask.
|
|
92
|
+
*
|
|
93
|
+
* @param mask
|
|
94
|
+
*/
|
|
88
95
|
mergeMask(mask) {
|
|
89
96
|
if (this.bitmap) {
|
|
90
97
|
for (let i = 0, n = this.size; i < n; i++)
|
|
91
98
|
this.bitmap[i] &= mask[i];
|
|
92
99
|
} else this.bitmap = mask;
|
|
93
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Combines the bitwise inverted `mask` with the context's mask (combined
|
|
103
|
+
* using bitwise AND). If the context mask is still undefined, first inverts
|
|
104
|
+
* `mask` in place and uses result as the initial context mask.
|
|
105
|
+
*
|
|
106
|
+
* @param mask
|
|
107
|
+
*/
|
|
108
|
+
mergeInvMask(mask) {
|
|
109
|
+
if (this.bitmap) {
|
|
110
|
+
for (let i = 0, n = this.size; i < n; i++)
|
|
111
|
+
this.bitmap[i] &= mask[i] ^ -1;
|
|
112
|
+
} else this.bitmap = this.invertMask(mask);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Bitwise inverts `mask` in place and then returns it.
|
|
116
|
+
*
|
|
117
|
+
* @param mask
|
|
118
|
+
*/
|
|
94
119
|
invertMask(mask) {
|
|
95
120
|
for (let i = 0, n = this.size; i < n; i++) mask[i] ^= -1;
|
|
96
121
|
return mask;
|
|
@@ -113,8 +138,7 @@ const execBitOr = (ctx, term, column) => {
|
|
|
113
138
|
if (b) mask = ctx.makeMask(b);
|
|
114
139
|
}
|
|
115
140
|
if (mask) {
|
|
116
|
-
|
|
117
|
-
ctx.mergeMask(mask);
|
|
141
|
+
term.type === "nor" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
118
142
|
}
|
|
119
143
|
};
|
|
120
144
|
const execOr = (ctx, term, column) => {
|
|
@@ -132,8 +156,7 @@ const execOr = (ctx, term, column) => {
|
|
|
132
156
|
}
|
|
133
157
|
}
|
|
134
158
|
if (mask) {
|
|
135
|
-
|
|
136
|
-
ctx.mergeMask(mask);
|
|
159
|
+
term.type === "nor" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
137
160
|
}
|
|
138
161
|
};
|
|
139
162
|
const delegateOr = (ctx, term, column) => {
|
|
@@ -168,8 +191,7 @@ const execBitAnd = (ctx, term, column) => {
|
|
|
168
191
|
if (b) mask = ctx.makeMask(b);
|
|
169
192
|
}
|
|
170
193
|
if (mask) {
|
|
171
|
-
|
|
172
|
-
ctx.mergeMask(mask);
|
|
194
|
+
term.type === "nand" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
173
195
|
} else {
|
|
174
196
|
ctx.bitmap = void 0;
|
|
175
197
|
}
|
|
@@ -198,8 +220,7 @@ const execAnd = (ctx, term, column) => {
|
|
|
198
220
|
}
|
|
199
221
|
}
|
|
200
222
|
if (mask) {
|
|
201
|
-
|
|
202
|
-
ctx.mergeMask(mask);
|
|
223
|
+
term.type === "nand" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
203
224
|
}
|
|
204
225
|
};
|
|
205
226
|
const delegateAnd = (ctx, term, column) => {
|
package/table.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
FLAG_RLE,
|
|
7
7
|
FLAG_UNIQUE
|
|
8
8
|
} from "./api.js";
|
|
9
|
-
import {
|
|
10
|
-
import { DictArrayColumn } from "./columns/dict-array.js";
|
|
9
|
+
import { DictTupleColumn } from "./columns/dict-tuple.js";
|
|
11
10
|
import { DictColumn } from "./columns/dict.js";
|
|
12
11
|
import { PlainColumn } from "./columns/plain.js";
|
|
12
|
+
import { TupleColumn } from "./columns/tuple.js";
|
|
13
13
|
import { TypedArrayColumn } from "./columns/typedarray.js";
|
|
14
14
|
import { __columnError } from "./internal/checks.js";
|
|
15
15
|
import { Query } from "./query.js";
|
|
@@ -147,7 +147,7 @@ const $untyped = {
|
|
|
147
147
|
__columnError(id, `RLE encoding not supported`);
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
return max > 1 ? new (isDict ?
|
|
150
|
+
return max > 1 ? new (isDict ? DictTupleColumn : TupleColumn)(id, table) : new (isDict ? DictColumn : PlainColumn)(id, table);
|
|
151
151
|
},
|
|
152
152
|
flags: FLAG_BITMAP | FLAG_DICT | FLAG_UNIQUE | FLAG_RLE,
|
|
153
153
|
cardinality: [0, -1 >>> 0]
|