@thi.ng/column-store 0.4.1 → 0.6.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 +49 -7
- package/api.d.ts +46 -11
- package/bitmap.d.ts +2 -0
- package/bitmap.js +37 -2
- package/columns/acolumn.d.ts +9 -5
- package/columns/acolumn.js +17 -2
- package/columns/dict-tuple.d.ts +3 -2
- package/columns/dict-tuple.js +10 -1
- package/columns/dict.d.ts +3 -2
- package/columns/dict.js +11 -1
- package/columns/plain.d.ts +3 -2
- package/columns/plain.js +11 -1
- package/columns/tuple.d.ts +3 -2
- package/columns/tuple.js +10 -1
- package/columns/typedarray.d.ts +4 -3
- package/columns/typedarray.js +11 -1
- package/columns/vector.d.ts +4 -3
- package/columns/vector.js +21 -3
- package/internal/indexof.d.ts +9 -0
- package/internal/indexof.js +43 -5
- package/package.json +2 -2
- package/query.d.ts +23 -20
- package/query.js +53 -8
- package/table.d.ts +26 -20
- package/table.js +8 -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.50 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
|
|
@@ -91,14 +92,45 @@ export interface IColumn {
|
|
|
91
92
|
validate(value: any): boolean;
|
|
92
93
|
/**
|
|
93
94
|
* Searches for `value` in the column data, optionally constrained to given
|
|
94
|
-
* `start`/`end` range
|
|
95
|
-
* otherwise -1.
|
|
95
|
+
* `start`/`end` range (end index is exclusive and defaults to current table
|
|
96
|
+
* length). If found, returns row ID of first occurrence, otherwise -1.
|
|
96
97
|
*
|
|
97
98
|
* @param value
|
|
98
99
|
* @param start
|
|
99
100
|
* @param end
|
|
100
101
|
*/
|
|
101
102
|
indexOf(value: any, start?: number, end?: number): number;
|
|
103
|
+
/**
|
|
104
|
+
* Searches for `value` in the column data in reverse order (from the last
|
|
105
|
+
* row), optionally constrained to given `start`/`end` range (end index is
|
|
106
|
+
* exclusive and defaults to current table length). If found, returns row ID
|
|
107
|
+
* of first occurrence, otherwise -1.
|
|
108
|
+
*
|
|
109
|
+
* @param value
|
|
110
|
+
* @param start
|
|
111
|
+
* @param end
|
|
112
|
+
*/
|
|
113
|
+
lastIndexOf(value: any, start?: number, end?: number): number;
|
|
114
|
+
/**
|
|
115
|
+
* Similar to {@link IColumn.indexOf}, but applies given predicate function
|
|
116
|
+
* to each row value. Returns index of first row for which the predicate is
|
|
117
|
+
* truthy, otherwise returns -1.
|
|
118
|
+
*
|
|
119
|
+
* @param pred
|
|
120
|
+
* @param start
|
|
121
|
+
* @param end
|
|
122
|
+
*/
|
|
123
|
+
findIndex(pred: Predicate<any>, start?: number, end?: number): number;
|
|
124
|
+
/**
|
|
125
|
+
* Similar to {@link IColumn.lastIndexOf}, but applies given predicate
|
|
126
|
+
* function to each row value. Returns index of first row for which the
|
|
127
|
+
* predicate is truthy, otherwise returns -1.
|
|
128
|
+
*
|
|
129
|
+
* @param pred
|
|
130
|
+
* @param start
|
|
131
|
+
* @param end
|
|
132
|
+
*/
|
|
133
|
+
findLastIndex(pred: Predicate<any>, start?: number, end?: number): number;
|
|
102
134
|
/**
|
|
103
135
|
* Returns value at given row.
|
|
104
136
|
*
|
|
@@ -123,9 +155,9 @@ export interface IColumn {
|
|
|
123
155
|
getRowKey(i: number): any;
|
|
124
156
|
valueKey(value: any): any;
|
|
125
157
|
}
|
|
126
|
-
export interface SerializedTable {
|
|
127
|
-
schema: ColumnSchema
|
|
128
|
-
columns: Record<
|
|
158
|
+
export interface SerializedTable<T extends Row> {
|
|
159
|
+
schema: ColumnSchema<T>;
|
|
160
|
+
columns: Record<ColumnID<T>, SerializedColumn>;
|
|
129
161
|
length: number;
|
|
130
162
|
}
|
|
131
163
|
export interface SerializedColumn {
|
|
@@ -137,13 +169,16 @@ export interface SerializedIndex {
|
|
|
137
169
|
next: number;
|
|
138
170
|
}
|
|
139
171
|
export type Row = Record<string, any>;
|
|
140
|
-
export
|
|
172
|
+
export type RowWithMeta<T extends Row> = T & {
|
|
173
|
+
__row: number;
|
|
174
|
+
};
|
|
175
|
+
export interface QueryTerm<T extends Row> {
|
|
141
176
|
type: string;
|
|
142
|
-
column?:
|
|
177
|
+
column?: ColumnID<T>;
|
|
143
178
|
value: any;
|
|
144
179
|
params?: any;
|
|
145
180
|
}
|
|
146
|
-
export type QueryTermOp = Fn3<QueryCtx
|
|
181
|
+
export type QueryTermOp = Fn3<QueryCtx<any>, QueryTerm<any>, Maybe<IColumn>, void>;
|
|
147
182
|
export interface QueryTermOpSpec {
|
|
148
183
|
/**
|
|
149
184
|
* Default mode is: "col";
|
package/bitmap.d.ts
CHANGED
|
@@ -28,8 +28,10 @@ export declare class Bitfield {
|
|
|
28
28
|
constructor(buffer?: Uint32Array | undefined);
|
|
29
29
|
ones(max?: number): Generator<number, void, unknown>;
|
|
30
30
|
first(start?: number, end?: number): number;
|
|
31
|
+
last(start?: number, end?: number): number;
|
|
31
32
|
setBit(id: number): void;
|
|
32
33
|
clearBit(id: number): void;
|
|
34
|
+
fill(x: 0 | 1, start: number, end: number): this;
|
|
33
35
|
ensure(size: number): Uint32Array<ArrayBufferLike>;
|
|
34
36
|
removeBit(id: number): void;
|
|
35
37
|
}
|
package/bitmap.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { __clampRange } from "./internal/indexof.js";
|
|
1
2
|
class BitmapIndex {
|
|
2
3
|
index = /* @__PURE__ */ new Map();
|
|
3
4
|
/**
|
|
@@ -62,9 +63,11 @@ 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
|
+
[start, end] = __clampRange(buffer.length << 5, start, end);
|
|
70
|
+
if (start >= end) return -1;
|
|
68
71
|
for (let i = start >>> 5, n = Math.min(Math.ceil(end / 32), buffer.length); i < n; i++) {
|
|
69
72
|
let bits = buffer[i];
|
|
70
73
|
while (bits) {
|
|
@@ -76,6 +79,22 @@ class Bitfield {
|
|
|
76
79
|
}
|
|
77
80
|
return -1;
|
|
78
81
|
}
|
|
82
|
+
last(start = 0, end) {
|
|
83
|
+
const { buffer } = this;
|
|
84
|
+
if (!buffer) return -1;
|
|
85
|
+
[start, end] = __clampRange(buffer.length << 5, start, end);
|
|
86
|
+
if (start >= end) return -1;
|
|
87
|
+
for (let i = end >>> 5, n = start >>> 5; i >= n; i--) {
|
|
88
|
+
let bits = buffer[i];
|
|
89
|
+
while (bits) {
|
|
90
|
+
const msb = Math.clz32(bits) ^ 31;
|
|
91
|
+
const x = (i << 5) + msb;
|
|
92
|
+
if (x >= start && x < end) return x;
|
|
93
|
+
bits ^= 1 << msb;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return -1;
|
|
97
|
+
}
|
|
79
98
|
setBit(id) {
|
|
80
99
|
const w = id >>> 5;
|
|
81
100
|
this.ensure(w)[w] |= 1 << (id & 31);
|
|
@@ -84,6 +103,22 @@ class Bitfield {
|
|
|
84
103
|
const w = id >>> 5;
|
|
85
104
|
this.ensure(w)[w] &= ~(1 << (id & 31));
|
|
86
105
|
}
|
|
106
|
+
fill(x, start, end) {
|
|
107
|
+
const i = start >>> 5;
|
|
108
|
+
const j = end >>> 5;
|
|
109
|
+
let m = ~((1 << (start & 31)) - 1);
|
|
110
|
+
const buf = this.ensure(j);
|
|
111
|
+
if (i === j) {
|
|
112
|
+
m &= (1 << (end & 31)) - 1;
|
|
113
|
+
buf[i] = x ? buf[i] | m : buf[i] & ~m;
|
|
114
|
+
} else {
|
|
115
|
+
buf[i] = x ? buf[i] | m : buf[i] & ~m;
|
|
116
|
+
buf.fill(x ? -1 : 0, i + 1, j);
|
|
117
|
+
m = (1 << (end & 31)) - 1;
|
|
118
|
+
buf[j] = x ? buf[j] | m : buf[j] & ~m;
|
|
119
|
+
}
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
87
122
|
ensure(size) {
|
|
88
123
|
const b = this.buffer;
|
|
89
124
|
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,9 @@ 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
|
+
abstract lastIndexOf(value: any, start?: number, end?: number): number;
|
|
25
|
+
findIndex(pred: Predicate<any>, start?: number, end?: number): number;
|
|
26
|
+
findLastIndex(pred: Predicate<any>, start?: number, end?: number): number;
|
|
23
27
|
encode(value: any): any;
|
|
24
28
|
decode(value: any): any;
|
|
25
29
|
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 { __clampRange } from "../internal/indexof.js";
|
|
6
7
|
class AColumn {
|
|
7
8
|
constructor(id, table) {
|
|
8
9
|
this.id = id;
|
|
@@ -16,6 +17,20 @@ class AColumn {
|
|
|
16
17
|
reindex() {
|
|
17
18
|
this.updateBitmap();
|
|
18
19
|
}
|
|
20
|
+
findIndex(pred, start = 0, end) {
|
|
21
|
+
[start, end] = __clampRange(this.table.length, start, end);
|
|
22
|
+
for (let i = start; i < end; i++) {
|
|
23
|
+
if (pred(this.getRow(i))) return i;
|
|
24
|
+
}
|
|
25
|
+
return -1;
|
|
26
|
+
}
|
|
27
|
+
findLastIndex(pred, start = 0, end) {
|
|
28
|
+
[start, end] = __clampRange(this.table.length, start, end);
|
|
29
|
+
for (let i = end; i-- > start; ) {
|
|
30
|
+
if (pred(this.getRow(i))) return i;
|
|
31
|
+
}
|
|
32
|
+
return -1;
|
|
33
|
+
}
|
|
19
34
|
encode(value) {
|
|
20
35
|
return value;
|
|
21
36
|
}
|
|
@@ -49,7 +64,7 @@ class AColumn {
|
|
|
49
64
|
}
|
|
50
65
|
}
|
|
51
66
|
ensureValue(val) {
|
|
52
|
-
return val != null ? val : this.spec.cardinality[0] > 0 ? this.spec.default ??
|
|
67
|
+
return val != null ? val : this.spec.cardinality[0] > 0 ? this.spec.default ?? __columnError(this.id, `missing value`) : this.spec.default ?? null;
|
|
53
68
|
}
|
|
54
69
|
ensureBitmap() {
|
|
55
70
|
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;
|
|
@@ -16,6 +16,7 @@ export declare class DictTupleColumn extends AColumn implements IColumn {
|
|
|
16
16
|
valueKey(value: any): (number | null)[];
|
|
17
17
|
removeRow(i: number): void;
|
|
18
18
|
indexOf(value: any, start?: number, end?: number): number;
|
|
19
|
+
lastIndexOf(value: any, start?: number, end?: number): number;
|
|
19
20
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
20
21
|
toJSON(): {
|
|
21
22
|
dict: {
|
package/columns/dict-tuple.js
CHANGED
|
@@ -2,7 +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
|
+
import { __indexOfTuple, __lastIndexOfTuple } from "../internal/indexof.js";
|
|
6
6
|
import { __serializeDict } from "../internal/serialize.js";
|
|
7
7
|
import { AColumn } from "./acolumn.js";
|
|
8
8
|
class DictTupleColumn extends AColumn {
|
|
@@ -65,6 +65,15 @@ class DictTupleColumn extends AColumn {
|
|
|
65
65
|
end
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
|
+
lastIndexOf(value, start = 0, end) {
|
|
69
|
+
return __lastIndexOfTuple(
|
|
70
|
+
value != null ? this.encode(value) : null,
|
|
71
|
+
this.values,
|
|
72
|
+
this.table.length,
|
|
73
|
+
start,
|
|
74
|
+
end
|
|
75
|
+
);
|
|
76
|
+
}
|
|
68
77
|
replaceValue(currValue, newValue) {
|
|
69
78
|
const { dict, values, bitmap } = this;
|
|
70
79
|
const res = dict.renameKey(currValue, newValue);
|
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;
|
|
@@ -16,6 +16,7 @@ export declare class DictColumn extends AColumn implements IColumn {
|
|
|
16
16
|
valueKey(value: any): number | number[] | undefined;
|
|
17
17
|
removeRow(i: number): void;
|
|
18
18
|
indexOf(value: any, start?: number, end?: number): number;
|
|
19
|
+
lastIndexOf(value: any, start?: number, end?: number): number;
|
|
19
20
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
20
21
|
toJSON(): {
|
|
21
22
|
dict: {
|
package/columns/dict.js
CHANGED
|
@@ -4,7 +4,7 @@ import { decodeBinary, encodeBinary } from "@thi.ng/rle-pack/binary";
|
|
|
4
4
|
import { decodeSimple, encodeSimple } from "@thi.ng/rle-pack/simple";
|
|
5
5
|
import { FLAG_RLE } from "../api.js";
|
|
6
6
|
import { __validateValue } from "../internal/checks.js";
|
|
7
|
-
import { __indexOfSingle } from "../internal/indexof.js";
|
|
7
|
+
import { __indexOfSingle, __lastIndexOfSingle } from "../internal/indexof.js";
|
|
8
8
|
import { __serializeDict } from "../internal/serialize.js";
|
|
9
9
|
import { AColumn } from "./acolumn.js";
|
|
10
10
|
class DictColumn extends AColumn {
|
|
@@ -68,6 +68,16 @@ class DictColumn extends AColumn {
|
|
|
68
68
|
end
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
|
+
lastIndexOf(value, start = 0, end) {
|
|
72
|
+
return __lastIndexOfSingle(
|
|
73
|
+
this.encode(value),
|
|
74
|
+
this.values,
|
|
75
|
+
this.bitmap,
|
|
76
|
+
this.table.length,
|
|
77
|
+
start,
|
|
78
|
+
end
|
|
79
|
+
);
|
|
80
|
+
}
|
|
71
81
|
replaceValue(currValue, newValue) {
|
|
72
82
|
const { dict, values, bitmap } = this;
|
|
73
83
|
const res = dict.renameKey(currValue, newValue);
|
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;
|
|
@@ -11,6 +11,7 @@ export declare class PlainColumn extends AColumn implements IColumn {
|
|
|
11
11
|
valueKey(x: any): any;
|
|
12
12
|
removeRow(i: number): void;
|
|
13
13
|
indexOf(value: any, start?: number, end?: number): number;
|
|
14
|
+
lastIndexOf(value: any, start?: number, end?: number): number;
|
|
14
15
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
15
16
|
toJSON(): {
|
|
16
17
|
values: any[];
|
package/columns/plain.js
CHANGED
|
@@ -1,7 +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
|
+
import { __indexOfSingle, __lastIndexOfSingle } from "../internal/indexof.js";
|
|
5
5
|
import { __replaceValue } from "../internal/replace.js";
|
|
6
6
|
import { AColumn } from "./acolumn.js";
|
|
7
7
|
class PlainColumn extends AColumn {
|
|
@@ -46,6 +46,16 @@ class PlainColumn extends AColumn {
|
|
|
46
46
|
end
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
|
+
lastIndexOf(value, start = 0, end) {
|
|
50
|
+
return __lastIndexOfSingle(
|
|
51
|
+
value,
|
|
52
|
+
this.values,
|
|
53
|
+
this.bitmap,
|
|
54
|
+
this.table.length,
|
|
55
|
+
start,
|
|
56
|
+
end
|
|
57
|
+
);
|
|
58
|
+
}
|
|
49
59
|
replaceValue(currValue, newValue) {
|
|
50
60
|
return __replaceValue(this.bitmap, this.values, currValue, newValue);
|
|
51
61
|
}
|
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;
|
|
@@ -13,6 +13,7 @@ export declare class TupleColumn extends AColumn implements IColumn {
|
|
|
13
13
|
valueKey(value: any): any[];
|
|
14
14
|
removeRow(i: number): void;
|
|
15
15
|
indexOf(value: any, start?: number, end?: number): number;
|
|
16
|
+
lastIndexOf(value: any, start?: number, end?: number): number;
|
|
16
17
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
17
18
|
toJSON(): {
|
|
18
19
|
values: Nullable<number[]>[];
|
package/columns/tuple.js
CHANGED
|
@@ -1,7 +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
|
+
import { __indexOfTuple, __lastIndexOfTuple } from "../internal/indexof.js";
|
|
5
5
|
import { AColumn } from "./acolumn.js";
|
|
6
6
|
class TupleColumn extends AColumn {
|
|
7
7
|
values = [];
|
|
@@ -48,6 +48,15 @@ class TupleColumn extends AColumn {
|
|
|
48
48
|
end
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
|
+
lastIndexOf(value, start = 0, end) {
|
|
52
|
+
return __lastIndexOfTuple(
|
|
53
|
+
value,
|
|
54
|
+
this.values,
|
|
55
|
+
this.table.length,
|
|
56
|
+
start,
|
|
57
|
+
end
|
|
58
|
+
);
|
|
59
|
+
}
|
|
51
60
|
replaceValue(currValue, newValue) {
|
|
52
61
|
const { values, bitmap } = this;
|
|
53
62
|
const isUnique = this.spec.flags & FLAG_UNIQUE;
|
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;
|
|
@@ -17,6 +17,7 @@ export declare class TypedArrayColumn extends AColumn implements IColumn {
|
|
|
17
17
|
valueKey(value: any): number | number[];
|
|
18
18
|
removeRow(i: number): void;
|
|
19
19
|
indexOf(value: any, start?: number, end?: number): number;
|
|
20
|
+
lastIndexOf(value: any, start?: number, end?: number): number;
|
|
20
21
|
replaceValue(currValue: any, newValue: any): boolean;
|
|
21
22
|
toJSON(): {
|
|
22
23
|
values: any[];
|
package/columns/typedarray.js
CHANGED
|
@@ -4,7 +4,7 @@ import { isNumber } from "@thi.ng/checks/is-number";
|
|
|
4
4
|
import {
|
|
5
5
|
LIMITS
|
|
6
6
|
} from "../api.js";
|
|
7
|
-
import { __indexOfSingle } from "../internal/indexof.js";
|
|
7
|
+
import { __indexOfSingle, __lastIndexOfSingle } from "../internal/indexof.js";
|
|
8
8
|
import { __replaceValue } from "../internal/replace.js";
|
|
9
9
|
import { __deserializeTyped, __serializeTyped } from "../internal/serialize.js";
|
|
10
10
|
import { AColumn } from "./acolumn.js";
|
|
@@ -78,6 +78,16 @@ class TypedArrayColumn extends AColumn {
|
|
|
78
78
|
end
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
|
+
lastIndexOf(value, start = 0, end) {
|
|
82
|
+
return __lastIndexOfSingle(
|
|
83
|
+
value,
|
|
84
|
+
this.values,
|
|
85
|
+
this.bitmap,
|
|
86
|
+
this.table.length,
|
|
87
|
+
start,
|
|
88
|
+
end
|
|
89
|
+
);
|
|
90
|
+
}
|
|
81
91
|
replaceValue(currValue, newValue) {
|
|
82
92
|
return __replaceValue(this.bitmap, this.values, currValue, newValue);
|
|
83
93
|
}
|
package/columns/vector.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
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;
|
|
@@ -18,6 +18,7 @@ export declare class VectorColumn extends AColumn implements IColumn {
|
|
|
18
18
|
valueKey(value: any): string | string[];
|
|
19
19
|
removeRow(i: number): void;
|
|
20
20
|
indexOf(value: any, start?: number, end?: number): number;
|
|
21
|
+
lastIndexOf(value: any, start?: number, end?: number): number;
|
|
21
22
|
replaceValue(): boolean;
|
|
22
23
|
toJSON(): {
|
|
23
24
|
values: any[];
|
package/columns/vector.js
CHANGED
|
@@ -6,6 +6,7 @@ import { unsupportedOp } from "@thi.ng/errors/unsupported";
|
|
|
6
6
|
import {
|
|
7
7
|
LIMITS
|
|
8
8
|
} from "../api.js";
|
|
9
|
+
import { __clampRange } from "../internal/indexof.js";
|
|
9
10
|
import { __deserializeTyped, __serializeTyped } from "../internal/serialize.js";
|
|
10
11
|
import { AColumn } from "./acolumn.js";
|
|
11
12
|
class VectorColumn extends AColumn {
|
|
@@ -76,10 +77,10 @@ class VectorColumn extends AColumn {
|
|
|
76
77
|
this.values.fill(0, (length - 1) * size);
|
|
77
78
|
this.bitmap?.removeBit(i);
|
|
78
79
|
}
|
|
79
|
-
indexOf(value, start = 0, end
|
|
80
|
+
indexOf(value, start = 0, end) {
|
|
81
|
+
if (value == null) return -1;
|
|
80
82
|
const { values, bitmap, size } = this;
|
|
81
|
-
start =
|
|
82
|
-
end = Math.min(end, this.table.length);
|
|
83
|
+
[start, end] = __clampRange(this.table.length, start, end);
|
|
83
84
|
if (bitmap) {
|
|
84
85
|
return bitmap.index.get(this.valueKey(value))?.first(start, end) ?? -1;
|
|
85
86
|
}
|
|
@@ -93,6 +94,23 @@ class VectorColumn extends AColumn {
|
|
|
93
94
|
}
|
|
94
95
|
return -1;
|
|
95
96
|
}
|
|
97
|
+
lastIndexOf(value, start = 0, end) {
|
|
98
|
+
if (value == null || value.length !== this.size) return -1;
|
|
99
|
+
const { values, bitmap, size } = this;
|
|
100
|
+
[start, end] = __clampRange(this.table.length, start, end);
|
|
101
|
+
if (bitmap) {
|
|
102
|
+
return bitmap.index.get(this.valueKey(value))?.last(start, end) ?? -1;
|
|
103
|
+
}
|
|
104
|
+
start *= size;
|
|
105
|
+
let i, j;
|
|
106
|
+
outer: for (i = end * size; (i -= size) >= start; ) {
|
|
107
|
+
for (j = 0; j < size; j++) {
|
|
108
|
+
if (values[i + j] !== value[j]) continue outer;
|
|
109
|
+
}
|
|
110
|
+
return i / size;
|
|
111
|
+
}
|
|
112
|
+
return -1;
|
|
113
|
+
}
|
|
96
114
|
replaceValue() {
|
|
97
115
|
unsupportedOp("TODO");
|
|
98
116
|
}
|
package/internal/indexof.d.ts
CHANGED
|
@@ -2,5 +2,14 @@ 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 */
|
|
6
|
+
export declare const __lastIndexOfSingle: (needle: any, values: ArrayLike<any>, bitmap: Maybe<BitmapIndex>, max: number, start: number, end?: number) => number;
|
|
7
|
+
/** @internal */
|
|
5
8
|
export declare const __indexOfTuple: (needle: ArrayLike<any> | null, values: ArrayLike<any>, max: number, start: number, end?: number) => number;
|
|
9
|
+
/** @internal */
|
|
10
|
+
export declare const __lastIndexOfTuple: (needle: ArrayLike<any> | null, values: ArrayLike<any>, max: number, start: number, end?: number) => number;
|
|
11
|
+
/** @internal */
|
|
12
|
+
export declare const __clamp: (x: number, a: number, b: number) => number;
|
|
13
|
+
/** @internal */
|
|
14
|
+
export declare const __clampRange: (max: number, start: number, end?: number) => number[];
|
|
6
15
|
//# sourceMappingURL=indexof.d.ts.map
|
package/internal/indexof.js
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
const __indexOfSingle = (needle, values, bitmap, max, start, end = max) => {
|
|
2
|
-
start =
|
|
3
|
-
end = Math.min(end, max);
|
|
2
|
+
[start, end] = __clampRange(max, start, end);
|
|
4
3
|
if (bitmap) return bitmap.index.get(needle)?.first(start, end) ?? -1;
|
|
5
4
|
for (let i = start; i < end; i++) {
|
|
6
5
|
if (values[i] === needle) return i;
|
|
7
6
|
}
|
|
8
7
|
return -1;
|
|
9
8
|
};
|
|
9
|
+
const __lastIndexOfSingle = (needle, values, bitmap, max, start, end = max) => {
|
|
10
|
+
[start, end] = __clampRange(max, start, end);
|
|
11
|
+
if (bitmap) return bitmap.index.get(needle)?.last(start, end) ?? -1;
|
|
12
|
+
for (let i = end; i-- > start; ) {
|
|
13
|
+
if (values[i] === needle) return i;
|
|
14
|
+
}
|
|
15
|
+
return -1;
|
|
16
|
+
};
|
|
10
17
|
const __indexOfTuple = (needle, values, max, start, end = max) => {
|
|
11
|
-
start =
|
|
12
|
-
end = Math.min(end, max);
|
|
18
|
+
[start, end] = __clampRange(max, start, end);
|
|
13
19
|
if (needle == null) {
|
|
14
20
|
for (let i2 = start; i2 < end; i2++) {
|
|
15
21
|
if (!values[i2]) return i2;
|
|
16
22
|
}
|
|
23
|
+
return -1;
|
|
17
24
|
}
|
|
18
25
|
const n = needle.length;
|
|
19
26
|
let i, j, row;
|
|
@@ -28,7 +35,38 @@ const __indexOfTuple = (needle, values, max, start, end = max) => {
|
|
|
28
35
|
}
|
|
29
36
|
return -1;
|
|
30
37
|
};
|
|
38
|
+
const __lastIndexOfTuple = (needle, values, max, start, end = max) => {
|
|
39
|
+
[start, end] = __clampRange(max, start, end);
|
|
40
|
+
if (needle == null) {
|
|
41
|
+
for (let i2 = end; i2-- > start; ) {
|
|
42
|
+
if (!values[i2]) return i2;
|
|
43
|
+
}
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
const n = needle.length;
|
|
47
|
+
let i, j, row;
|
|
48
|
+
outer: for (i = end; i-- > start; ) {
|
|
49
|
+
row = values[i];
|
|
50
|
+
if (row?.length === n) {
|
|
51
|
+
for (j = 0; j < n; j++) {
|
|
52
|
+
if (row[j] !== needle[j]) continue outer;
|
|
53
|
+
}
|
|
54
|
+
return i;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return -1;
|
|
58
|
+
};
|
|
59
|
+
const __clamp = (x, a, b) => x < a ? a : x > b ? b : x;
|
|
60
|
+
const __clampRange = (max, start, end) => {
|
|
61
|
+
start = __clamp(start, 0, max);
|
|
62
|
+
end = __clamp(end ?? max, start, max);
|
|
63
|
+
return [start, end];
|
|
64
|
+
};
|
|
31
65
|
export {
|
|
66
|
+
__clamp,
|
|
67
|
+
__clampRange,
|
|
32
68
|
__indexOfSingle,
|
|
33
|
-
__indexOfTuple
|
|
69
|
+
__indexOfTuple,
|
|
70
|
+
__lastIndexOfSingle,
|
|
71
|
+
__lastIndexOfTuple
|
|
34
72
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/column-store",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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": "f1d8da5644dd22f212fc313df2187380da968a7c\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 { __clampRange } 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,8 +292,37 @@ 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
|
+
__fillMask(ctx, $start, $end >= 0 ? $end + 1 : $end);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
rowRange: {
|
|
309
|
+
mode: "row",
|
|
310
|
+
fn: (ctx, term) => {
|
|
311
|
+
const max = ctx.table.length;
|
|
312
|
+
let { start = 0, end = max } = term.value;
|
|
313
|
+
[start, end] = __clampRange(max, start, end);
|
|
314
|
+
__fillMask(ctx, start, end);
|
|
315
|
+
}
|
|
279
316
|
}
|
|
280
317
|
};
|
|
318
|
+
const __fillMask = (ctx, start, end, fill = 1) => {
|
|
319
|
+
if (start >= 0 && end >= 0) {
|
|
320
|
+
const mask = ctx.makeMask();
|
|
321
|
+
const bitmap = new Bitfield(mask);
|
|
322
|
+
bitmap.fill(fill, start, end);
|
|
323
|
+
ctx.mergeMask(mask);
|
|
324
|
+
} else ctx.clear();
|
|
325
|
+
};
|
|
281
326
|
const registerQueryOp = (type, spec) => {
|
|
282
327
|
if (QUERY_OPS[type]) illegalArgs(`query op ${type} already registered`);
|
|
283
328
|
QUERY_OPS[type] = spec;
|
package/table.d.ts
CHANGED
|
@@ -1,38 +1,44 @@
|
|
|
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
|
+
lastIndexOf(id: ColumnID<T>, value: any, start?: number, end?: number): number;
|
|
37
|
+
validateRow(row: Partial<T>): void;
|
|
38
|
+
validateColumnSpec(id: ColumnID<T>, spec: ColumnSpec): void;
|
|
33
39
|
toJSON(): {
|
|
34
|
-
schema: ColumnSchema
|
|
35
|
-
columns: Record<
|
|
40
|
+
schema: ColumnSchema<T>;
|
|
41
|
+
columns: Record<ColumnID<T>, IColumn>;
|
|
36
42
|
length: number;
|
|
37
43
|
};
|
|
38
44
|
}
|
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;
|
|
@@ -101,6 +105,9 @@ class Table {
|
|
|
101
105
|
indexOf(id, value, start, end) {
|
|
102
106
|
return this.columns[id]?.indexOf(value, start, end) ?? -1;
|
|
103
107
|
}
|
|
108
|
+
lastIndexOf(id, value, start, end) {
|
|
109
|
+
return this.columns[id]?.lastIndexOf(value, start, end) ?? -1;
|
|
110
|
+
}
|
|
104
111
|
validateRow(row) {
|
|
105
112
|
const { columns } = this;
|
|
106
113
|
for (let id in columns) {
|