@thi.ng/column-store 0.4.0 → 0.5.1
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 +49 -7
- package/api.d.ts +15 -9
- package/bitmap.d.ts +1 -0
- package/bitmap.js +23 -2
- package/columns/acolumn.d.ts +8 -5
- package/columns/acolumn.js +21 -2
- package/columns/dict-tuple.d.ts +2 -2
- package/columns/dict.d.ts +2 -2
- package/columns/plain.d.ts +2 -2
- package/columns/tuple.d.ts +2 -2
- package/columns/typedarray.d.ts +3 -3
- package/columns/vector.d.ts +4 -4
- package/columns/vector.js +1 -0
- package/internal/indexof.d.ts +3 -0
- package/internal/indexof.js +7 -4
- package/internal/serialize.d.ts +1 -1
- package/package.json +3 -3
- package/query.d.ts +23 -20
- package/query.js +60 -8
- package/table.d.ts +25 -20
- package/table.js +5 -1
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
- [AND](#and)
|
|
35
35
|
- [Negation](#negation)
|
|
36
36
|
- [Predicate-based matchers](#predicate-based-matchers)
|
|
37
|
+
- [Row ranges](#row-ranges)
|
|
38
|
+
- [Value ranges](#value-ranges)
|
|
37
39
|
- [Custom operators](#custom-operators)
|
|
38
40
|
- [Result aggregation](#result-aggregation)
|
|
39
41
|
- [Query ranges](#query-ranges)
|
|
@@ -55,10 +57,11 @@ As the name indicates, data is stored in different columns, where each column
|
|
|
55
57
|
manages its own schema, value validation, backing storage, indexing,
|
|
56
58
|
serialization etc.
|
|
57
59
|
|
|
58
|
-
From a user's POV, columns are not (usually) used directly, but via
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
From a user's POV, columns are not (usually) used directly, but accessed via a
|
|
61
|
+
table, which provides row-based access to compound data records and acts as
|
|
62
|
+
facade to the various configured columns. Data records are added as JS objects
|
|
63
|
+
to a table, which then pulls out related values, validates them and the
|
|
64
|
+
delegates them to the individual columns for encoding & storage.
|
|
62
65
|
|
|
63
66
|
An example table definition looks like this (explanation of column types in next
|
|
64
67
|
section below):
|
|
@@ -66,8 +69,17 @@ section below):
|
|
|
66
69
|
```ts tangle:export/readme-types.ts
|
|
67
70
|
import { Table, FLAG_DICT, FLAG_UNIQUE } from "@thi.ng/column-store";
|
|
68
71
|
|
|
72
|
+
// schema for a single row item
|
|
73
|
+
interface Item {
|
|
74
|
+
id: number;
|
|
75
|
+
name: string;
|
|
76
|
+
aliases?: string[];
|
|
77
|
+
latlon: number[] | Float32Array;
|
|
78
|
+
tags: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
69
81
|
// define a table with the given columns
|
|
70
|
-
const table = new Table({
|
|
82
|
+
const table = new Table<Item>({
|
|
71
83
|
|
|
72
84
|
// column of single numeric values
|
|
73
85
|
// (default cardinality makes values required)
|
|
@@ -367,6 +379,30 @@ can be used, otherwise the behavior is:
|
|
|
367
379
|
- [`matchPartialRow`](https://docs.thi.ng/umbrella/column-store/classes/Query.html#matchpartialrow):
|
|
368
380
|
apply predicate to partial row (only selected columns)
|
|
369
381
|
|
|
382
|
+
#### Row ranges
|
|
383
|
+
|
|
384
|
+
The
|
|
385
|
+
[`rowRange`](https://docs.thi.ng/umbrella/column-store/classes/Query.html#rowrange)
|
|
386
|
+
operator selects the given `start` (inclusive) .. `end` (exclusive) range of row
|
|
387
|
+
indices.
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
// select first ten rows
|
|
391
|
+
query.rowRange(0, 10);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### Value ranges
|
|
395
|
+
|
|
396
|
+
The
|
|
397
|
+
[`valueRange`](https://docs.thi.ng/umbrella/column-store/classes/Query.html#valuerange)
|
|
398
|
+
operator selects rows based on a given column's `start` .. `end` vaulue range
|
|
399
|
+
(both inclusive) of row indices.
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
// select rows where ID is in closed [100,109] interval
|
|
403
|
+
query.valueRange("id", 100, 109);
|
|
404
|
+
```
|
|
405
|
+
|
|
370
406
|
### Custom operators
|
|
371
407
|
|
|
372
408
|
Custom query operators can be registered via
|
|
@@ -412,7 +448,7 @@ For Node.js REPL:
|
|
|
412
448
|
const cs = await import("@thi.ng/column-store");
|
|
413
449
|
```
|
|
414
450
|
|
|
415
|
-
Package sizes (brotli'd, pre-treeshake): ESM:
|
|
451
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 5.37 KB
|
|
416
452
|
|
|
417
453
|
## Dependencies
|
|
418
454
|
|
|
@@ -438,7 +474,13 @@ import {
|
|
|
438
474
|
FLAG_UNIQUE,
|
|
439
475
|
} from "@thi.ng/column-store";
|
|
440
476
|
|
|
441
|
-
|
|
477
|
+
interface Item {
|
|
478
|
+
id: number;
|
|
479
|
+
type: string;
|
|
480
|
+
tags: string[];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const table = new Table<Item>({
|
|
442
484
|
// ID column stores 8bit ints (typed array)
|
|
443
485
|
id: { type: "u8" },
|
|
444
486
|
// Type column stores indexed strings
|
package/api.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { FloatType, Fn3, IntType, Maybe, UintType } from "@thi.ng/api";
|
|
1
|
+
import type { FloatType, Fn3, IntType, Maybe, Predicate, 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
|
-
export type
|
|
5
|
+
export type ColumnID<T extends Row> = Exclude<keyof T, number | symbol>;
|
|
6
|
+
export type ColumnSchema<T extends Row> = Record<ColumnID<T>, ColumnSpec>;
|
|
6
7
|
export type NumericType = IntType | UintType | FloatType;
|
|
7
8
|
export type VectorType = `${NumericType}vec`;
|
|
8
9
|
export type Cardinality = [number, number];
|
|
@@ -55,7 +56,7 @@ export interface ColumnTypeSpec {
|
|
|
55
56
|
* Factory function to instantiate a colum type for given table, column name
|
|
56
57
|
* & spec.
|
|
57
58
|
*/
|
|
58
|
-
impl: Fn3<Table
|
|
59
|
+
impl: Fn3<Table<any>, string, ColumnSpec, IColumn>;
|
|
59
60
|
/**
|
|
60
61
|
* Bit mask of supported flags (default: 0, i.e. none allowed). During
|
|
61
62
|
* column validation, the (inverted) mask will be applied to the user
|
|
@@ -99,6 +100,8 @@ export interface IColumn {
|
|
|
99
100
|
* @param end
|
|
100
101
|
*/
|
|
101
102
|
indexOf(value: any, start?: number, end?: number): number;
|
|
103
|
+
findIndex(pred: Predicate<any>, start?: number, end?: number): number;
|
|
104
|
+
findLastIndex(pred: Predicate<any>, start?: number, end?: number): number;
|
|
102
105
|
/**
|
|
103
106
|
* Returns value at given row.
|
|
104
107
|
*
|
|
@@ -123,9 +126,9 @@ export interface IColumn {
|
|
|
123
126
|
getRowKey(i: number): any;
|
|
124
127
|
valueKey(value: any): any;
|
|
125
128
|
}
|
|
126
|
-
export interface SerializedTable {
|
|
127
|
-
schema: ColumnSchema
|
|
128
|
-
columns: Record<
|
|
129
|
+
export interface SerializedTable<T extends Row> {
|
|
130
|
+
schema: ColumnSchema<T>;
|
|
131
|
+
columns: Record<ColumnID<T>, SerializedColumn>;
|
|
129
132
|
length: number;
|
|
130
133
|
}
|
|
131
134
|
export interface SerializedColumn {
|
|
@@ -137,13 +140,16 @@ export interface SerializedIndex {
|
|
|
137
140
|
next: number;
|
|
138
141
|
}
|
|
139
142
|
export type Row = Record<string, any>;
|
|
140
|
-
export
|
|
143
|
+
export type RowWithMeta<T extends Row> = T & {
|
|
144
|
+
__row: number;
|
|
145
|
+
};
|
|
146
|
+
export interface QueryTerm<T extends Row> {
|
|
141
147
|
type: string;
|
|
142
|
-
column?:
|
|
148
|
+
column?: ColumnID<T>;
|
|
143
149
|
value: any;
|
|
144
150
|
params?: any;
|
|
145
151
|
}
|
|
146
|
-
export type QueryTermOp = Fn3<QueryCtx
|
|
152
|
+
export type QueryTermOp = Fn3<QueryCtx<any>, QueryTerm<any>, Maybe<IColumn>, void>;
|
|
147
153
|
export interface QueryTermOpSpec {
|
|
148
154
|
/**
|
|
149
155
|
* Default mode is: "col";
|
package/bitmap.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare class Bitfield {
|
|
|
30
30
|
first(start?: number, end?: number): number;
|
|
31
31
|
setBit(id: number): void;
|
|
32
32
|
clearBit(id: number): void;
|
|
33
|
+
fill(x: 0 | 1, start: number, end: number): this;
|
|
33
34
|
ensure(size: number): Uint32Array<ArrayBufferLike>;
|
|
34
35
|
removeBit(id: number): void;
|
|
35
36
|
}
|
package/bitmap.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { __clamp } from "./internal/indexof.js";
|
|
1
2
|
class BitmapIndex {
|
|
2
3
|
index = /* @__PURE__ */ new Map();
|
|
3
4
|
/**
|
|
@@ -62,9 +63,13 @@ class Bitfield {
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
|
-
first(start = 0, end
|
|
66
|
+
first(start = 0, end) {
|
|
66
67
|
const { buffer } = this;
|
|
67
|
-
if (!buffer
|
|
68
|
+
if (!buffer) return -1;
|
|
69
|
+
const max = buffer.length << 5;
|
|
70
|
+
start = __clamp(start, 0, max);
|
|
71
|
+
end = __clamp(end ?? max, 0, max);
|
|
72
|
+
if (start >= end) return -1;
|
|
68
73
|
for (let i = start >>> 5, n = Math.min(Math.ceil(end / 32), buffer.length); i < n; i++) {
|
|
69
74
|
let bits = buffer[i];
|
|
70
75
|
while (bits) {
|
|
@@ -84,6 +89,22 @@ class Bitfield {
|
|
|
84
89
|
const w = id >>> 5;
|
|
85
90
|
this.ensure(w)[w] &= ~(1 << (id & 31));
|
|
86
91
|
}
|
|
92
|
+
fill(x, start, end) {
|
|
93
|
+
const i = start >>> 5;
|
|
94
|
+
const j = end >>> 5;
|
|
95
|
+
let m = ~((1 << (start & 31)) - 1);
|
|
96
|
+
const buf = this.ensure(j);
|
|
97
|
+
if (i === j) {
|
|
98
|
+
m &= (1 << (end & 31)) - 1;
|
|
99
|
+
buf[i] = x ? buf[i] | m : buf[i] & ~m;
|
|
100
|
+
} else {
|
|
101
|
+
buf[i] = x ? buf[i] | m : buf[i] & ~m;
|
|
102
|
+
buf.fill(x ? -1 : 0, i + 1, j);
|
|
103
|
+
m = (1 << (end & 31)) - 1;
|
|
104
|
+
buf[j] = x ? buf[j] | m : buf[j] & ~m;
|
|
105
|
+
}
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
87
108
|
ensure(size) {
|
|
88
109
|
const b = this.buffer;
|
|
89
110
|
if (!b || size >= b.length) {
|
package/columns/acolumn.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import type { Predicate } from "@thi.ng/api";
|
|
1
2
|
import type { BidirIndex } from "@thi.ng/bidir-index";
|
|
2
|
-
import { type ColumnSpec, type IColumn, type SerializedColumn, type SerializedIndex } from "../api.js";
|
|
3
|
+
import { type ColumnID, type ColumnSpec, type IColumn, type Row, type SerializedColumn, type SerializedIndex } from "../api.js";
|
|
3
4
|
import { BitmapIndex } from "../bitmap.js";
|
|
4
5
|
import type { Table } from "../table.js";
|
|
5
|
-
export declare abstract class AColumn implements IColumn {
|
|
6
|
-
readonly id:
|
|
7
|
-
table: Table
|
|
6
|
+
export declare abstract class AColumn<T extends Row = Row> implements IColumn {
|
|
7
|
+
readonly id: ColumnID<T>;
|
|
8
|
+
table: Table<T>;
|
|
8
9
|
spec: ColumnSpec;
|
|
9
10
|
bitmap?: BitmapIndex;
|
|
10
11
|
dict?: BidirIndex<any>;
|
|
11
12
|
abstract isArray: boolean;
|
|
12
|
-
constructor(id:
|
|
13
|
+
constructor(id: ColumnID<T>, table: Table<T>);
|
|
13
14
|
abstract load(spec: SerializedColumn): void;
|
|
14
15
|
reindex(): void;
|
|
15
16
|
abstract validate(value: any): boolean;
|
|
@@ -20,6 +21,8 @@ export declare abstract class AColumn implements IColumn {
|
|
|
20
21
|
abstract getRow(i: number): any;
|
|
21
22
|
abstract getRowKey(i: number): any;
|
|
22
23
|
abstract indexOf(value: any, start?: number, end?: number): number;
|
|
24
|
+
findIndex(pred: Predicate<any>, start?: number, end?: number): number;
|
|
25
|
+
findLastIndex(pred: Predicate<any>, start?: number, end?: number): number;
|
|
23
26
|
encode(value: any): any;
|
|
24
27
|
decode(value: any): any;
|
|
25
28
|
protected loadDict(serialized: SerializedIndex): void;
|
package/columns/acolumn.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
2
1
|
import {
|
|
3
2
|
FLAG_BITMAP
|
|
4
3
|
} from "../api.js";
|
|
5
4
|
import { BitmapIndex } from "../bitmap.js";
|
|
5
|
+
import { __columnError } from "../internal/checks.js";
|
|
6
|
+
import { __clamp } from "../internal/indexof.js";
|
|
6
7
|
class AColumn {
|
|
7
8
|
constructor(id, table) {
|
|
8
9
|
this.id = id;
|
|
@@ -16,6 +17,24 @@ class AColumn {
|
|
|
16
17
|
reindex() {
|
|
17
18
|
this.updateBitmap();
|
|
18
19
|
}
|
|
20
|
+
findIndex(pred, start = 0, end) {
|
|
21
|
+
const max = this.table.length - 1;
|
|
22
|
+
start = __clamp(start, 0, max);
|
|
23
|
+
end = __clamp(end ?? max, 0, max);
|
|
24
|
+
for (let i = start; i <= end; i++) {
|
|
25
|
+
if (pred(this.getRow(i))) return i;
|
|
26
|
+
}
|
|
27
|
+
return -1;
|
|
28
|
+
}
|
|
29
|
+
findLastIndex(pred, start = 0, end) {
|
|
30
|
+
const max = this.table.length - 1;
|
|
31
|
+
start = __clamp(start, 0, max);
|
|
32
|
+
end = __clamp(end ?? max, 0, max);
|
|
33
|
+
for (let i = end; i >= start; i--) {
|
|
34
|
+
if (pred(this.getRow(i))) return i;
|
|
35
|
+
}
|
|
36
|
+
return -1;
|
|
37
|
+
}
|
|
19
38
|
encode(value) {
|
|
20
39
|
return value;
|
|
21
40
|
}
|
|
@@ -49,7 +68,7 @@ class AColumn {
|
|
|
49
68
|
}
|
|
50
69
|
}
|
|
51
70
|
ensureValue(val) {
|
|
52
|
-
return val != null ? val : this.spec.cardinality[0] > 0 ? this.spec.default ??
|
|
71
|
+
return val != null ? val : this.spec.cardinality[0] > 0 ? this.spec.default ?? __columnError(this.id, `missing value`) : this.spec.default ?? null;
|
|
53
72
|
}
|
|
54
73
|
ensureBitmap() {
|
|
55
74
|
if (this.spec.flags & FLAG_BITMAP) this.bitmap = new BitmapIndex();
|
package/columns/dict-tuple.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BidirIndex } from "@thi.ng/bidir-index";
|
|
2
|
-
import { type
|
|
2
|
+
import { type Row, type SerializedColumn } from "../api.js";
|
|
3
3
|
import { AColumn } from "./acolumn.js";
|
|
4
|
-
export declare class DictTupleColumn extends
|
|
4
|
+
export declare class DictTupleColumn<T extends Row = Row> extends AColumn<T> {
|
|
5
5
|
values: (number[] | null)[];
|
|
6
6
|
dict: BidirIndex<any>;
|
|
7
7
|
readonly isArray = true;
|
package/columns/dict.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BidirIndex } from "@thi.ng/bidir-index";
|
|
2
|
-
import { type
|
|
2
|
+
import { type Row, type SerializedColumn } from "../api.js";
|
|
3
3
|
import { AColumn } from "./acolumn.js";
|
|
4
|
-
export declare class DictColumn extends
|
|
4
|
+
export declare class DictColumn<T extends Row = Row> extends AColumn<T> {
|
|
5
5
|
values: (number | null)[];
|
|
6
6
|
dict: BidirIndex<any>;
|
|
7
7
|
readonly isArray = false;
|
package/columns/plain.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Row, type SerializedColumn } from "../api.js";
|
|
2
2
|
import { AColumn } from "./acolumn.js";
|
|
3
|
-
export declare class PlainColumn extends
|
|
3
|
+
export declare class PlainColumn<T extends Row = Row> extends AColumn<T> {
|
|
4
4
|
values: any[];
|
|
5
5
|
readonly isArray = false;
|
|
6
6
|
load({ values }: SerializedColumn): void;
|
package/columns/tuple.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Nullable } from "@thi.ng/api";
|
|
2
|
-
import { type
|
|
2
|
+
import { type Row, type SerializedColumn } from "../api.js";
|
|
3
3
|
import { AColumn } from "./acolumn.js";
|
|
4
|
-
export declare class TupleColumn extends
|
|
4
|
+
export declare class TupleColumn<T extends Row = Row> extends AColumn<T> {
|
|
5
5
|
values: Nullable<number[]>[];
|
|
6
6
|
readonly isArray = true;
|
|
7
7
|
load(spec: SerializedColumn): void;
|
package/columns/typedarray.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { type TypedArray } from "@thi.ng/api/typedarray";
|
|
2
|
-
import { type
|
|
2
|
+
import { type ColumnID, type NumericType, type Row, type SerializedColumn } from "../api.js";
|
|
3
3
|
import type { Table } from "../table.js";
|
|
4
4
|
import { AColumn } from "./acolumn.js";
|
|
5
|
-
export declare class TypedArrayColumn extends
|
|
5
|
+
export declare class TypedArrayColumn<T extends Row = Row> extends AColumn<T> {
|
|
6
6
|
values: TypedArray;
|
|
7
7
|
type: NumericType;
|
|
8
8
|
limit: [number, number];
|
|
9
9
|
protected tmp: TypedArray;
|
|
10
10
|
readonly isArray = false;
|
|
11
|
-
constructor(id:
|
|
11
|
+
constructor(id: ColumnID<T>, table: Table<T>);
|
|
12
12
|
load({ values }: SerializedColumn): void;
|
|
13
13
|
validate(value: any): boolean;
|
|
14
14
|
setRow(i: number, value: any): void;
|
package/columns/vector.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { type TypedArray } from "@thi.ng/api/typedarray";
|
|
2
|
-
import { type
|
|
2
|
+
import { type ColumnID, type NumericType, type Row, type SerializedColumn } from "../api.js";
|
|
3
3
|
import type { Table } from "../table.js";
|
|
4
4
|
import { AColumn } from "./acolumn.js";
|
|
5
|
-
export declare class VectorColumn extends
|
|
5
|
+
export declare class VectorColumn<T extends Row = Row> extends AColumn<T> {
|
|
6
6
|
values: TypedArray;
|
|
7
7
|
type: NumericType;
|
|
8
8
|
size: number;
|
|
9
9
|
limit: [number, number];
|
|
10
10
|
protected tmp: TypedArray;
|
|
11
11
|
readonly isArray = false;
|
|
12
|
-
constructor(id:
|
|
12
|
+
constructor(id: ColumnID<T>, table: Table<T>);
|
|
13
13
|
load({ values }: SerializedColumn): void;
|
|
14
14
|
validate(value: any): boolean;
|
|
15
15
|
setRow(i: number, value: any): void;
|
|
16
|
-
getRow(i: number): Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike
|
|
16
|
+
getRow(i: number): Uint32Array<ArrayBufferLike> | Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike>;
|
|
17
17
|
getRowKey(i: number): string;
|
|
18
18
|
valueKey(value: any): string | string[];
|
|
19
19
|
removeRow(i: number): void;
|
package/columns/vector.js
CHANGED
|
@@ -77,6 +77,7 @@ class VectorColumn extends AColumn {
|
|
|
77
77
|
this.bitmap?.removeBit(i);
|
|
78
78
|
}
|
|
79
79
|
indexOf(value, start = 0, end = this.table.length) {
|
|
80
|
+
if (value == null) return -1;
|
|
80
81
|
const { values, bitmap, size } = this;
|
|
81
82
|
start = Math.max(start, 0);
|
|
82
83
|
end = Math.min(end, this.table.length);
|
package/internal/indexof.d.ts
CHANGED
|
@@ -2,5 +2,8 @@ import type { Maybe } from "@thi.ng/api";
|
|
|
2
2
|
import type { BitmapIndex } from "../bitmap.js";
|
|
3
3
|
/** @internal */
|
|
4
4
|
export declare const __indexOfSingle: (needle: any, values: ArrayLike<any>, bitmap: Maybe<BitmapIndex>, max: number, start: number, end?: number) => number;
|
|
5
|
+
/** @internal */
|
|
5
6
|
export declare const __indexOfTuple: (needle: ArrayLike<any> | null, values: ArrayLike<any>, max: number, start: number, end?: number) => number;
|
|
7
|
+
/** @internal */
|
|
8
|
+
export declare const __clamp: (x: number, a: number, b: number) => number;
|
|
6
9
|
//# sourceMappingURL=indexof.d.ts.map
|
package/internal/indexof.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const __indexOfSingle = (needle, values, bitmap, max, start, end = max) => {
|
|
2
|
-
start =
|
|
3
|
-
end =
|
|
2
|
+
start = __clamp(start, 0, max);
|
|
3
|
+
end = __clamp(end, 0, max);
|
|
4
4
|
if (bitmap) return bitmap.index.get(needle)?.first(start, end) ?? -1;
|
|
5
5
|
for (let i = start; i < end; i++) {
|
|
6
6
|
if (values[i] === needle) return i;
|
|
@@ -8,12 +8,13 @@ const __indexOfSingle = (needle, values, bitmap, max, start, end = max) => {
|
|
|
8
8
|
return -1;
|
|
9
9
|
};
|
|
10
10
|
const __indexOfTuple = (needle, values, max, start, end = max) => {
|
|
11
|
-
start =
|
|
12
|
-
end =
|
|
11
|
+
start = __clamp(start, 0, max);
|
|
12
|
+
end = __clamp(end, 0, max);
|
|
13
13
|
if (needle == null) {
|
|
14
14
|
for (let i2 = start; i2 < end; i2++) {
|
|
15
15
|
if (!values[i2]) return i2;
|
|
16
16
|
}
|
|
17
|
+
return -1;
|
|
17
18
|
}
|
|
18
19
|
const n = needle.length;
|
|
19
20
|
let i, j, row;
|
|
@@ -28,7 +29,9 @@ const __indexOfTuple = (needle, values, max, start, end = max) => {
|
|
|
28
29
|
}
|
|
29
30
|
return -1;
|
|
30
31
|
};
|
|
32
|
+
const __clamp = (x, a, b) => x < a ? a : x > b ? b : x;
|
|
31
33
|
export {
|
|
34
|
+
__clamp,
|
|
32
35
|
__indexOfSingle,
|
|
33
36
|
__indexOfTuple
|
|
34
37
|
};
|
package/internal/serialize.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export declare const __serializeTyped: ($values: NumericArray, spec: ColumnSpec,
|
|
|
11
11
|
values: any[];
|
|
12
12
|
};
|
|
13
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
|
|
14
|
+
export declare const __deserializeTyped: (type: Type, flags: number, values: number[]) => Uint32Array<ArrayBufferLike> | Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike> | Int8Array<ArrayBufferLike> | Int16Array<ArrayBufferLike> | Int32Array<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike> | Uint16Array<ArrayBufferLike>;
|
|
15
15
|
//# sourceMappingURL=serialize.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/column-store",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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.2.
|
|
47
|
+
"@thi.ng/rle-pack": "^3.2.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"esbuild": "^0.27.2",
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"status": "alpha",
|
|
127
127
|
"year": 2025
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "34b762e0eeaf54adf4a879843a96a53e5e59d6d1\n"
|
|
130
130
|
}
|
package/query.d.ts
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
import type { Predicate } from "@thi.ng/api";
|
|
2
|
-
import type { QueryTerm, QueryTermOpSpec } from "./api.js";
|
|
2
|
+
import type { ColumnID, QueryTerm, QueryTermOpSpec, Row } from "./api.js";
|
|
3
3
|
import type { Table } from "./table.js";
|
|
4
|
-
export declare class Query {
|
|
5
|
-
table: Table
|
|
6
|
-
terms: QueryTerm[];
|
|
7
|
-
constructor(table: Table
|
|
8
|
-
addTerm(term: QueryTerm): this;
|
|
4
|
+
export declare class Query<T extends Row> {
|
|
5
|
+
readonly table: Table<T>;
|
|
6
|
+
terms: QueryTerm<T>[];
|
|
7
|
+
constructor(table: Table<T>, terms?: QueryTerm<T>[]);
|
|
8
|
+
addTerm(term: QueryTerm<T>): this;
|
|
9
9
|
/** Alias for {@link Query.or} */
|
|
10
|
-
where: (column:
|
|
10
|
+
where: (column: ColumnID<T>, value: any) => this;
|
|
11
11
|
/** Alias for {@link Query.nor} */
|
|
12
|
-
whereNot: (column:
|
|
13
|
-
or(column:
|
|
14
|
-
nor(column:
|
|
15
|
-
and(column:
|
|
16
|
-
nand(column:
|
|
17
|
-
matchColumn(column:
|
|
18
|
-
matchPartialRow(columns:
|
|
19
|
-
matchRow(pred: Predicate<
|
|
20
|
-
|
|
12
|
+
whereNot: (column: ColumnID<T>, value: any) => this;
|
|
13
|
+
or(column: ColumnID<T>, value: any): this;
|
|
14
|
+
nor(column: ColumnID<T>, value: any): this;
|
|
15
|
+
and(column: ColumnID<T>, value: any): this;
|
|
16
|
+
nand(column: ColumnID<T>, value: any): this;
|
|
17
|
+
matchColumn(column: ColumnID<T>, pred: Predicate<any>): this;
|
|
18
|
+
matchPartialRow<K extends ColumnID<T>>(columns: K[], pred: Predicate<Pick<T, K>>): this;
|
|
19
|
+
matchRow(pred: Predicate<T>): this;
|
|
20
|
+
valueRange(column: ColumnID<T>, start: any, end?: any): this;
|
|
21
|
+
rowRange(start?: number, end?: number): this;
|
|
22
|
+
[Symbol.iterator](): Generator<import("./api.js").RowWithMeta<T>, void, unknown>;
|
|
21
23
|
}
|
|
22
|
-
export declare class QueryCtx {
|
|
23
|
-
readonly query: Query
|
|
24
|
-
readonly table: Table
|
|
24
|
+
export declare class QueryCtx<T extends Row> {
|
|
25
|
+
readonly query: Query<T>;
|
|
26
|
+
readonly table: Table<T>;
|
|
25
27
|
readonly size: number;
|
|
26
28
|
bitmap?: Uint32Array;
|
|
27
|
-
constructor(query: Query);
|
|
29
|
+
constructor(query: Query<T>);
|
|
30
|
+
clear(): void;
|
|
28
31
|
makeMask(seed?: Uint32Array): Uint32Array<ArrayBuffer>;
|
|
29
32
|
/**
|
|
30
33
|
* Combines the `mask` with the context's mask (combined using bitwise AND).
|
package/query.js
CHANGED
|
@@ -2,6 +2,8 @@ import { isArray } from "@thi.ng/checks/is-array";
|
|
|
2
2
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
3
3
|
import { unsupportedOp } from "@thi.ng/errors/unsupported";
|
|
4
4
|
import { Bitfield } from "./bitmap.js";
|
|
5
|
+
import { __columnError } from "./internal/checks.js";
|
|
6
|
+
import { __clamp } from "./internal/indexof.js";
|
|
5
7
|
class Query {
|
|
6
8
|
constructor(table, terms = []) {
|
|
7
9
|
this.table = table;
|
|
@@ -49,6 +51,19 @@ class Query {
|
|
|
49
51
|
this.terms.push({ type: "matchRow", value: pred });
|
|
50
52
|
return this;
|
|
51
53
|
}
|
|
54
|
+
valueRange(column, start, end) {
|
|
55
|
+
if (this.table.columns[column].isArray)
|
|
56
|
+
__columnError(
|
|
57
|
+
column,
|
|
58
|
+
`operator not supported with this column type`
|
|
59
|
+
);
|
|
60
|
+
this.terms.push({ type: "valueRange", column, value: { start, end } });
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
rowRange(start = 0, end) {
|
|
64
|
+
this.terms.push({ type: "rowRange", value: { start, end } });
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
52
67
|
*[Symbol.iterator]() {
|
|
53
68
|
const { table } = this;
|
|
54
69
|
const ctx = new QueryCtx(this);
|
|
@@ -57,7 +72,7 @@ class Query {
|
|
|
57
72
|
let column;
|
|
58
73
|
if (term.column) {
|
|
59
74
|
column = ctx.table.columns[term.column];
|
|
60
|
-
if (!column) illegalArgs(`column: ${term.column}`);
|
|
75
|
+
if (!column) illegalArgs(`column: ${String(term.column)}`);
|
|
61
76
|
} else if (QUERY_OPS[term.type].mode !== "row") {
|
|
62
77
|
illegalArgs(
|
|
63
78
|
`query op: ${term.type} requires a column name given`
|
|
@@ -80,6 +95,9 @@ class QueryCtx {
|
|
|
80
95
|
table;
|
|
81
96
|
size;
|
|
82
97
|
bitmap;
|
|
98
|
+
clear() {
|
|
99
|
+
this.bitmap = void 0;
|
|
100
|
+
}
|
|
83
101
|
makeMask(seed) {
|
|
84
102
|
const mask = new Uint32Array(this.size);
|
|
85
103
|
if (seed) mask.set(seed);
|
|
@@ -174,7 +192,7 @@ const execBitAnd = (ctx, term, column) => {
|
|
|
174
192
|
for (let k of key) {
|
|
175
193
|
const b = bitmap.index.get(k)?.buffer;
|
|
176
194
|
if (!b) {
|
|
177
|
-
if (term.type === "and") ctx.
|
|
195
|
+
if (term.type === "and") ctx.clear();
|
|
178
196
|
return;
|
|
179
197
|
}
|
|
180
198
|
colBitmaps.push(b);
|
|
@@ -191,9 +209,7 @@ const execBitAnd = (ctx, term, column) => {
|
|
|
191
209
|
}
|
|
192
210
|
if (mask) {
|
|
193
211
|
term.type === "nand" ? ctx.mergeInvMask(mask) : ctx.mergeMask(mask);
|
|
194
|
-
} else
|
|
195
|
-
ctx.bitmap = void 0;
|
|
196
|
-
}
|
|
212
|
+
} else ctx.clear();
|
|
197
213
|
};
|
|
198
214
|
const execAnd = (ctx, term, column) => {
|
|
199
215
|
const n = ctx.table.length;
|
|
@@ -213,7 +229,7 @@ const execAnd = (ctx, term, column) => {
|
|
|
213
229
|
for (let i = 0; i < n; i++) mask[i] &= m[i];
|
|
214
230
|
} else mask = m;
|
|
215
231
|
} else {
|
|
216
|
-
if (term.type === "and") ctx.
|
|
232
|
+
if (term.type === "and") ctx.clear();
|
|
217
233
|
return;
|
|
218
234
|
}
|
|
219
235
|
}
|
|
@@ -253,7 +269,7 @@ const QUERY_OPS = {
|
|
|
253
269
|
const columns = term.params;
|
|
254
270
|
const pred = term.value;
|
|
255
271
|
let mask;
|
|
256
|
-
for (let i = 0, n =
|
|
272
|
+
for (let i = 0, n = table.length; i < n; i++) {
|
|
257
273
|
if (pred(table.getPartialRow(i, columns, false))) {
|
|
258
274
|
if (!mask) mask = ctx.makeMask();
|
|
259
275
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
@@ -268,7 +284,7 @@ const QUERY_OPS = {
|
|
|
268
284
|
const table = ctx.table;
|
|
269
285
|
const pred = term.value;
|
|
270
286
|
let mask;
|
|
271
|
-
for (let i = 0, n =
|
|
287
|
+
for (let i = 0, n = table.length; i < n; i++) {
|
|
272
288
|
if (pred(table.getRow(i, false))) {
|
|
273
289
|
if (!mask) mask = ctx.makeMask();
|
|
274
290
|
mask[i >>> 5] |= 1 << (i & 31);
|
|
@@ -276,6 +292,42 @@ const QUERY_OPS = {
|
|
|
276
292
|
}
|
|
277
293
|
if (mask) ctx.mergeMask(mask);
|
|
278
294
|
}
|
|
295
|
+
},
|
|
296
|
+
valueRange: {
|
|
297
|
+
fn: (ctx, term, column) => {
|
|
298
|
+
const max = ctx.table.length;
|
|
299
|
+
const { start, end } = term.value;
|
|
300
|
+
let $start = 0;
|
|
301
|
+
let $end = max;
|
|
302
|
+
if (start != null) $start = column.findIndex((x) => x >= start);
|
|
303
|
+
if (end != null)
|
|
304
|
+
$end = column.findLastIndex((x) => x <= end, $start);
|
|
305
|
+
if ($start >= 0 && $end >= 0) {
|
|
306
|
+
const mask = ctx.makeMask();
|
|
307
|
+
const bitmap = new Bitfield(mask);
|
|
308
|
+
bitmap.fill(
|
|
309
|
+
1,
|
|
310
|
+
$start >= 0 ? $start : 0,
|
|
311
|
+
$end >= 0 ? $end + 1 : max
|
|
312
|
+
);
|
|
313
|
+
ctx.mergeMask(mask);
|
|
314
|
+
} else ctx.clear();
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
rowRange: {
|
|
318
|
+
mode: "row",
|
|
319
|
+
fn: (ctx, term) => {
|
|
320
|
+
const max = ctx.table.length;
|
|
321
|
+
let { start = 0, end = max } = term.value;
|
|
322
|
+
start = __clamp(start, 0, max);
|
|
323
|
+
end = __clamp(end, 0, max);
|
|
324
|
+
if (start >= 0 && end >= 0) {
|
|
325
|
+
const mask = ctx.makeMask();
|
|
326
|
+
const bitmap = new Bitfield(mask);
|
|
327
|
+
bitmap.fill(1, start, end);
|
|
328
|
+
ctx.mergeMask(mask);
|
|
329
|
+
} else ctx.clear();
|
|
330
|
+
}
|
|
279
331
|
}
|
|
280
332
|
};
|
|
281
333
|
const registerQueryOp = (type, spec) => {
|
package/table.d.ts
CHANGED
|
@@ -1,38 +1,43 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Maybe } from "@thi.ng/api";
|
|
2
|
+
import { type ColumnID, type ColumnSchema, type ColumnSpec, type ColumnTypeSpec, type IColumn, type QueryTerm, type Row, type RowWithMeta, type SerializedTable } from "./api.js";
|
|
2
3
|
import { Query } from "./query.js";
|
|
3
4
|
/**
|
|
4
5
|
* Placeholder only. Unused so far.
|
|
5
6
|
*/
|
|
6
7
|
export interface TableOpts {
|
|
7
8
|
}
|
|
8
|
-
export declare class Table {
|
|
9
|
+
export declare class Table<T extends Row> {
|
|
9
10
|
opts: TableOpts;
|
|
10
|
-
schema: ColumnSchema
|
|
11
|
-
columns: Record<
|
|
11
|
+
schema: ColumnSchema<T>;
|
|
12
|
+
columns: Record<ColumnID<T>, IColumn>;
|
|
12
13
|
length: number;
|
|
13
|
-
static load(serialized: SerializedTable
|
|
14
|
-
constructor(schema: Record<
|
|
14
|
+
static load<T extends Row>(serialized: SerializedTable<T>, opts?: Partial<TableOpts>): Table<T>;
|
|
15
|
+
constructor(schema: Record<ColumnID<T>, Partial<ColumnSpec> & {
|
|
15
16
|
type: ColumnSpec["type"];
|
|
16
17
|
}>, opts?: Partial<TableOpts>);
|
|
17
|
-
query(terms?: QueryTerm[]): Query
|
|
18
|
-
addColumn(id:
|
|
18
|
+
query(terms?: QueryTerm<T>[]): Query<T>;
|
|
19
|
+
addColumn(id: ColumnID<T>, spec: Partial<ColumnSpec> & {
|
|
19
20
|
type: ColumnSpec["type"];
|
|
20
21
|
}): void;
|
|
21
|
-
removeColumn(id:
|
|
22
|
-
[Symbol.iterator](): Generator<
|
|
22
|
+
removeColumn(id: ColumnID<T>): boolean;
|
|
23
|
+
[Symbol.iterator](): Generator<Maybe<T>, void, unknown>;
|
|
23
24
|
reindex(): void;
|
|
24
|
-
addRow(row:
|
|
25
|
-
addRows(rows: Iterable<
|
|
26
|
-
updateRow(i: number, row:
|
|
25
|
+
addRow(row: Partial<T>): void;
|
|
26
|
+
addRows(rows: Iterable<Partial<T>>): void;
|
|
27
|
+
updateRow(i: number, row: T): void;
|
|
27
28
|
removeRow(i: number): void;
|
|
28
|
-
getRow(i: number, safe?: boolean
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
getRow(i: number, safe?: boolean): Maybe<T>;
|
|
30
|
+
getRow(i: number, safe?: boolean, includeID?: false): Maybe<T>;
|
|
31
|
+
getRow(i: number, safe?: boolean, includeID?: true): Maybe<RowWithMeta<T>>;
|
|
32
|
+
getPartialRow<K extends ColumnID<T>>(i: number, columns: K[], safe?: boolean): Maybe<Pick<T, K>>;
|
|
33
|
+
getPartialRow<K extends ColumnID<T>>(i: number, columns: K[], safe?: boolean, includeID?: false): Maybe<Pick<T, K>>;
|
|
34
|
+
getPartialRow<K extends ColumnID<T>>(i: number, columns: K[], safe?: boolean, includeID?: true): Maybe<RowWithMeta<Pick<T, K>>>;
|
|
35
|
+
indexOf(id: ColumnID<T>, value: any, start?: number, end?: number): number;
|
|
36
|
+
validateRow(row: Partial<T>): void;
|
|
37
|
+
validateColumnSpec(id: ColumnID<T>, spec: ColumnSpec): void;
|
|
33
38
|
toJSON(): {
|
|
34
|
-
schema: ColumnSchema
|
|
35
|
-
columns: Record<
|
|
39
|
+
schema: ColumnSchema<T>;
|
|
40
|
+
columns: Record<ColumnID<T>, IColumn>;
|
|
36
41
|
length: number;
|
|
37
42
|
};
|
|
38
43
|
}
|
package/table.js
CHANGED
|
@@ -43,7 +43,11 @@ class Table {
|
|
|
43
43
|
};
|
|
44
44
|
this.validateColumnSpec(id, $spec);
|
|
45
45
|
this.schema[id] = $spec;
|
|
46
|
-
this.columns[id] = COLUMN_TYPES[spec.type].impl(
|
|
46
|
+
this.columns[id] = COLUMN_TYPES[spec.type].impl(
|
|
47
|
+
this,
|
|
48
|
+
String(id),
|
|
49
|
+
$spec
|
|
50
|
+
);
|
|
47
51
|
}
|
|
48
52
|
removeColumn(id) {
|
|
49
53
|
if (this.columns[id]) return false;
|