@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 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.11 KB
355
+ Package sizes (brotli'd, pre-treeshake): ESM: 4.15 KB
302
356
 
303
357
  ## Dependencies
304
358
 
@@ -8,6 +8,7 @@ export declare abstract class AColumn {
8
8
  spec: ColumnSpec;
9
9
  bitmap?: BitmapIndex;
10
10
  dict?: BidirIndex<any>;
11
+ abstract isArray: boolean;
11
12
  constructor(id: string, table: Table);
12
13
  encode(value: any): any;
13
14
  decode(value: any): any;
@@ -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 (isArray(value)) {
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 DictArrayColumn extends AColumn implements IColumn {
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-array.d.ts.map
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 DictArrayColumn extends AColumn {
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
- DictArrayColumn
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 ArrayColumn extends AColumn implements IColumn {
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=array.d.ts.map
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 ArrayColumn extends AColumn {
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
- ArrayColumn
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.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/array": {
101
- "default": "./columns/array.js"
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": "28440e6d826cce964928f4c2ffe1f0e3e2e25ab0\n"
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
- this.terms = terms;
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
- if (term.type === "nor") ctx.invertMask(mask);
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
- if (term.type === "nor") ctx.invertMask(mask);
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
- if (term.type === "nand") ctx.invertMask(mask);
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
- if (term.type === "nand") ctx.invertMask(mask);
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 { ArrayColumn } from "./columns/array.js";
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 ? DictArrayColumn : ArrayColumn)(id, table) : new (isDict ? DictColumn : PlainColumn)(id, table);
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]