@thi.ng/column-store 0.2.0 → 0.4.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 +22 -15
- package/api.d.ts +15 -0
- package/bitmap.d.ts +1 -0
- package/bitmap.js +18 -4
- package/columns/acolumn.d.ts +1 -0
- package/columns/dict-tuple.d.ts +1 -0
- package/columns/dict-tuple.js +10 -0
- package/columns/dict.d.ts +2 -1
- package/columns/dict.js +13 -2
- package/columns/plain.d.ts +1 -0
- package/columns/plain.js +11 -0
- package/columns/tuple.d.ts +1 -0
- package/columns/tuple.js +10 -0
- package/columns/typedarray.d.ts +3 -2
- package/columns/typedarray.js +21 -16
- package/columns/vector.d.ts +3 -2
- package/columns/vector.js +27 -23
- package/internal/indexof.d.ts +6 -0
- package/internal/indexof.js +34 -0
- package/internal/serialize.d.ts +8 -0
- package/internal/serialize.js +20 -1
- package/package.json +2 -2
- package/query.js +1 -1
- package/table.d.ts +3 -2
- package/table.js +16 -13
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ delegates them to the columns.
|
|
|
63
63
|
An example table definition looks like this (explanation of column types in next
|
|
64
64
|
section below):
|
|
65
65
|
|
|
66
|
-
```ts
|
|
66
|
+
```ts tangle:export/readme-types.ts
|
|
67
67
|
import { Table, FLAG_DICT, FLAG_UNIQUE } from "@thi.ng/column-store";
|
|
68
68
|
|
|
69
69
|
// define a table with the given columns
|
|
@@ -81,7 +81,7 @@ const table = new Table({
|
|
|
81
81
|
aliases: { type: "str", cardinality: [0, 3] },
|
|
82
82
|
|
|
83
83
|
// required fixed size tuples (aka vectors) of numbers
|
|
84
|
-
latlon: { type: "
|
|
84
|
+
latlon: { type: "f32vec", cardinality: [2, 2] },
|
|
85
85
|
|
|
86
86
|
// optional tuples of max. 10 strings, with default
|
|
87
87
|
// the given flags (explained further below) are triggering:
|
|
@@ -105,6 +105,11 @@ table.addRow({
|
|
|
105
105
|
});
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
> [!IMPORTANT]
|
|
109
|
+
> Columns can be named freely, with the exception that the `__` name prefix is
|
|
110
|
+
> reserved for internal use. For example, `foo` is allowed, but `__foo` is a
|
|
111
|
+
> reserved name.
|
|
112
|
+
|
|
108
113
|
### Column types
|
|
109
114
|
|
|
110
115
|
The current built-in column types only support numeric or string values, though
|
|
@@ -149,12 +154,13 @@ configs are: `[0,3]` (with default given) or `[3,3]`.
|
|
|
149
154
|
When [querying](#query-engine) vector columns using the standard
|
|
150
155
|
`(n)or`/`(n)and` operators, always the entire vector is matched (by value).
|
|
151
156
|
|
|
152
|
-
> [!IMPORTANT]
|
|
153
|
-
>
|
|
154
|
-
>
|
|
155
|
-
>
|
|
156
|
-
>
|
|
157
|
-
> (incl. any
|
|
157
|
+
> [!IMPORTANT]
|
|
158
|
+
> For performance reasons, rows retrieved from vector columns contain mutable
|
|
159
|
+
> data views of the underlying column storage. That means when manipulating data
|
|
160
|
+
> in these views, the underlying data in the column would be changed too. To
|
|
161
|
+
> avoid index corruption, always edit only copies of this vector data and then
|
|
162
|
+
> use `table.updateRow()` to properly update the column storage (incl. any
|
|
163
|
+
> internal indexes).
|
|
158
164
|
|
|
159
165
|
### Serialization options
|
|
160
166
|
|
|
@@ -310,8 +316,8 @@ TODO see code examples below
|
|
|
310
316
|
|
|
311
317
|
The query engine works by applying a number of [query
|
|
312
318
|
terms](https://docs.thi.ng/umbrella/column-store/interfaces/QueryTerm.html) in
|
|
313
|
-
series, with each step intersecting
|
|
314
|
-
step(s), thereby narrowing down the result set.
|
|
319
|
+
series, with each step intersecting (aka logical AND) its results with the
|
|
320
|
+
results of the previous step(s), thereby narrowing down the result set.
|
|
315
321
|
|
|
316
322
|
By default, individual query terms operate on a single column, but can also can
|
|
317
323
|
also apply to multiple. Terms are supplied either as array given to the
|
|
@@ -406,7 +412,7 @@ For Node.js REPL:
|
|
|
406
412
|
const cs = await import("@thi.ng/column-store");
|
|
407
413
|
```
|
|
408
414
|
|
|
409
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 4.
|
|
415
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 4.67 KB
|
|
410
416
|
|
|
411
417
|
## Dependencies
|
|
412
418
|
|
|
@@ -465,19 +471,20 @@ table.addRows([
|
|
|
465
471
|
const unsortedImages = table.query().where("type", "img").and("tags", "unsorted");
|
|
466
472
|
|
|
467
473
|
// queries are iterables and only execute when the iterator is consumed
|
|
474
|
+
// each query result includes a `__row` ID
|
|
468
475
|
console.log([...unsortedImages]);
|
|
469
|
-
// [ { id: 102, type: "img", tags: [ "unsorted" ] } ]
|
|
476
|
+
// [ { id: 102, type: "img", tags: [ "unsorted" ], __row: 2 } ]
|
|
470
477
|
|
|
471
478
|
// select items with `a` OR `b` tags, intersect with those which have `c` AND `d` tags
|
|
472
479
|
const complexTagQuery = table.query().or("tags", ["a", "b"]).and("tags", ["c", "d"]);
|
|
473
480
|
console.log([...complexTagQuery]);
|
|
474
|
-
// [ { id: 104, type: "img", tags: [ "b", "c", "d" ] } ]
|
|
481
|
+
// [ { id: 104, type: "img", tags: [ "b", "c", "d" ], __row: 4 } ]
|
|
475
482
|
|
|
476
483
|
// query using custom predicates
|
|
477
484
|
console.log([...table.query().matchColumn("id", (id) => id > 102)]);
|
|
478
485
|
// [
|
|
479
|
-
// { id: 103, type: "img", tags: [ "unsorted" ] },
|
|
480
|
-
// { id: 104, type: "img", tags: [ "b", "c", "d" ] }
|
|
486
|
+
// { id: 103, type: "img", tags: [ "unsorted" ], __row: 3 },
|
|
487
|
+
// { id: 104, type: "img", tags: [ "b", "c", "d" ], __row: 4 }
|
|
481
488
|
// ]
|
|
482
489
|
|
|
483
490
|
// serialize table to JSON
|
package/api.d.ts
CHANGED
|
@@ -89,6 +89,21 @@ export interface IColumn {
|
|
|
89
89
|
load(spec: SerializedColumn): void;
|
|
90
90
|
reindex(): void;
|
|
91
91
|
validate(value: any): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Searches for `value` in the column data, optionally constrained to given
|
|
94
|
+
* `start`/`end` range. If found, returns row ID of first occurrence,
|
|
95
|
+
* otherwise -1.
|
|
96
|
+
*
|
|
97
|
+
* @param value
|
|
98
|
+
* @param start
|
|
99
|
+
* @param end
|
|
100
|
+
*/
|
|
101
|
+
indexOf(value: any, start?: number, end?: number): number;
|
|
102
|
+
/**
|
|
103
|
+
* Returns value at given row.
|
|
104
|
+
*
|
|
105
|
+
* @param i
|
|
106
|
+
*/
|
|
92
107
|
getRow(i: number): any;
|
|
93
108
|
/**
|
|
94
109
|
* Sets the column's value at given row index. Assumes the value has been
|
package/bitmap.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare class Bitfield {
|
|
|
27
27
|
buffer?: Uint32Array | undefined;
|
|
28
28
|
constructor(buffer?: Uint32Array | undefined);
|
|
29
29
|
ones(max?: number): Generator<number, void, unknown>;
|
|
30
|
+
first(start?: number, end?: number): number;
|
|
30
31
|
setBit(id: number): void;
|
|
31
32
|
clearBit(id: number): void;
|
|
32
33
|
ensure(size: number): Uint32Array<ArrayBufferLike>;
|
package/bitmap.js
CHANGED
|
@@ -49,10 +49,10 @@ class Bitfield {
|
|
|
49
49
|
this.buffer = buffer;
|
|
50
50
|
}
|
|
51
51
|
*ones(max = Infinity) {
|
|
52
|
-
const
|
|
53
|
-
if (!
|
|
54
|
-
for (let i = 0, n =
|
|
55
|
-
let bits =
|
|
52
|
+
const { buffer } = this;
|
|
53
|
+
if (!buffer) return;
|
|
54
|
+
for (let i = 0, n = buffer.length; i < n; i++) {
|
|
55
|
+
let bits = buffer[i];
|
|
56
56
|
while (bits) {
|
|
57
57
|
const lsb = bits & -bits;
|
|
58
58
|
const x = (i << 5) + (Math.clz32(lsb) ^ 31);
|
|
@@ -62,6 +62,20 @@ class Bitfield {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
first(start = 0, end = (this.buffer?.length ?? -1) * 32) {
|
|
66
|
+
const { buffer } = this;
|
|
67
|
+
if (!buffer || start >= end) return -1;
|
|
68
|
+
for (let i = start >>> 5, n = Math.min(Math.ceil(end / 32), buffer.length); i < n; i++) {
|
|
69
|
+
let bits = buffer[i];
|
|
70
|
+
while (bits) {
|
|
71
|
+
const lsb = bits & -bits;
|
|
72
|
+
const x = (i << 5) + (Math.clz32(lsb) ^ 31);
|
|
73
|
+
if (x >= start && x < end) return x;
|
|
74
|
+
bits ^= lsb;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return -1;
|
|
78
|
+
}
|
|
65
79
|
setBit(id) {
|
|
66
80
|
const w = id >>> 5;
|
|
67
81
|
this.ensure(w)[w] |= 1 << (id & 31);
|
package/columns/acolumn.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare abstract class AColumn implements IColumn {
|
|
|
19
19
|
abstract valueKey(x: any): any;
|
|
20
20
|
abstract getRow(i: number): any;
|
|
21
21
|
abstract getRowKey(i: number): any;
|
|
22
|
+
abstract indexOf(value: any, start?: number, end?: number): number;
|
|
22
23
|
encode(value: any): any;
|
|
23
24
|
decode(value: any): any;
|
|
24
25
|
protected loadDict(serialized: SerializedIndex): void;
|
package/columns/dict-tuple.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class DictTupleColumn extends AColumn implements IColumn {
|
|
|
15
15
|
getRowKey(i: number): number[] | null;
|
|
16
16
|
valueKey(value: any): (number | null)[];
|
|
17
17
|
removeRow(i: number): void;
|
|
18
|
+
indexOf(value: any, start?: number, end?: number): number;
|
|
18
19
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
19
20
|
toJSON(): {
|
|
20
21
|
dict: {
|
package/columns/dict-tuple.js
CHANGED
|
@@ -2,6 +2,7 @@ import { BidirIndex } from "@thi.ng/bidir-index";
|
|
|
2
2
|
import { isArray } from "@thi.ng/checks/is-array";
|
|
3
3
|
import { FLAG_UNIQUE } from "../api.js";
|
|
4
4
|
import { __validateArrayValue } from "../internal/checks.js";
|
|
5
|
+
import { __indexOfTuple } from "../internal/indexof.js";
|
|
5
6
|
import { __serializeDict } from "../internal/serialize.js";
|
|
6
7
|
import { AColumn } from "./acolumn.js";
|
|
7
8
|
class DictTupleColumn extends AColumn {
|
|
@@ -55,6 +56,15 @@ class DictTupleColumn extends AColumn {
|
|
|
55
56
|
this.values.splice(i, 1);
|
|
56
57
|
this.bitmap?.removeBit(i);
|
|
57
58
|
}
|
|
59
|
+
indexOf(value, start = 0, end = this.table.length) {
|
|
60
|
+
return __indexOfTuple(
|
|
61
|
+
value != null ? this.encode(value) : null,
|
|
62
|
+
this.values,
|
|
63
|
+
this.table.length,
|
|
64
|
+
start,
|
|
65
|
+
end
|
|
66
|
+
);
|
|
67
|
+
}
|
|
58
68
|
replaceValue(currValue, newValue) {
|
|
59
69
|
const { dict, values, bitmap } = this;
|
|
60
70
|
const res = dict.renameKey(currValue, newValue);
|
package/columns/dict.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare class DictColumn extends AColumn implements IColumn {
|
|
|
7
7
|
readonly isArray = false;
|
|
8
8
|
load({ dict, values }: SerializedColumn): void;
|
|
9
9
|
reindex(): void;
|
|
10
|
-
encode(value: any): number |
|
|
10
|
+
encode(value: any): number | null;
|
|
11
11
|
decode(value: any): any;
|
|
12
12
|
validate(value: any): boolean;
|
|
13
13
|
setRow(i: number, value: any): void;
|
|
@@ -15,6 +15,7 @@ export declare class DictColumn extends AColumn implements IColumn {
|
|
|
15
15
|
getRowKey(i: number): number | null;
|
|
16
16
|
valueKey(value: any): number | number[] | undefined;
|
|
17
17
|
removeRow(i: number): void;
|
|
18
|
+
indexOf(value: any, start?: number, end?: number): number;
|
|
18
19
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
19
20
|
toJSON(): {
|
|
20
21
|
dict: {
|
package/columns/dict.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { BidirIndex } from "@thi.ng/bidir-index";
|
|
2
|
+
import { isArray } from "@thi.ng/checks/is-array";
|
|
2
3
|
import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
|
|
3
4
|
import { decodeSimple, encodeSimple } from "@thi.ng/rle-pack/simple";
|
|
4
5
|
import { FLAG_RLE } from "../api.js";
|
|
5
6
|
import { __validateValue } from "../internal/checks.js";
|
|
7
|
+
import { __indexOfSingle } from "../internal/indexof.js";
|
|
6
8
|
import { __serializeDict } from "../internal/serialize.js";
|
|
7
9
|
import { AColumn } from "./acolumn.js";
|
|
8
|
-
import { isArray } from "@thi.ng/checks/is-array";
|
|
9
10
|
class DictColumn extends AColumn {
|
|
10
11
|
values = [];
|
|
11
12
|
dict = new BidirIndex();
|
|
@@ -25,7 +26,7 @@ class DictColumn extends AColumn {
|
|
|
25
26
|
super.updateBitmap();
|
|
26
27
|
}
|
|
27
28
|
encode(value) {
|
|
28
|
-
return this.dict.get(value);
|
|
29
|
+
return this.dict.get(value) ?? null;
|
|
29
30
|
}
|
|
30
31
|
decode(value) {
|
|
31
32
|
return this.dict.getID(value);
|
|
@@ -57,6 +58,16 @@ class DictColumn extends AColumn {
|
|
|
57
58
|
this.values.splice(i, 1);
|
|
58
59
|
this.bitmap?.removeBit(i);
|
|
59
60
|
}
|
|
61
|
+
indexOf(value, start = 0, end = this.table.length) {
|
|
62
|
+
return __indexOfSingle(
|
|
63
|
+
this.encode(value),
|
|
64
|
+
this.values,
|
|
65
|
+
this.bitmap,
|
|
66
|
+
this.table.length,
|
|
67
|
+
start,
|
|
68
|
+
end
|
|
69
|
+
);
|
|
70
|
+
}
|
|
60
71
|
replaceValue(currValue, newValue) {
|
|
61
72
|
const { dict, values, bitmap } = this;
|
|
62
73
|
const res = dict.renameKey(currValue, newValue);
|
package/columns/plain.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class PlainColumn extends AColumn implements IColumn {
|
|
|
10
10
|
getRowKey(i: number): any;
|
|
11
11
|
valueKey(x: any): any;
|
|
12
12
|
removeRow(i: number): void;
|
|
13
|
+
indexOf(value: any, start?: number, end?: number): number;
|
|
13
14
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
14
15
|
toJSON(): {
|
|
15
16
|
values: any[];
|
package/columns/plain.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { decodeSimple, encodeSimple } from "@thi.ng/rle-pack/simple";
|
|
2
2
|
import { FLAG_RLE } from "../api.js";
|
|
3
3
|
import { __validateValue } from "../internal/checks.js";
|
|
4
|
+
import { __indexOfSingle } from "../internal/indexof.js";
|
|
4
5
|
import { __replaceValue } from "../internal/replace.js";
|
|
5
6
|
import { AColumn } from "./acolumn.js";
|
|
6
7
|
class PlainColumn extends AColumn {
|
|
@@ -35,6 +36,16 @@ class PlainColumn extends AColumn {
|
|
|
35
36
|
this.values.splice(i, 1);
|
|
36
37
|
this.bitmap?.removeBit(i);
|
|
37
38
|
}
|
|
39
|
+
indexOf(value, start = 0, end) {
|
|
40
|
+
return __indexOfSingle(
|
|
41
|
+
value,
|
|
42
|
+
this.values,
|
|
43
|
+
this.bitmap,
|
|
44
|
+
this.table.length,
|
|
45
|
+
start,
|
|
46
|
+
end
|
|
47
|
+
);
|
|
48
|
+
}
|
|
38
49
|
replaceValue(currValue, newValue) {
|
|
39
50
|
return __replaceValue(this.bitmap, this.values, currValue, newValue);
|
|
40
51
|
}
|
package/columns/tuple.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class TupleColumn extends AColumn implements IColumn {
|
|
|
12
12
|
getRowKey(i: number): Nullable<number[]>;
|
|
13
13
|
valueKey(value: any): any[];
|
|
14
14
|
removeRow(i: number): void;
|
|
15
|
+
indexOf(value: any, start?: number, end?: number): number;
|
|
15
16
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
16
17
|
toJSON(): {
|
|
17
18
|
values: Nullable<number[]>[];
|
package/columns/tuple.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { __indexOfTuple } from "../internal/indexof.js";
|
|
4
5
|
import { AColumn } from "./acolumn.js";
|
|
5
6
|
class TupleColumn extends AColumn {
|
|
6
7
|
values = [];
|
|
@@ -38,6 +39,15 @@ class TupleColumn extends AColumn {
|
|
|
38
39
|
this.values.splice(i, 1);
|
|
39
40
|
this.bitmap?.removeBit(i);
|
|
40
41
|
}
|
|
42
|
+
indexOf(value, start = 0, end = this.table.length) {
|
|
43
|
+
return __indexOfTuple(
|
|
44
|
+
value,
|
|
45
|
+
this.values,
|
|
46
|
+
this.table.length,
|
|
47
|
+
start,
|
|
48
|
+
end
|
|
49
|
+
);
|
|
50
|
+
}
|
|
41
51
|
replaceValue(currValue, newValue) {
|
|
42
52
|
const { values, bitmap } = this;
|
|
43
53
|
const isUnique = this.spec.flags & FLAG_UNIQUE;
|
package/columns/typedarray.d.ts
CHANGED
|
@@ -9,16 +9,17 @@ export declare class TypedArrayColumn extends AColumn implements IColumn {
|
|
|
9
9
|
protected tmp: TypedArray;
|
|
10
10
|
readonly isArray = false;
|
|
11
11
|
constructor(id: string, table: Table);
|
|
12
|
-
load(
|
|
12
|
+
load({ values }: SerializedColumn): void;
|
|
13
13
|
validate(value: any): boolean;
|
|
14
14
|
setRow(i: number, value: any): void;
|
|
15
15
|
getRow(i: number): number;
|
|
16
16
|
getRowKey(i: number): number;
|
|
17
17
|
valueKey(value: any): number | number[];
|
|
18
18
|
removeRow(i: number): void;
|
|
19
|
+
indexOf(value: any, start?: number, end?: number): number;
|
|
19
20
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
20
21
|
toJSON(): {
|
|
21
|
-
values:
|
|
22
|
+
values: any[];
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
//# sourceMappingURL=typedarray.d.ts.map
|
package/columns/typedarray.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { typedArray } from "@thi.ng/api/typedarray";
|
|
2
|
+
import { isArray } from "@thi.ng/checks/is-array";
|
|
2
3
|
import { isNumber } from "@thi.ng/checks/is-number";
|
|
3
|
-
import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
|
|
4
4
|
import {
|
|
5
|
-
FLAG_RLE,
|
|
6
5
|
LIMITS
|
|
7
6
|
} from "../api.js";
|
|
7
|
+
import { __indexOfSingle } from "../internal/indexof.js";
|
|
8
8
|
import { __replaceValue } from "../internal/replace.js";
|
|
9
|
+
import { __deserializeTyped, __serializeTyped } from "../internal/serialize.js";
|
|
9
10
|
import { AColumn } from "./acolumn.js";
|
|
10
|
-
import { isArray } from "@thi.ng/checks/is-array";
|
|
11
11
|
class TypedArrayColumn extends AColumn {
|
|
12
12
|
values;
|
|
13
13
|
type;
|
|
@@ -21,13 +21,8 @@ class TypedArrayColumn extends AColumn {
|
|
|
21
21
|
this.values = typedArray(this.type, 8);
|
|
22
22
|
this.tmp = typedArray(this.type, 1);
|
|
23
23
|
}
|
|
24
|
-
load(
|
|
25
|
-
|
|
26
|
-
const values = decodeBinary(spec.values);
|
|
27
|
-
this.values = typedArray(this.type, values.buffer);
|
|
28
|
-
} else {
|
|
29
|
-
this.values = typedArray(this.type, spec.values);
|
|
30
|
-
}
|
|
24
|
+
load({ values }) {
|
|
25
|
+
this.values = __deserializeTyped(this.type, this.spec.flags, values);
|
|
31
26
|
this.reindex();
|
|
32
27
|
}
|
|
33
28
|
validate(value) {
|
|
@@ -73,15 +68,25 @@ class TypedArrayColumn extends AColumn {
|
|
|
73
68
|
this.values[this.table.length - 1] = 0;
|
|
74
69
|
this.bitmap?.removeBit(i);
|
|
75
70
|
}
|
|
71
|
+
indexOf(value, start = 0, end = this.table.length) {
|
|
72
|
+
return __indexOfSingle(
|
|
73
|
+
value,
|
|
74
|
+
this.values,
|
|
75
|
+
this.bitmap,
|
|
76
|
+
this.table.length,
|
|
77
|
+
start,
|
|
78
|
+
end
|
|
79
|
+
);
|
|
80
|
+
}
|
|
76
81
|
replaceValue(currValue, newValue) {
|
|
77
82
|
return __replaceValue(this.bitmap, this.values, currValue, newValue);
|
|
78
83
|
}
|
|
79
84
|
toJSON() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
return __serializeTyped(
|
|
86
|
+
this.values.subarray(0, this.table.length),
|
|
87
|
+
this.spec,
|
|
88
|
+
this.type
|
|
89
|
+
);
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
92
|
export {
|
package/columns/vector.d.ts
CHANGED
|
@@ -10,13 +10,14 @@ export declare class VectorColumn extends AColumn implements IColumn {
|
|
|
10
10
|
protected tmp: TypedArray;
|
|
11
11
|
readonly isArray = false;
|
|
12
12
|
constructor(id: string, table: Table);
|
|
13
|
-
load(
|
|
13
|
+
load({ values }: SerializedColumn): void;
|
|
14
14
|
validate(value: any): boolean;
|
|
15
15
|
setRow(i: number, value: any): void;
|
|
16
|
-
getRow(i: number):
|
|
16
|
+
getRow(i: number): Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike>;
|
|
17
17
|
getRowKey(i: number): string;
|
|
18
18
|
valueKey(value: any): string | string[];
|
|
19
19
|
removeRow(i: number): void;
|
|
20
|
+
indexOf(value: any, start?: number, end?: number): number;
|
|
20
21
|
replaceValue(): boolean;
|
|
21
22
|
toJSON(): {
|
|
22
23
|
values: any[];
|
package/columns/vector.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { typedArray } from "@thi.ng/api/typedarray";
|
|
2
|
+
import { isArray } from "@thi.ng/checks/is-array";
|
|
2
3
|
import { isArrayLike } from "@thi.ng/checks/is-arraylike";
|
|
3
4
|
import { isNumber } from "@thi.ng/checks/is-number";
|
|
4
5
|
import { unsupportedOp } from "@thi.ng/errors/unsupported";
|
|
5
|
-
import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
|
|
6
6
|
import {
|
|
7
|
-
FLAG_RLE,
|
|
8
7
|
LIMITS
|
|
9
8
|
} from "../api.js";
|
|
9
|
+
import { __deserializeTyped, __serializeTyped } from "../internal/serialize.js";
|
|
10
10
|
import { AColumn } from "./acolumn.js";
|
|
11
|
-
import { isArray } from "@thi.ng/checks/is-array";
|
|
12
11
|
class VectorColumn extends AColumn {
|
|
13
12
|
values;
|
|
14
13
|
type;
|
|
@@ -24,13 +23,8 @@ class VectorColumn extends AColumn {
|
|
|
24
23
|
this.values = typedArray(this.type, 8 * this.size);
|
|
25
24
|
this.tmp = typedArray(this.type, this.size);
|
|
26
25
|
}
|
|
27
|
-
load(
|
|
28
|
-
|
|
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
|
-
}
|
|
26
|
+
load({ values }) {
|
|
27
|
+
this.values = __deserializeTyped(this.type, this.spec.flags, values);
|
|
34
28
|
this.reindex();
|
|
35
29
|
}
|
|
36
30
|
validate(value) {
|
|
@@ -82,22 +76,32 @@ class VectorColumn extends AColumn {
|
|
|
82
76
|
this.values.fill(0, (length - 1) * size);
|
|
83
77
|
this.bitmap?.removeBit(i);
|
|
84
78
|
}
|
|
79
|
+
indexOf(value, start = 0, end = this.table.length) {
|
|
80
|
+
const { values, bitmap, size } = this;
|
|
81
|
+
start = Math.max(start, 0);
|
|
82
|
+
end = Math.min(end, this.table.length);
|
|
83
|
+
if (bitmap) {
|
|
84
|
+
return bitmap.index.get(this.valueKey(value))?.first(start, end) ?? -1;
|
|
85
|
+
}
|
|
86
|
+
end *= size;
|
|
87
|
+
let i, j;
|
|
88
|
+
outer: for (i = start * size; i < end; i += size) {
|
|
89
|
+
for (j = 0; j < size; j++) {
|
|
90
|
+
if (values[i + j] !== value[j]) continue outer;
|
|
91
|
+
}
|
|
92
|
+
return i / size;
|
|
93
|
+
}
|
|
94
|
+
return -1;
|
|
95
|
+
}
|
|
85
96
|
replaceValue() {
|
|
86
97
|
unsupportedOp("TODO");
|
|
87
98
|
}
|
|
88
99
|
toJSON() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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 };
|
|
100
|
+
return __serializeTyped(
|
|
101
|
+
this.values.subarray(0, this.table.length * this.size),
|
|
102
|
+
this.spec,
|
|
103
|
+
this.type
|
|
104
|
+
);
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
export {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Maybe } from "@thi.ng/api";
|
|
2
|
+
import type { BitmapIndex } from "../bitmap.js";
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare const __indexOfSingle: (needle: any, values: ArrayLike<any>, bitmap: Maybe<BitmapIndex>, max: number, start: number, end?: number) => number;
|
|
5
|
+
export declare const __indexOfTuple: (needle: ArrayLike<any> | null, values: ArrayLike<any>, max: number, start: number, end?: number) => number;
|
|
6
|
+
//# sourceMappingURL=indexof.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const __indexOfSingle = (needle, values, bitmap, max, start, end = max) => {
|
|
2
|
+
start = Math.max(start, 0);
|
|
3
|
+
end = Math.min(end, max);
|
|
4
|
+
if (bitmap) return bitmap.index.get(needle)?.first(start, end) ?? -1;
|
|
5
|
+
for (let i = start; i < end; i++) {
|
|
6
|
+
if (values[i] === needle) return i;
|
|
7
|
+
}
|
|
8
|
+
return -1;
|
|
9
|
+
};
|
|
10
|
+
const __indexOfTuple = (needle, values, max, start, end = max) => {
|
|
11
|
+
start = Math.max(start, 0);
|
|
12
|
+
end = Math.min(end, max);
|
|
13
|
+
if (needle == null) {
|
|
14
|
+
for (let i2 = start; i2 < end; i2++) {
|
|
15
|
+
if (!values[i2]) return i2;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const n = needle.length;
|
|
19
|
+
let i, j, row;
|
|
20
|
+
outer: for (i = start; i < end; i++) {
|
|
21
|
+
row = values[i];
|
|
22
|
+
if (row?.length === n) {
|
|
23
|
+
for (j = 0; j < n; j++) {
|
|
24
|
+
if (row[j] !== needle[j]) continue outer;
|
|
25
|
+
}
|
|
26
|
+
return i;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return -1;
|
|
30
|
+
};
|
|
31
|
+
export {
|
|
32
|
+
__indexOfSingle,
|
|
33
|
+
__indexOfTuple
|
|
34
|
+
};
|
package/internal/serialize.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { type NumericArray, type Type } from "@thi.ng/api/typedarray";
|
|
1
2
|
import type { BidirIndex } from "@thi.ng/bidir-index";
|
|
3
|
+
import { type ColumnSpec } from "../api.js";
|
|
2
4
|
/** @internal */
|
|
3
5
|
export declare const __serializeDict: (dict: BidirIndex<any>) => {
|
|
4
6
|
index: any;
|
|
5
7
|
next: number;
|
|
6
8
|
};
|
|
9
|
+
/** @internal */
|
|
10
|
+
export declare const __serializeTyped: ($values: NumericArray, spec: ColumnSpec, type: Type) => {
|
|
11
|
+
values: any[];
|
|
12
|
+
};
|
|
13
|
+
/** @internal */
|
|
14
|
+
export declare const __deserializeTyped: (type: Type, flags: number, values: number[]) => Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike>;
|
|
7
15
|
//# sourceMappingURL=serialize.d.ts.map
|
package/internal/serialize.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SIZEOF,
|
|
3
|
+
typedArray
|
|
4
|
+
} from "@thi.ng/api/typedarray";
|
|
5
|
+
import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
|
|
6
|
+
import { decodeSimple, encodeSimple } from "@thi.ng/rle-pack/simple";
|
|
7
|
+
import { FLAG_RLE } from "../api.js";
|
|
1
8
|
const __serializeDict = (dict) => {
|
|
2
9
|
const res = [];
|
|
3
10
|
for (let [val, id] of dict.entries()) res[id] = val;
|
|
4
11
|
return { index: res, next: dict.nextID };
|
|
5
12
|
};
|
|
13
|
+
const __serializeTyped = ($values, spec, type) => {
|
|
14
|
+
if (spec.flags & FLAG_RLE) {
|
|
15
|
+
$values = type[0] === "f" ? encodeSimple($values) : encodeBinary($values, $values.length, SIZEOF[type] * 8);
|
|
16
|
+
}
|
|
17
|
+
let values = Array.from($values);
|
|
18
|
+
const prec = spec.opts?.prec;
|
|
19
|
+
if (prec != null) values = values.map((x) => +x.toFixed(prec));
|
|
20
|
+
return { values };
|
|
21
|
+
};
|
|
22
|
+
const __deserializeTyped = (type, flags, values) => flags & FLAG_RLE ? type[0] === "f" ? typedArray(type, decodeSimple(values)) : typedArray(type, decodeBinary(values).buffer) : typedArray(type, values);
|
|
6
23
|
export {
|
|
7
|
-
|
|
24
|
+
__deserializeTyped,
|
|
25
|
+
__serializeDict,
|
|
26
|
+
__serializeTyped
|
|
8
27
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/column-store",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"status": "alpha",
|
|
127
127
|
"year": 2025
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "5c620a1aa00535cc7c674794790edd87b2e5e526\n"
|
|
130
130
|
}
|
package/query.js
CHANGED
package/table.d.ts
CHANGED
|
@@ -25,8 +25,9 @@ export declare class Table {
|
|
|
25
25
|
addRows(rows: Iterable<Row>): void;
|
|
26
26
|
updateRow(i: number, row: Row): void;
|
|
27
27
|
removeRow(i: number): void;
|
|
28
|
-
getRow(i: number, safe?: boolean): Row | undefined;
|
|
29
|
-
getPartialRow(i: number, columns: string[], safe?: boolean): Row | undefined;
|
|
28
|
+
getRow(i: number, safe?: boolean, includeID?: boolean): Row | undefined;
|
|
29
|
+
getPartialRow(i: number, columns: string[], safe?: boolean, includeID?: boolean): Row | undefined;
|
|
30
|
+
indexOf(id: string, value: any, start?: number, end?: number): number;
|
|
30
31
|
validateRow(row: Row): void;
|
|
31
32
|
validateColumnSpec(id: string, spec: ColumnSpec): void;
|
|
32
33
|
toJSON(): {
|
package/table.js
CHANGED
|
@@ -82,22 +82,25 @@ class Table {
|
|
|
82
82
|
}
|
|
83
83
|
this.length--;
|
|
84
84
|
}
|
|
85
|
-
getRow(i, safe = true) {
|
|
85
|
+
getRow(i, safe = true, includeID = false) {
|
|
86
86
|
if (safe && (i < 0 || i >= this.length)) return;
|
|
87
|
-
const row = {};
|
|
87
|
+
const row = includeID ? { __row: i } : {};
|
|
88
88
|
for (let id in this.columns) {
|
|
89
89
|
row[id] = this.columns[id].getRow(i);
|
|
90
90
|
}
|
|
91
91
|
return row;
|
|
92
92
|
}
|
|
93
|
-
getPartialRow(i, columns, safe = true) {
|
|
93
|
+
getPartialRow(i, columns, safe = true, includeID = false) {
|
|
94
94
|
if (safe && (i < 0 || i >= this.length)) return;
|
|
95
|
-
const row = {};
|
|
95
|
+
const row = includeID ? { __row: i } : {};
|
|
96
96
|
for (let id of columns) {
|
|
97
97
|
row[id] = this.columns[id]?.getRow(i);
|
|
98
98
|
}
|
|
99
99
|
return row;
|
|
100
100
|
}
|
|
101
|
+
indexOf(id, value, start, end) {
|
|
102
|
+
return this.columns[id]?.indexOf(value, start, end) ?? -1;
|
|
103
|
+
}
|
|
101
104
|
validateRow(row) {
|
|
102
105
|
const { columns } = this;
|
|
103
106
|
for (let id in columns) {
|
|
@@ -114,15 +117,13 @@ class Table {
|
|
|
114
117
|
`unsupported flags for column type: ${spec.type}`
|
|
115
118
|
);
|
|
116
119
|
const [min, max] = spec.cardinality;
|
|
117
|
-
if (max < min)
|
|
120
|
+
if (max < min || min < def.cardinality[0] || max > def.cardinality[1] || max < 1)
|
|
118
121
|
__columnError(id, `wrong cardinality: ${spec.cardinality}`);
|
|
119
|
-
if (min < def.cardinality[0] || max > def.cardinality[1])
|
|
120
|
-
__columnError(id, `wrong cardimality`);
|
|
121
122
|
if (def.required && min === 0 && spec.default == null)
|
|
122
123
|
__columnError(id, `missing default value`);
|
|
123
124
|
if (spec.default != null) {
|
|
124
125
|
if (max > 1 !== isArray(spec.default))
|
|
125
|
-
__columnError(id, `default value`);
|
|
126
|
+
__columnError(id, `wrong default value`);
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
toJSON() {
|
|
@@ -139,7 +140,6 @@ const $typed = {
|
|
|
139
140
|
cardinality: [0, 1],
|
|
140
141
|
required: true
|
|
141
142
|
};
|
|
142
|
-
const $float = { ...$typed, flags: FLAG_BITMAP };
|
|
143
143
|
const $untyped = {
|
|
144
144
|
impl: (table, id, { flags, cardinality: [_, max] }) => {
|
|
145
145
|
const isDict = flags & FLAG_DICT;
|
|
@@ -159,7 +159,6 @@ const $vec = {
|
|
|
159
159
|
cardinality: [0, -1 >>> 0],
|
|
160
160
|
required: true
|
|
161
161
|
};
|
|
162
|
-
const $fvec = { ...$vec, flags: FLAG_BITMAP };
|
|
163
162
|
const COLUMN_TYPES = {
|
|
164
163
|
u8: $typed,
|
|
165
164
|
i8: $typed,
|
|
@@ -167,14 +166,18 @@ const COLUMN_TYPES = {
|
|
|
167
166
|
i16: $typed,
|
|
168
167
|
u32: $typed,
|
|
169
168
|
i32: $typed,
|
|
170
|
-
f32: $
|
|
171
|
-
f64: $
|
|
169
|
+
f32: $typed,
|
|
170
|
+
f64: $typed,
|
|
172
171
|
num: $untyped,
|
|
173
172
|
str: $untyped,
|
|
174
173
|
u8vec: $vec,
|
|
175
174
|
u16vec: $vec,
|
|
176
175
|
u32vec: $vec,
|
|
177
|
-
|
|
176
|
+
i8vec: $vec,
|
|
177
|
+
i16vec: $vec,
|
|
178
|
+
i32vec: $vec,
|
|
179
|
+
f32vec: $vec,
|
|
180
|
+
f64vec: $vec
|
|
178
181
|
};
|
|
179
182
|
const registerColumnType = (type, spec) => {
|
|
180
183
|
if (COLUMN_TYPES[type])
|