@thi.ng/column-store 0.1.1 → 0.2.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 CHANGED
@@ -17,6 +17,8 @@
17
17
  - [About](#about)
18
18
  - [Column storage](#column-storage)
19
19
  - [Column types](#column-types)
20
+ - [Vector column types](#vector-column-types)
21
+ - [Serialization options](#serialization-options)
20
22
  - [Custom column types](#custom-column-types)
21
23
  - [Cardinality](#cardinality)
22
24
  - [Default values](#default-values)
@@ -49,6 +51,60 @@ In-memory column store database with customizable column types, extensible query
49
51
 
50
52
  ## Column storage
51
53
 
54
+ As the name indicates, data is stored in different columns, where each column
55
+ manages its own schema, value validation, backing storage, indexing,
56
+ serialization etc.
57
+
58
+ From a user's POV, columns are not (usually) used directly, but via table, which
59
+ acts as facade to the various configured columns. Data items are added as JS
60
+ objects to a table, which then pulls out related values, validates them and the
61
+ delegates them to the columns.
62
+
63
+ An example table definition looks like this (explanation of column types in next
64
+ section below):
65
+
66
+ ```ts
67
+ import { Table, FLAG_DICT, FLAG_UNIQUE } from "@thi.ng/column-store";
68
+
69
+ // define a table with the given columns
70
+ const table = new Table({
71
+
72
+ // column of single numeric values
73
+ // (default cardinality makes values required)
74
+ id: { type: "num" },
75
+
76
+ // column of required string values
77
+ name: { type: "str" },
78
+
79
+ // optional tuples of max. 3 string values
80
+ // (if no value is given, the column stores null)
81
+ aliases: { type: "str", cardinality: [0, 3] },
82
+
83
+ // required fixed size tuples (aka vectors) of numbers
84
+ latlon: { type: "num", cardinality: [2, 2] },
85
+
86
+ // optional tuples of max. 10 strings, with default
87
+ // the given flags (explained further below) are triggering:
88
+ // - dictionary-based encoding
89
+ // - unique values (per tuple)
90
+ tags: {
91
+ type: "str",
92
+ cardinality: [0, 10],
93
+ default: ["todo"],
94
+ flags: FLAG_DICT | FLAG_UNIQUE
95
+ }
96
+ });
97
+
98
+ // add data
99
+ table.addRow({
100
+ id: 1,
101
+ name: "karsten",
102
+ aliases: ["toxi"],
103
+ latlon: [47.421, 10.984],
104
+ tags: ["person", "opensource"],
105
+ });
106
+ ```
107
+
52
108
  ### Column types
53
109
 
54
110
  The current built-in column types only support numeric or string values, though
@@ -62,8 +118,8 @@ Note: Booleans and `BigInt`s are still unsupported, but being worked on...
62
118
 
63
119
  | **Column type** | **Description** | **Tuples supported** | **RLE serialization** |
64
120
  |-----------------|---------------------|----------------------|-----------------------|
65
- | `num` | JS numbers | ✅ | ✅ <sup>(1)</sup> |
66
- | `str` | JS strings (UTF-16) | ✅ | ✅ <sup>(1)</sup> |
121
+ | `num` | JS numbers | ✅ | ✅ <sup>(1)</sup> |
122
+ | `str` | JS strings (UTF-16) | ✅ | ✅ <sup>(1)</sup> |
67
123
  | `u8` | 8bit unsigned int | ❌ | ✅ |
68
124
  | `i8` | 8bit signed int | ❌ | ✅ |
69
125
  | `u16` | 16bit unsigned int | ❌ | ✅ |
@@ -73,7 +129,56 @@ Note: Booleans and `BigInt`s are still unsupported, but being worked on...
73
129
  | `f32` | 32bit float | ❌ | ❌ |
74
130
  | `f64` | 64bit float | ❌ | ❌ |
75
131
 
76
- - <sup>(1)</sup> only if `FLAG_DICT` is enabled, [further information](#flag_rle)
132
+ - <sup>(1)</sup> only if max. cardinality is 1, [further information](#flag_rle)
133
+
134
+ ### Vector column types
135
+
136
+ Columns storing fixed size n-dimensional vectors can be created vis the `vec`
137
+ suffix for any of the typedarray based column types, i.e. `u8vec`, `i16vec`,
138
+ `f32vec` etc.
139
+
140
+ The `cardinality` column config (in the form `[min,max]`) is interpreted as follows:
141
+
142
+ - if `min` is zero, the value is optional, but a `default` value MUST be defined
143
+ for the column. Otherwise the `min` value MUST be the same as `max`.
144
+ - `max` defines the actual vector size
145
+
146
+ Therefore, using a 3D vector as example, the only two possible `cardinality`
147
+ configs are: `[0,3]` (with default given) or `[3,3]`.
148
+
149
+ When [querying](#query-engine) vector columns using the standard
150
+ `(n)or`/`(n)and` operators, always the entire vector is matched (by value).
151
+
152
+ > [!IMPORTANT] For performance reasons, rows retrieved from vector columns
153
+ > contain mutable data views of the underlying column storage. That means when
154
+ > manipulating data in these views, the underlying data in the column would be
155
+ > changed too. To avoid index corruption, always edit only copies of this vector
156
+ > data and then use `table.updateRow()` to properly update the column storage
157
+ > (incl. any internal indexes).
158
+
159
+ ### Serialization options
160
+
161
+ For `f32`, `f64`, `f32vec` and `f64vec` column types, the optional `prec` column
162
+ option can be provided to specify the number of fractional digits used in
163
+ the JSON serialization:
164
+
165
+ ```ts tangle:export/readme-serialize-prec.ts
166
+ import { Table } from "@thi.ng/column-store";
167
+
168
+ const table = new Table({
169
+ vec: { type: "f32vec", cardinality: [3, 3], opts: { prec: 2 } }
170
+ });
171
+
172
+ table.addRow({ vec: [1.11111, 22.22222, 333.33333]});
173
+
174
+ console.log(JSON.stringify(table));
175
+ // {
176
+ // "schema":{"vec":{"cardinality":[3,3],"flags":0,"type":"f32vec","opts":{"prec":2}}},
177
+ // "columns":{"vec":{"values":[1.11,22.22,333.33]}},
178
+ // "length":1
179
+ // }
180
+
181
+ ```
77
182
 
78
183
  ### Custom column types
79
184
 
@@ -159,15 +264,18 @@ Note: Not supported by typedarray-backed column types.
159
264
 
160
265
  (Value: 0x08)
161
266
 
162
- This flag enables bitwise [Run-length encoding](https://thi.ng/rle-pack) in the
267
+ This flag enables [Run-length encoding](https://thi.ng/rle-pack) in the
163
268
  JSON serialization of a column, potentially leading to dramatic file size
164
269
  savings, esp. for dictionary-based data.
165
270
 
166
- Only applicable to these column types & configurations:
271
+ Two modes of RLE compression are supported, depending on column type:
272
+
273
+ - Simple RLE merely stores arrays in `[value1, count1, value2, count2...]` form
274
+ - Binary RLE uses more advanced bitwise encoding, but is only available for
275
+ typedarray and vector columns
167
276
 
168
- - typedarray-based integer columns (see [table](#column-types))
169
- - dictionary-based single value columns (if the min. cardinality is zero, a
170
- default value **must** be supplied)
277
+ Tuple-based columns do not support RLE and will throw an error at creation time
278
+ when trying to use this flag.
171
279
 
172
280
  #### Custom flags
173
281
 
@@ -298,7 +406,7 @@ For Node.js REPL:
298
406
  const cs = await import("@thi.ng/column-store");
299
407
  ```
300
408
 
301
- Package sizes (brotli'd, pre-treeshake): ESM: 4.11 KB
409
+ Package sizes (brotli'd, pre-treeshake): ESM: 4.65 KB
302
410
 
303
411
  ## Dependencies
304
412
 
package/api.d.ts CHANGED
@@ -1,15 +1,54 @@
1
- import type { FloatType, Fn3, IntType, Maybe, TypedArray, UintType } from "@thi.ng/api";
1
+ import type { FloatType, Fn3, IntType, Maybe, UintType } from "@thi.ng/api";
2
2
  import type { BitmapIndex } from "./bitmap.js";
3
3
  import type { QueryCtx } from "./query.js";
4
4
  import type { Table } from "./table.js";
5
5
  export type ColumnSchema = Record<string, ColumnSpec>;
6
6
  export type NumericType = IntType | UintType | FloatType;
7
+ export type VectorType = `${NumericType}vec`;
7
8
  export type Cardinality = [number, number];
8
9
  export interface ColumnSpec {
9
- type: NumericType | "num" | "str" | string;
10
+ /**
11
+ * Column type ID (see readme for overview)
12
+ */
13
+ type: NumericType | VectorType | "num" | "str" | string;
14
+ /**
15
+ * `[min,max]` number of allowed values per row. The following cardinality
16
+ * presets are available in general (but not for all column types, see
17
+ * readme for overview):
18
+ *
19
+ * - {@link REQUIRED}: [1,1] (default) — value is required
20
+ * - {@link OPTIONAL}: [0,1] — value is optional
21
+ * - {@link ZERO_PLUS}: [0, (2**32)-1] — zero or more values
22
+ * - {@link ONE_PLUS}: [1, (2**32)-1] — one or more values
23
+ *
24
+ * Note: Some column types always require a value. So when using
25
+ * {@link OPTIONAL}, you might also need to provide a
26
+ * {@link ColumnSpec.default} value.
27
+ */
10
28
  cardinality: Cardinality;
29
+ /**
30
+ * Bit mask to control column behavior/encoding. The lowest 16 bits are
31
+ * reserved for built-in column types and internal use. The upper 16 bits
32
+ * are freely usable for custom purposes.
33
+ *
34
+ * @remarks
35
+ * The following built-in flags are available in general (but not for all
36
+ * column types, see readme for overview):
37
+ *
38
+ * - {@link FLAG_BITMAP}: Enable bitmap indexing
39
+ * - {@link FLAG_DICT}: Enable dictionary encoding of values
40
+ * - {@link FLAG_UNIQUE}: Enable Set-semantics, i.e. unique values per tuple
41
+ * - {@link FLAG_RLE}: Enable run-length encoding in serialization
42
+ */
11
43
  flags: number;
44
+ /**
45
+ * Default value
46
+ */
12
47
  default?: any;
48
+ /**
49
+ * Columntype specific options (e.g. for serialization)
50
+ */
51
+ opts?: Record<string, any>;
13
52
  }
14
53
  export interface ColumnTypeSpec {
15
54
  /**
@@ -42,8 +81,9 @@ export declare const FLAG_DICT: number;
42
81
  export declare const FLAG_BITMAP: number;
43
82
  export declare const FLAG_UNIQUE: number;
44
83
  export declare const FLAG_RLE: number;
84
+ /** @internal */
85
+ export declare const LIMITS: Record<NumericType, [number, number]>;
45
86
  export interface IColumn {
46
- values: any[] | TypedArray;
47
87
  bitmap?: BitmapIndex;
48
88
  readonly isArray: boolean;
49
89
  load(spec: SerializedColumn): void;
@@ -65,6 +105,8 @@ export interface IColumn {
65
105
  encode(value: any): any;
66
106
  decode(value: any): any;
67
107
  replaceValue(currValue: any, newValue: any): boolean;
108
+ getRowKey(i: number): any;
109
+ valueKey(value: any): any;
68
110
  }
69
111
  export interface SerializedTable {
70
112
  schema: ColumnSchema;
package/api.js CHANGED
@@ -6,11 +6,23 @@ const FLAG_DICT = 1 << 0;
6
6
  const FLAG_BITMAP = 1 << 1;
7
7
  const FLAG_UNIQUE = 1 << 2;
8
8
  const FLAG_RLE = 1 << 3;
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
+ };
9
20
  export {
10
21
  FLAG_BITMAP,
11
22
  FLAG_DICT,
12
23
  FLAG_RLE,
13
24
  FLAG_UNIQUE,
25
+ LIMITS,
14
26
  ONE_PLUS,
15
27
  OPTIONAL,
16
28
  REQUIRED,
@@ -1,18 +1,28 @@
1
1
  import type { BidirIndex } from "@thi.ng/bidir-index";
2
- import { type ColumnSpec, type SerializedIndex } from "../api.js";
2
+ import { type ColumnSpec, type IColumn, type SerializedColumn, type SerializedIndex } from "../api.js";
3
3
  import { BitmapIndex } from "../bitmap.js";
4
4
  import type { Table } from "../table.js";
5
- export declare abstract class AColumn {
5
+ export declare abstract class AColumn implements IColumn {
6
6
  readonly id: string;
7
7
  table: Table;
8
8
  spec: ColumnSpec;
9
9
  bitmap?: BitmapIndex;
10
10
  dict?: BidirIndex<any>;
11
+ abstract isArray: boolean;
11
12
  constructor(id: string, table: Table);
13
+ abstract load(spec: SerializedColumn): void;
14
+ reindex(): void;
15
+ abstract validate(value: any): boolean;
16
+ abstract setRow(i: number, value: any): void;
17
+ abstract removeRow(i: number): void;
18
+ abstract replaceValue(currValue: any, newValue: any): boolean;
19
+ abstract valueKey(x: any): any;
20
+ abstract getRow(i: number): any;
21
+ abstract getRowKey(i: number): any;
12
22
  encode(value: any): any;
13
23
  decode(value: any): any;
14
24
  protected loadDict(serialized: SerializedIndex): void;
15
- protected updateBitmap(rows: ArrayLike<any>): void;
25
+ protected updateBitmap(): void;
16
26
  protected ensureValue(val: any): any;
17
27
  protected ensureBitmap(): void;
18
28
  }
@@ -1,6 +1,7 @@
1
- import { isArray } from "@thi.ng/checks";
2
1
  import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
3
- import { FLAG_BITMAP } from "../api.js";
2
+ import {
3
+ FLAG_BITMAP
4
+ } from "../api.js";
4
5
  import { BitmapIndex } from "../bitmap.js";
5
6
  class AColumn {
6
7
  constructor(id, table) {
@@ -12,6 +13,9 @@ class AColumn {
12
13
  spec;
13
14
  bitmap;
14
15
  dict;
16
+ reindex() {
17
+ this.updateBitmap();
18
+ }
15
19
  encode(value) {
16
20
  return value;
17
21
  }
@@ -31,16 +35,17 @@ class AColumn {
31
35
  }
32
36
  dict.nextID = serialized.next;
33
37
  }
34
- updateBitmap(rows) {
38
+ updateBitmap() {
35
39
  this.ensureBitmap();
36
- const { bitmap } = this;
40
+ const { bitmap, isArray } = this;
37
41
  if (!bitmap) return;
38
42
  bitmap.clear();
39
- for (let i = 0; i < rows.length; i++) {
40
- const value = rows[i];
41
- if (isArray(value)) {
43
+ for (let i = 0, n = this.table.length; i < n; i++) {
44
+ const value = this.getRow(i);
45
+ if (value == null) continue;
46
+ if (isArray) {
42
47
  for (let x of value) bitmap.setBit(x, i);
43
- } else bitmap.setBit(value, i);
48
+ } else bitmap.setBit(this.getRowKey(i), i);
44
49
  }
45
50
  }
46
51
  ensureValue(val) {
@@ -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;
@@ -12,6 +12,8 @@ export declare class DictArrayColumn extends AColumn implements IColumn {
12
12
  validate(value: any): boolean;
13
13
  setRow(i: number, value: any[]): void;
14
14
  getRow(i: number): any[] | null;
15
+ getRowKey(i: number): number[] | null;
16
+ valueKey(value: any): (number | null)[];
15
17
  removeRow(i: number): void;
16
18
  replaceValue(currValue: any, newValue: any): boolean;
17
19
  toJSON(): {
@@ -22,4 +24,4 @@ export declare class DictArrayColumn extends AColumn implements IColumn {
22
24
  values: (number[] | null)[];
23
25
  };
24
26
  }
25
- //# sourceMappingURL=dict-array.d.ts.map
27
+ //# sourceMappingURL=dict-tuple.d.ts.map
@@ -4,14 +4,14 @@ 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;
11
11
  load({ dict, values }) {
12
12
  this.values = values;
13
13
  super.loadDict(dict);
14
- super.updateBitmap(this.values);
14
+ super.updateBitmap();
15
15
  }
16
16
  reindex() {
17
17
  const dict = this.dict;
@@ -20,7 +20,7 @@ class DictArrayColumn extends AColumn {
20
20
  (ids) => ids ? newDict.addAll(dict.getAllIDs(ids)) : null
21
21
  );
22
22
  this.dict = newDict;
23
- super.updateBitmap(this.values);
23
+ super.updateBitmap();
24
24
  }
25
25
  encode(value) {
26
26
  return this.dict.getAll(isArray(value) ? value : [value], false, true);
@@ -45,6 +45,12 @@ class DictArrayColumn extends AColumn {
45
45
  const values = this.values[i];
46
46
  return values != null ? this.dict.getAllIDs(values) : null;
47
47
  }
48
+ getRowKey(i) {
49
+ return this.values[i];
50
+ }
51
+ valueKey(value) {
52
+ return this.encode(value);
53
+ }
48
54
  removeRow(i) {
49
55
  this.values.splice(i, 1);
50
56
  this.bitmap?.removeBit(i);
@@ -75,5 +81,5 @@ class DictArrayColumn extends AColumn {
75
81
  }
76
82
  }
77
83
  export {
78
- DictArrayColumn
84
+ DictTupleColumn
79
85
  };
package/columns/dict.d.ts CHANGED
@@ -12,6 +12,8 @@ export declare class DictColumn extends AColumn implements IColumn {
12
12
  validate(value: any): boolean;
13
13
  setRow(i: number, value: any): void;
14
14
  getRow(i: number): any;
15
+ getRowKey(i: number): number | null;
16
+ valueKey(value: any): number | number[] | undefined;
15
17
  removeRow(i: number): void;
16
18
  replaceValue(currValue: any, newValue: any): boolean;
17
19
  toJSON(): {
package/columns/dict.js CHANGED
@@ -1,17 +1,19 @@
1
1
  import { BidirIndex } from "@thi.ng/bidir-index";
2
- import { decode as decodeRLE, encode as encodeRLE } from "@thi.ng/rle-pack";
2
+ import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
3
+ import { decodeSimple, encodeSimple } from "@thi.ng/rle-pack/simple";
3
4
  import { FLAG_RLE } from "../api.js";
4
5
  import { __validateValue } from "../internal/checks.js";
5
6
  import { __serializeDict } from "../internal/serialize.js";
6
7
  import { AColumn } from "./acolumn.js";
8
+ import { isArray } from "@thi.ng/checks/is-array";
7
9
  class DictColumn extends AColumn {
8
10
  values = [];
9
11
  dict = new BidirIndex();
10
12
  isArray = false;
11
13
  load({ dict, values }) {
12
- this.values = this.spec.flags & FLAG_RLE ? Array.from(decodeRLE(values)) : values;
14
+ this.values = this.spec.flags & FLAG_RLE ? this.spec.cardinality[0] === 0 && this.spec.default == null ? decodeSimple(values) : Array.from(decodeBinary(values)) : values;
13
15
  super.loadDict(dict);
14
- super.updateBitmap(this.values);
16
+ super.updateBitmap();
15
17
  }
16
18
  reindex() {
17
19
  const dict = this.dict;
@@ -20,7 +22,7 @@ class DictColumn extends AColumn {
20
22
  (x) => x != null ? newDict.add(dict.getID(x)) : null
21
23
  );
22
24
  this.dict = newDict;
23
- super.updateBitmap(this.values);
25
+ super.updateBitmap();
24
26
  }
25
27
  encode(value) {
26
28
  return this.dict.get(value);
@@ -45,6 +47,12 @@ class DictColumn extends AColumn {
45
47
  const value = this.values[i];
46
48
  return value != null ? this.dict.getID(value) : null;
47
49
  }
50
+ getRowKey(i) {
51
+ return this.values[i];
52
+ }
53
+ valueKey(value) {
54
+ return isArray(value) ? this.dict.getAll(value) : this.dict.get(value);
55
+ }
48
56
  removeRow(i) {
49
57
  this.values.splice(i, 1);
50
58
  this.bitmap?.removeBit(i);
@@ -67,12 +75,19 @@ class DictColumn extends AColumn {
67
75
  return true;
68
76
  }
69
77
  toJSON() {
70
- let values = this.values;
71
- if (this.spec.flags & FLAG_RLE) {
72
- const numBits = Math.max(1, Math.ceil(Math.log2(this.dict.size)));
73
- values = Array.from(
74
- encodeRLE(values, values.length, numBits)
75
- );
78
+ let { values, spec } = this;
79
+ if (spec.flags & FLAG_RLE) {
80
+ if (spec.cardinality[0] == 0 && spec.default == null) {
81
+ values = encodeSimple(values);
82
+ } else {
83
+ const numBits = Math.max(
84
+ 1,
85
+ Math.ceil(Math.log2(this.dict.size))
86
+ );
87
+ values = Array.from(
88
+ encodeBinary(values, values.length, numBits)
89
+ );
90
+ }
76
91
  }
77
92
  return { dict: __serializeDict(this.dict), values };
78
93
  }
@@ -1,13 +1,14 @@
1
- import type { IColumn, SerializedColumn } from "../api.js";
1
+ import { type IColumn, type SerializedColumn } from "../api.js";
2
2
  import { AColumn } from "./acolumn.js";
3
3
  export declare class PlainColumn extends AColumn implements IColumn {
4
4
  values: any[];
5
5
  readonly isArray = false;
6
- load(spec: SerializedColumn): void;
7
- reindex(): void;
6
+ load({ values }: SerializedColumn): void;
8
7
  validate(value: any): boolean;
9
8
  setRow(i: number, value: any): void;
10
9
  getRow(i: number): any;
10
+ getRowKey(i: number): any;
11
+ valueKey(x: any): any;
11
12
  removeRow(i: number): void;
12
13
  replaceValue(currValue: any, newValue: any): boolean;
13
14
  toJSON(): {
package/columns/plain.js CHANGED
@@ -1,16 +1,15 @@
1
+ import { decodeSimple, encodeSimple } from "@thi.ng/rle-pack/simple";
2
+ import { FLAG_RLE } from "../api.js";
1
3
  import { __validateValue } from "../internal/checks.js";
2
4
  import { __replaceValue } from "../internal/replace.js";
3
5
  import { AColumn } from "./acolumn.js";
4
6
  class PlainColumn extends AColumn {
5
7
  values = [];
6
8
  isArray = false;
7
- load(spec) {
8
- this.values = spec.values;
9
+ load({ values }) {
10
+ this.values = this.spec.flags & FLAG_RLE ? Array.from(decodeSimple(values)) : values;
9
11
  this.reindex();
10
12
  }
11
- reindex() {
12
- super.updateBitmap(this.values);
13
- }
14
13
  validate(value) {
15
14
  return __validateValue(this.spec, value);
16
15
  }
@@ -26,6 +25,12 @@ class PlainColumn extends AColumn {
26
25
  getRow(i) {
27
26
  return this.values[i];
28
27
  }
28
+ getRowKey(i) {
29
+ return this.values[i];
30
+ }
31
+ valueKey(x) {
32
+ return x;
33
+ }
29
34
  removeRow(i) {
30
35
  this.values.splice(i, 1);
31
36
  this.bitmap?.removeBit(i);
@@ -34,7 +39,9 @@ class PlainColumn extends AColumn {
34
39
  return __replaceValue(this.bitmap, this.values, currValue, newValue);
35
40
  }
36
41
  toJSON() {
37
- return { values: this.values };
42
+ return {
43
+ values: this.spec.flags & FLAG_RLE ? encodeSimple(this.values) : this.values
44
+ };
38
45
  }
39
46
  }
40
47
  export {
@@ -1,19 +1,20 @@
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;
8
- reindex(): void;
9
8
  encode(value: any): any[];
10
9
  validate(value: any): boolean;
11
10
  setRow(i: number, value: any[]): void;
12
11
  getRow(i: number): Nullable<number[]>;
12
+ getRowKey(i: number): Nullable<number[]>;
13
+ valueKey(value: any): any[];
13
14
  removeRow(i: number): void;
14
15
  replaceValue(currValue: any, newValue: any): boolean;
15
16
  toJSON(): {
16
17
  values: Nullable<number[]>[];
17
18
  };
18
19
  }
19
- //# sourceMappingURL=array.d.ts.map
20
+ //# sourceMappingURL=tuple.d.ts.map
@@ -2,16 +2,13 @@ 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) {
9
9
  this.values = spec.values;
10
10
  this.reindex();
11
11
  }
12
- reindex() {
13
- super.updateBitmap(this.values);
14
- }
15
12
  encode(value) {
16
13
  return isArray(value) ? value : [value];
17
14
  }
@@ -31,6 +28,12 @@ class ArrayColumn extends AColumn {
31
28
  getRow(i) {
32
29
  return this.values[i];
33
30
  }
31
+ getRowKey(i) {
32
+ return this.values[i];
33
+ }
34
+ valueKey(value) {
35
+ return this.encode(value);
36
+ }
34
37
  removeRow(i) {
35
38
  this.values.splice(i, 1);
36
39
  this.bitmap?.removeBit(i);
@@ -58,5 +61,5 @@ class ArrayColumn extends AColumn {
58
61
  }
59
62
  }
60
63
  export {
61
- ArrayColumn
64
+ TupleColumn
62
65
  };
@@ -6,13 +6,15 @@ export declare class TypedArrayColumn extends AColumn implements IColumn {
6
6
  values: TypedArray;
7
7
  type: NumericType;
8
8
  limit: [number, number];
9
+ protected tmp: TypedArray;
9
10
  readonly isArray = false;
10
11
  constructor(id: string, table: Table);
11
12
  load(spec: SerializedColumn): void;
12
- reindex(): void;
13
13
  validate(value: any): boolean;
14
14
  setRow(i: number, value: any): void;
15
15
  getRow(i: number): number;
16
+ getRowKey(i: number): number;
17
+ valueKey(value: any): number | number[];
16
18
  removeRow(i: number): void;
17
19
  replaceValue(currValue: any, newValue: any): boolean;
18
20
  toJSON(): {
@@ -1,45 +1,35 @@
1
1
  import { SIZEOF, typedArray } from "@thi.ng/api/typedarray";
2
2
  import { isNumber } from "@thi.ng/checks/is-number";
3
- import { decode as decodeRLE, encode as encodeRLE } from "@thi.ng/rle-pack";
3
+ import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
4
4
  import {
5
- FLAG_RLE
5
+ FLAG_RLE,
6
+ LIMITS
6
7
  } from "../api.js";
7
8
  import { __replaceValue } from "../internal/replace.js";
8
9
  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
- };
10
+ import { isArray } from "@thi.ng/checks/is-array";
20
11
  class TypedArrayColumn extends AColumn {
21
12
  values;
22
13
  type;
23
14
  limit;
15
+ tmp;
24
16
  isArray = false;
25
17
  constructor(id, table) {
26
18
  super(id, table);
27
19
  this.type = table.schema[id].type;
28
20
  this.limit = LIMITS[this.type];
29
21
  this.values = typedArray(this.type, 8);
22
+ this.tmp = typedArray(this.type, 1);
30
23
  }
31
24
  load(spec) {
32
25
  if (this.spec.flags & FLAG_RLE) {
33
- const values = decodeRLE(spec.values);
26
+ const values = decodeBinary(spec.values);
34
27
  this.values = typedArray(this.type, values.buffer);
35
28
  } else {
36
29
  this.values = typedArray(this.type, spec.values);
37
30
  }
38
31
  this.reindex();
39
32
  }
40
- reindex() {
41
- super.updateBitmap(this.values.subarray(0, this.table.length));
42
- }
43
33
  validate(value) {
44
34
  return isNumber(value) && value >= this.limit[0] && value <= this.limit[1] || value == null && this.spec.default != null;
45
35
  }
@@ -63,6 +53,21 @@ class TypedArrayColumn extends AColumn {
63
53
  getRow(i) {
64
54
  return this.values[i];
65
55
  }
56
+ getRowKey(i) {
57
+ return this.values[i];
58
+ }
59
+ valueKey(value) {
60
+ const { tmp } = this;
61
+ if (isArray(value)) {
62
+ return value.map((x) => {
63
+ tmp[0] = x;
64
+ return tmp[0];
65
+ });
66
+ } else {
67
+ tmp[0] = value;
68
+ return tmp[0];
69
+ }
70
+ }
66
71
  removeRow(i) {
67
72
  this.values.copyWithin(i, i + 1, this.table.length);
68
73
  this.values[this.table.length - 1] = 0;
@@ -74,7 +79,7 @@ class TypedArrayColumn extends AColumn {
74
79
  toJSON() {
75
80
  let values = this.values.subarray(0, this.table.length);
76
81
  if (this.spec.flags & FLAG_RLE) {
77
- values = encodeRLE(values, values.length, SIZEOF[this.type] * 8);
82
+ values = encodeBinary(values, values.length, SIZEOF[this.type] * 8);
78
83
  }
79
84
  return { values: Array.from(values) };
80
85
  }
@@ -0,0 +1,25 @@
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 VectorColumn extends AColumn implements IColumn {
6
+ values: TypedArray;
7
+ type: NumericType;
8
+ size: number;
9
+ limit: [number, number];
10
+ protected tmp: TypedArray;
11
+ readonly isArray = false;
12
+ constructor(id: string, table: Table);
13
+ load(spec: SerializedColumn): void;
14
+ validate(value: any): boolean;
15
+ setRow(i: number, value: any): void;
16
+ getRow(i: number): Uint8Array<ArrayBufferLike> | Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike>;
17
+ getRowKey(i: number): string;
18
+ valueKey(value: any): string | string[];
19
+ removeRow(i: number): void;
20
+ replaceValue(): boolean;
21
+ toJSON(): {
22
+ values: any[];
23
+ };
24
+ }
25
+ //# sourceMappingURL=vector.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { SIZEOF, typedArray } from "@thi.ng/api/typedarray";
2
+ import { isArrayLike } from "@thi.ng/checks/is-arraylike";
3
+ import { isNumber } from "@thi.ng/checks/is-number";
4
+ import { unsupportedOp } from "@thi.ng/errors/unsupported";
5
+ import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
6
+ import {
7
+ FLAG_RLE,
8
+ LIMITS
9
+ } from "../api.js";
10
+ import { AColumn } from "./acolumn.js";
11
+ import { isArray } from "@thi.ng/checks/is-array";
12
+ class VectorColumn extends AColumn {
13
+ values;
14
+ type;
15
+ size;
16
+ limit;
17
+ tmp;
18
+ isArray = false;
19
+ constructor(id, table) {
20
+ super(id, table);
21
+ this.type = this.spec.type.split("v")[0];
22
+ this.size = this.spec.cardinality[1];
23
+ this.limit = LIMITS[this.type];
24
+ this.values = typedArray(this.type, 8 * this.size);
25
+ this.tmp = typedArray(this.type, this.size);
26
+ }
27
+ load(spec) {
28
+ if (this.spec.flags & FLAG_RLE) {
29
+ const values = decodeBinary(spec.values);
30
+ this.values = typedArray(this.type, values.buffer);
31
+ } else {
32
+ this.values = typedArray(this.type, spec.values);
33
+ }
34
+ this.reindex();
35
+ }
36
+ validate(value) {
37
+ return isArrayLike(value) && value.length == this.size || value == null && this.spec.default != null;
38
+ }
39
+ setRow(i, value) {
40
+ value = this.ensureValue(value);
41
+ const j = i * this.size;
42
+ let len = this.values.length;
43
+ if (j >= len) {
44
+ while (j >= len) len <<= 1;
45
+ const tmp = typedArray(this.type, len);
46
+ tmp.set(this.values);
47
+ this.values = tmp;
48
+ }
49
+ const { values, bitmap } = this;
50
+ if (bitmap) {
51
+ bitmap.clearBit(this.getRowKey(i), i);
52
+ bitmap.setBit(this.valueKey(value), i);
53
+ }
54
+ values.set(value, j);
55
+ }
56
+ getRow(i) {
57
+ const { size } = this;
58
+ i *= size;
59
+ return this.values.subarray(i, i + size);
60
+ }
61
+ getRowKey(i) {
62
+ return this.getRow(i).join("|");
63
+ }
64
+ valueKey(value) {
65
+ const { tmp } = this;
66
+ if (isArray(value) && !isNumber(value[0])) {
67
+ return value.map((x) => {
68
+ tmp.set(x);
69
+ return tmp.join("|");
70
+ });
71
+ } else {
72
+ tmp.set(value);
73
+ return tmp.join("|");
74
+ }
75
+ }
76
+ removeRow(i) {
77
+ const {
78
+ size,
79
+ table: { length }
80
+ } = this;
81
+ this.values.copyWithin(i, i + size, length * size);
82
+ this.values.fill(0, (length - 1) * size);
83
+ this.bitmap?.removeBit(i);
84
+ }
85
+ replaceValue() {
86
+ unsupportedOp("TODO");
87
+ }
88
+ toJSON() {
89
+ let $values = this.values.subarray(0, this.table.length * this.size);
90
+ if (this.spec.flags & FLAG_RLE) {
91
+ $values = encodeBinary(
92
+ $values,
93
+ $values.length,
94
+ SIZEOF[this.type] * 8
95
+ );
96
+ }
97
+ let values = Array.from($values);
98
+ const prec = this.spec.opts?.prec;
99
+ if (prec != null) values = values.map((x) => +x.toFixed(prec));
100
+ return { values };
101
+ }
102
+ }
103
+ export {
104
+ VectorColumn
105
+ };
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.2.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",
@@ -44,7 +44,7 @@
44
44
  "@thi.ng/bidir-index": "^1.5.0",
45
45
  "@thi.ng/checks": "^3.8.4",
46
46
  "@thi.ng/errors": "^2.6.3",
47
- "@thi.ng/rle-pack": "^3.1.118"
47
+ "@thi.ng/rle-pack": "^3.2.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "esbuild": "^0.27.2",
@@ -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,9 +106,15 @@
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
  },
115
+ "./columns/vector": {
116
+ "default": "./columns/vector.js"
117
+ },
115
118
  "./query": {
116
119
  "default": "./query.js"
117
120
  },
@@ -123,5 +126,5 @@
123
126
  "status": "alpha",
124
127
  "year": 2025
125
128
  },
126
- "gitHead": "28440e6d826cce964928f4c2ffe1f0e3e2e25ab0\n"
129
+ "gitHead": "b90a2f41eb0b3c89391bbb7cfff940192f23a83c\n"
127
130
  }
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;
@@ -98,42 +123,39 @@ class QueryCtx {
98
123
  }
99
124
  const execBitOr = (ctx, term, column) => {
100
125
  const bitmap = column.bitmap;
101
- const value = column.encode(term.value);
126
+ const key = column.valueKey(term.value);
102
127
  let mask;
103
- if (isArray(value)) {
104
- for (let v of value) {
105
- const b = bitmap.index.get(v)?.buffer;
128
+ if (isArray(key)) {
129
+ for (let k of key) {
130
+ const b = bitmap.index.get(k)?.buffer;
106
131
  if (!b) continue;
107
132
  if (mask) {
108
133
  for (let i = 0; i < b.length; i++) mask[i] |= b[i];
109
134
  } else mask = ctx.makeMask(b);
110
135
  }
111
136
  } else {
112
- const b = bitmap.index.get(value)?.buffer;
137
+ const b = bitmap.index.get(key)?.buffer;
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) => {
121
145
  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;
146
+ const key = column.valueKey(term.value);
147
+ const pred = column.isArray ? (row, k) => row.includes(k) : (row, k) => row === k;
125
148
  let mask;
126
- for (let v of isArray(encoded) ? encoded : [encoded]) {
149
+ for (let k of isArray(key) ? key : [key]) {
127
150
  for (let i = 0; i < n; i++) {
128
- if (pred(values[i], v)) {
151
+ if (pred(column.getRowKey(i), k)) {
129
152
  if (!mask) mask = ctx.makeMask();
130
153
  mask[i >>> 5] |= 1 << (i & 31);
131
154
  }
132
155
  }
133
156
  }
134
157
  if (mask) {
135
- if (term.type === "nor") ctx.invertMask(mask);
136
- ctx.mergeMask(mask);
158
+ term.type === "nor" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
137
159
  }
138
160
  };
139
161
  const delegateOr = (ctx, term, column) => {
@@ -145,12 +167,12 @@ const delegateOr = (ctx, term, column) => {
145
167
  };
146
168
  const execBitAnd = (ctx, term, column) => {
147
169
  const bitmap = column.bitmap;
148
- const value = column.encode(term.value);
170
+ const key = column.valueKey(term.value);
149
171
  let mask;
150
- if (isArray(value)) {
172
+ if (isArray(key)) {
151
173
  const colBitmaps = [];
152
- for (let v of value) {
153
- const b = bitmap.index.get(v)?.buffer;
174
+ for (let k of key) {
175
+ const b = bitmap.index.get(k)?.buffer;
154
176
  if (!b) {
155
177
  if (term.type === "and") ctx.bitmap = void 0;
156
178
  return;
@@ -164,26 +186,24 @@ const execBitAnd = (ctx, term, column) => {
164
186
  } else mask = ctx.makeMask(b);
165
187
  }
166
188
  } else {
167
- const b = bitmap.index.get(value)?.buffer;
189
+ const b = bitmap.index.get(key)?.buffer;
168
190
  if (b) mask = ctx.makeMask(b);
169
191
  }
170
192
  if (mask) {
171
- if (term.type === "nand") ctx.invertMask(mask);
172
- ctx.mergeMask(mask);
193
+ term.type === "nand" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
173
194
  } else {
174
195
  ctx.bitmap = void 0;
175
196
  }
176
197
  };
177
198
  const execAnd = (ctx, term, column) => {
178
199
  const n = ctx.table.length;
179
- const encoded = column.encode(term.value) ?? null;
180
- const values = column.values;
200
+ const key = column.valueKey(term.value) ?? null;
181
201
  const pred = column.isArray ? (row, v) => row.includes(v) : (row, v) => row === v;
182
202
  let mask;
183
- for (let v of isArray(encoded) ? encoded : [encoded]) {
203
+ for (let k of isArray(key) ? key : [key]) {
184
204
  let m;
185
205
  for (let i = 0; i < n; i++) {
186
- if (pred(values[i], v)) {
206
+ if (pred(column.getRowKey(i), k)) {
187
207
  if (!m) m = ctx.makeMask();
188
208
  m[i >>> 5] |= 1 << (i & 31);
189
209
  }
@@ -198,8 +218,7 @@ const execAnd = (ctx, term, column) => {
198
218
  }
199
219
  }
200
220
  if (mask) {
201
- if (term.type === "nand") ctx.invertMask(mask);
202
- ctx.mergeMask(mask);
221
+ term.type === "nand" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
203
222
  }
204
223
  };
205
224
  const delegateAnd = (ctx, term, column) => {
@@ -216,11 +235,10 @@ const QUERY_OPS = {
216
235
  nand: { fn: delegateAnd },
217
236
  matchCol: {
218
237
  fn: (ctx, term, column) => {
219
- const values = column.values;
220
238
  const pred = term.value;
221
239
  let mask;
222
240
  for (let i = 0, n = ctx.table.length; i < n; i++) {
223
- if (pred(column.decode(values[i]))) {
241
+ if (pred(column.getRow(i))) {
224
242
  if (!mask) mask = ctx.makeMask();
225
243
  mask[i >>> 5] |= 1 << (i & 31);
226
244
  }
package/table.js CHANGED
@@ -6,11 +6,12 @@ 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
+ import { VectorColumn } from "./columns/vector.js";
14
15
  import { __columnError } from "./internal/checks.js";
15
16
  import { Query } from "./query.js";
16
17
  class Table {
@@ -140,18 +141,25 @@ const $typed = {
140
141
  };
141
142
  const $float = { ...$typed, flags: FLAG_BITMAP };
142
143
  const $untyped = {
143
- impl: (table, id, { flags, cardinality: [min, max], default: d }) => {
144
+ impl: (table, id, { flags, cardinality: [_, max] }) => {
144
145
  const isDict = flags & FLAG_DICT;
145
- if (flags & FLAG_RLE) {
146
- if (!isDict || max > 1 || min === 0 && d == null) {
147
- __columnError(id, `RLE encoding not supported`);
148
- }
149
- }
150
- return max > 1 ? new (isDict ? DictArrayColumn : ArrayColumn)(id, table) : new (isDict ? DictColumn : PlainColumn)(id, table);
146
+ if (flags & FLAG_RLE && max > 1) __columnError(id, `RLE not supported`);
147
+ return max > 1 ? new (isDict ? DictTupleColumn : TupleColumn)(id, table) : new (isDict ? DictColumn : PlainColumn)(id, table);
151
148
  },
152
149
  flags: FLAG_BITMAP | FLAG_DICT | FLAG_UNIQUE | FLAG_RLE,
153
150
  cardinality: [0, -1 >>> 0]
154
151
  };
152
+ const $vec = {
153
+ impl: (table, id, { cardinality: [min, max] }) => {
154
+ if (min > 0 && min !== max)
155
+ __columnError(id, `only fixed size vectors supported`);
156
+ return new VectorColumn(id, table);
157
+ },
158
+ flags: FLAG_BITMAP | FLAG_RLE,
159
+ cardinality: [0, -1 >>> 0],
160
+ required: true
161
+ };
162
+ const $fvec = { ...$vec, flags: FLAG_BITMAP };
155
163
  const COLUMN_TYPES = {
156
164
  u8: $typed,
157
165
  i8: $typed,
@@ -162,7 +170,11 @@ const COLUMN_TYPES = {
162
170
  f32: $float,
163
171
  f64: $float,
164
172
  num: $untyped,
165
- str: $untyped
173
+ str: $untyped,
174
+ u8vec: $vec,
175
+ u16vec: $vec,
176
+ u32vec: $vec,
177
+ f32vec: $fvec
166
178
  };
167
179
  const registerColumnType = (type, spec) => {
168
180
  if (COLUMN_TYPES[type])