@thi.ng/column-store 0.4.1 → 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 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 table, which
59
- acts as facade to the various configured columns. Data items are added as JS
60
- objects to a table, which then pulls out related values, validates them and the
61
- delegates them to the columns.
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: 4.67 KB
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
- const table = new Table({
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 ColumnSchema = Record<string, ColumnSpec>;
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, string, ColumnSpec, IColumn>;
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<string, SerializedColumn>;
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 interface QueryTerm {
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?: string;
148
+ column?: ColumnID<T>;
143
149
  value: any;
144
150
  params?: any;
145
151
  }
146
- export type QueryTermOp = Fn3<QueryCtx, QueryTerm, Maybe<IColumn>, void>;
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 = (this.buffer?.length ?? -1) * 32) {
66
+ first(start = 0, end) {
66
67
  const { buffer } = this;
67
- if (!buffer || start >= end) return -1;
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) {
@@ -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: string;
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: string, table: Table);
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;
@@ -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 ?? illegalArgs(`missing value for column: ${this.id}`) : this.spec.default ?? null;
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();
@@ -1,7 +1,7 @@
1
1
  import { BidirIndex } from "@thi.ng/bidir-index";
2
- import { type IColumn, type SerializedColumn } from "../api.js";
2
+ import { type Row, type SerializedColumn } from "../api.js";
3
3
  import { AColumn } from "./acolumn.js";
4
- export declare class DictTupleColumn extends AColumn implements IColumn {
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 IColumn, type SerializedColumn } from "../api.js";
2
+ import { type Row, type SerializedColumn } from "../api.js";
3
3
  import { AColumn } from "./acolumn.js";
4
- export declare class DictColumn extends AColumn implements IColumn {
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;
@@ -1,6 +1,6 @@
1
- import { type IColumn, type SerializedColumn } from "../api.js";
1
+ import { type Row, type SerializedColumn } from "../api.js";
2
2
  import { AColumn } from "./acolumn.js";
3
- export declare class PlainColumn extends AColumn implements IColumn {
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;
@@ -1,7 +1,7 @@
1
1
  import type { Nullable } from "@thi.ng/api";
2
- import { type IColumn, type SerializedColumn } from "../api.js";
2
+ import { type Row, type SerializedColumn } from "../api.js";
3
3
  import { AColumn } from "./acolumn.js";
4
- export declare class TupleColumn extends AColumn implements IColumn {
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;
@@ -1,14 +1,14 @@
1
1
  import { type TypedArray } from "@thi.ng/api/typedarray";
2
- import { type IColumn, type NumericType, type SerializedColumn } from "../api.js";
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 AColumn implements IColumn {
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: string, table: Table);
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;
@@ -1,19 +1,19 @@
1
1
  import { type TypedArray } from "@thi.ng/api/typedarray";
2
- import { type IColumn, type NumericType, type SerializedColumn } from "../api.js";
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 AColumn implements IColumn {
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: string, table: Table);
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> | Uint32Array<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);
@@ -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
@@ -1,6 +1,6 @@
1
1
  const __indexOfSingle = (needle, values, bitmap, max, start, end = max) => {
2
- start = Math.max(start, 0);
3
- end = Math.min(end, max);
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 = Math.max(start, 0);
12
- end = Math.min(end, max);
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
  };
@@ -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> | Uint32Array<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.4.1",
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",
@@ -126,5 +126,5 @@
126
126
  "status": "alpha",
127
127
  "year": 2025
128
128
  },
129
- "gitHead": "3f0b151fb2283888314eb34206ba50c2dd5c7af6\n"
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, terms?: QueryTerm[]);
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: string, value: any) => this;
10
+ where: (column: ColumnID<T>, value: any) => this;
11
11
  /** Alias for {@link Query.nor} */
12
- whereNot: (column: string, value: any) => this;
13
- or(column: string, value: any): this;
14
- nor(column: string, value: any): this;
15
- and(column: string, value: any): this;
16
- nand(column: string, value: any): this;
17
- matchColumn(column: string, pred: Predicate<any>): this;
18
- matchPartialRow(columns: string[], pred: Predicate<Record<string, any>>): this;
19
- matchRow(pred: Predicate<Record<string, any>>): this;
20
- [Symbol.iterator](): Generator<import("./api.js").Row | undefined, void, unknown>;
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.bitmap = void 0;
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.bitmap = void 0;
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 = ctx.table.length; i < n; i++) {
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 = ctx.table.length; i < n; i++) {
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 { type ColumnSchema, type ColumnSpec, type ColumnTypeSpec, type IColumn, type QueryTerm, type Row, type SerializedTable } from "./api.js";
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<string, IColumn>;
11
+ schema: ColumnSchema<T>;
12
+ columns: Record<ColumnID<T>, IColumn>;
12
13
  length: number;
13
- static load(serialized: SerializedTable, opts?: Partial<TableOpts>): Table;
14
- constructor(schema: Record<string, Partial<ColumnSpec> & {
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: string, spec: Partial<ColumnSpec> & {
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: string): boolean;
22
- [Symbol.iterator](): Generator<Row | undefined, void, unknown>;
22
+ removeColumn(id: ColumnID<T>): boolean;
23
+ [Symbol.iterator](): Generator<Maybe<T>, void, unknown>;
23
24
  reindex(): void;
24
- addRow(row: Row): void;
25
- addRows(rows: Iterable<Row>): void;
26
- updateRow(i: number, row: Row): void;
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, 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;
31
- validateRow(row: Row): void;
32
- validateColumnSpec(id: string, spec: ColumnSpec): void;
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<string, IColumn>;
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(this, id, $spec);
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;