@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 +117 -9
- package/api.d.ts +45 -3
- package/api.js +12 -0
- package/columns/acolumn.d.ts +13 -3
- package/columns/acolumn.js +13 -8
- package/columns/{dict-array.d.ts → dict-tuple.d.ts} +4 -2
- package/columns/{dict-array.js → dict-tuple.js} +10 -4
- package/columns/dict.d.ts +2 -0
- package/columns/dict.js +25 -10
- package/columns/plain.d.ts +4 -3
- package/columns/plain.js +13 -6
- package/columns/{array.d.ts → tuple.d.ts} +4 -3
- package/columns/{array.js → tuple.js} +8 -5
- package/columns/typedarray.d.ts +3 -1
- package/columns/typedarray.js +23 -18
- package/columns/vector.d.ts +25 -0
- package/columns/vector.js +105 -0
- package/package.json +11 -8
- package/query.d.ts +20 -0
- package/query.js +49 -31
- package/table.js +22 -10
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
|
|
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
|
|
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
|
-
|
|
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
|
-
-
|
|
169
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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,
|
package/columns/acolumn.d.ts
CHANGED
|
@@ -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(
|
|
25
|
+
protected updateBitmap(): void;
|
|
16
26
|
protected ensureValue(val: any): any;
|
|
17
27
|
protected ensureBitmap(): void;
|
|
18
28
|
}
|
package/columns/acolumn.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { isArray } from "@thi.ng/checks";
|
|
2
1
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
3
|
-
import {
|
|
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(
|
|
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 <
|
|
40
|
-
const value =
|
|
41
|
-
if (
|
|
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(
|
|
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
|
|
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-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
}
|
package/columns/plain.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
|
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=
|
|
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
|
|
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
|
-
|
|
64
|
+
TupleColumn
|
|
62
65
|
};
|
package/columns/typedarray.d.ts
CHANGED
|
@@ -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(): {
|
package/columns/typedarray.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
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/
|
|
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,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": "
|
|
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
|
-
|
|
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
|
|
126
|
+
const key = column.valueKey(term.value);
|
|
102
127
|
let mask;
|
|
103
|
-
if (isArray(
|
|
104
|
-
for (let
|
|
105
|
-
const b = bitmap.index.get(
|
|
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(
|
|
137
|
+
const b = bitmap.index.get(key)?.buffer;
|
|
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) => {
|
|
121
145
|
const n = ctx.table.length;
|
|
122
|
-
const
|
|
123
|
-
const
|
|
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
|
|
149
|
+
for (let k of isArray(key) ? key : [key]) {
|
|
127
150
|
for (let i = 0; i < n; i++) {
|
|
128
|
-
if (pred(
|
|
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
|
-
|
|
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
|
|
170
|
+
const key = column.valueKey(term.value);
|
|
149
171
|
let mask;
|
|
150
|
-
if (isArray(
|
|
172
|
+
if (isArray(key)) {
|
|
151
173
|
const colBitmaps = [];
|
|
152
|
-
for (let
|
|
153
|
-
const b = bitmap.index.get(
|
|
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(
|
|
189
|
+
const b = bitmap.index.get(key)?.buffer;
|
|
168
190
|
if (b) mask = ctx.makeMask(b);
|
|
169
191
|
}
|
|
170
192
|
if (mask) {
|
|
171
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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: [
|
|
144
|
+
impl: (table, id, { flags, cardinality: [_, max] }) => {
|
|
144
145
|
const isDict = flags & FLAG_DICT;
|
|
145
|
-
if (flags & FLAG_RLE)
|
|
146
|
-
|
|
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])
|