@thi.ng/column-store 0.12.0 → 0.13.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
@@ -337,6 +337,24 @@ for(let result of query) { ... }
337
337
  const results = [...query];
338
338
  ```
339
339
 
340
+ Alternatively, queries can also be eagerly executed via
341
+ [`.exec()`](https://docs.thi.ng/umbrella/column-store/classes/Query.html#exec).
342
+ This will also include metadata alongside the results, useful for paging
343
+ results.
344
+
345
+ ```ts
346
+ // predefine query
347
+ const query = table.query().or("name", ["alice", "bob"]).limit(10);
348
+
349
+ const results = query.exec();
350
+ // {
351
+ // results: [{...}, ...],
352
+ // total: 123,
353
+ // offset: 0,
354
+ // limit: 10
355
+ // }
356
+ ```
357
+
340
358
  #### Optimized row iteration
341
359
 
342
360
  The query engine works by applying a number of [query
@@ -475,7 +493,7 @@ TODO
475
493
 
476
494
  ## Status
477
495
 
478
- **ALPHA** - bleeding edge / work-in-progress
496
+ **BETA** - possibly breaking changes forthcoming
479
497
 
480
498
  [Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bcolumn-store%5D+in%3Atitle)
481
499
 
@@ -505,7 +523,7 @@ For Node.js REPL:
505
523
  const cs = await import("@thi.ng/column-store");
506
524
  ```
507
525
 
508
- Package sizes (brotli'd, pre-treeshake): ESM: 6.27 KB
526
+ Package sizes (brotli'd, pre-treeshake): ESM: 6.48 KB
509
527
 
510
528
  ## Dependencies
511
529
 
package/api.d.ts CHANGED
@@ -192,6 +192,25 @@ export interface QueryTermOpSpec {
192
192
  */
193
193
  fn: QueryTermOp;
194
194
  }
195
+ export interface QueryResult<T extends Row> {
196
+ /**
197
+ * Array of result rows (each incl. `__row` index)
198
+ */
199
+ results: RowWithMeta<T>[];
200
+ /**
201
+ * Total number of query results (might be more than in
202
+ * {@link QueryResult.results}).
203
+ */
204
+ total: number;
205
+ /**
206
+ * Page info. Configured result offset (via {@link Query.limit}).
207
+ */
208
+ offset: number;
209
+ /**
210
+ * Page info. Configured result limit (via {@link Query.limit}).
211
+ */
212
+ limit: number;
213
+ }
195
214
  /**
196
215
  * Initial capacity for typedarray and vector columns
197
216
  *
@@ -11,5 +11,5 @@ export declare const __lastIndexOfTuple: (needle: ArrayLike<any> | null, values:
11
11
  /** @internal */
12
12
  export declare const __clamp: (x: number, a: number, b: number) => number;
13
13
  /** @internal */
14
- export declare const __clampRange: (max: number, start: number, end?: number) => number[];
14
+ export declare const __clampRange: (max: number, start: number, end?: number) => [number, number];
15
15
  //# sourceMappingURL=indexof.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/column-store",
3
- "version": "0.12.0",
3
+ "version": "0.13.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",
@@ -127,8 +127,8 @@
127
127
  }
128
128
  },
129
129
  "thi.ng": {
130
- "status": "alpha",
130
+ "status": "beta",
131
131
  "year": 2025
132
132
  },
133
- "gitHead": "82ec7cc54d0e89dad89cde87fa36c444512d71d7\n"
133
+ "gitHead": "b3bcd5623ec78379cceebc6808d7c7b262dfc168\n"
134
134
  }
package/query.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Comparator, Predicate } from "@thi.ng/api";
2
- import type { ColumnID, QueryTerm, QueryTermOpSpec, Row, RowWithMeta } from "./api.js";
2
+ import type { ColumnID, QueryResult, QueryTerm, QueryTermOpSpec, Row, RowWithMeta } from "./api.js";
3
3
  import type { Table } from "./table.js";
4
4
  export declare class Query<T extends Row> {
5
5
  readonly table: Table<T>;
@@ -36,6 +36,12 @@ export declare class Query<T extends Row> {
36
36
  matchRow(pred: Predicate<T>): this;
37
37
  valueRange(column: ColumnID<T>, start: any, end?: any): this;
38
38
  rowRange(start?: number, end?: number): this;
39
+ /**
40
+ * Eagerly executes query with current terms and returns results as array in
41
+ * an object including total number of results and selected paging details
42
+ * (i.e. offset & limit).
43
+ */
44
+ exec(): QueryResult<T>;
39
45
  [Symbol.iterator](): Generator<RowWithMeta<T>, void, unknown>;
40
46
  }
41
47
  export declare class QueryCtx<T extends Row> {
@@ -44,11 +50,13 @@ export declare class QueryCtx<T extends Row> {
44
50
  readonly size: number;
45
51
  bitmap?: Uint32Array;
46
52
  constructor(query: Query<T>);
53
+ exec(): this;
47
54
  /**
48
55
  * If a bitmap is already present, yield iterator of only currently selected
49
56
  * row IDs, otherwise yields row IDs in `[0,table.length)` range.
50
57
  */
51
58
  [Symbol.iterator](): Generator<number, void, unknown>;
59
+ rows(): Generator<RowWithMeta<T>, void, unknown>;
52
60
  clear(): void;
53
61
  makeMask(seed?: number | Uint32Array): Uint32Array<ArrayBuffer>;
54
62
  /**
package/query.js CHANGED
@@ -103,42 +103,60 @@ class Query {
103
103
  this.terms.push({ type: "rowRange", value: { start, end } });
104
104
  return this;
105
105
  }
106
- *[Symbol.iterator]() {
106
+ /**
107
+ * Eagerly executes query with current terms and returns results as array in
108
+ * an object including total number of results and selected paging details
109
+ * (i.e. offset & limit).
110
+ */
111
+ exec() {
107
112
  const { table, _limit, _offset } = this;
108
- const ctx = new QueryCtx(this);
109
- for (const term of this.terms) {
110
- const op = QUERY_OPS[term.type];
111
- let column;
112
- if (term.column) {
113
- column = ctx.table.columns[term.column];
114
- if (!column) illegalArgs(`column: ${String(term.column)}`);
115
- } else if (QUERY_OPS[term.type].mode !== "row") {
116
- illegalArgs(
117
- `query op: ${term.type} requires a column name given`
118
- );
119
- }
120
- if (!op.fn(ctx, term, column)) return;
121
- }
122
- if (!ctx.bitmap) return;
113
+ const ctx = new QueryCtx(this).exec();
114
+ let rows = [];
115
+ const result = {
116
+ results: rows,
117
+ total: 0,
118
+ offset: _offset,
119
+ limit: _limit
120
+ };
121
+ if (!ctx.bitmap && this.terms.length) return result;
123
122
  if (this._cmp) {
124
- const rows = [];
123
+ rows = [...ctx.rows()].sort(this._cmp);
124
+ result.total = rows.length;
125
+ result.results = rows.slice(
126
+ ...__clampRange(rows.length, _offset, _offset + _limit)
127
+ );
128
+ } else {
129
+ let n = 0;
130
+ const max = _offset + _limit;
125
131
  for (const i of ctx) {
126
- rows.push(table.getRow(i, false, true));
127
- }
128
- rows.sort(this._cmp);
129
- for (let i = this._offset, n2 = Math.min(rows.length, i + _limit); i < n2; i++) {
130
- yield rows[i];
132
+ if (n >= _offset && n < max) {
133
+ rows.push(table.getRow(i, false, true));
134
+ }
135
+ n++;
131
136
  }
132
- return;
137
+ result.total = n;
133
138
  }
134
- let j = 0;
135
- const n = _offset + _limit;
136
- for (const i of ctx) {
137
- if (j >= _offset) {
138
- if (j >= n) return;
139
- yield table.getRow(i, false, true);
139
+ return result;
140
+ }
141
+ *[Symbol.iterator]() {
142
+ const { table, _limit, _offset } = this;
143
+ const ctx = new QueryCtx(this).exec();
144
+ if (!ctx.bitmap && this.terms.length) return;
145
+ if (this._cmp) {
146
+ const rows = [...ctx.rows()].sort(this._cmp);
147
+ yield* rows.slice(
148
+ ...__clampRange(rows.length, _offset, _offset + _limit)
149
+ );
150
+ } else {
151
+ let j = 0;
152
+ const n = _offset + _limit;
153
+ for (const i of ctx) {
154
+ if (j >= _offset) {
155
+ if (j >= n) return;
156
+ yield table.getRow(i, false, true);
157
+ }
158
+ j++;
140
159
  }
141
- j++;
142
160
  }
143
161
  }
144
162
  }
@@ -151,6 +169,22 @@ class QueryCtx {
151
169
  table;
152
170
  size;
153
171
  bitmap;
172
+ exec() {
173
+ for (const term of this.query.terms) {
174
+ const op = QUERY_OPS[term.type];
175
+ let column;
176
+ if (term.column) {
177
+ column = this.table.columns[term.column];
178
+ if (!column) illegalArgs(`column: ${String(term.column)}`);
179
+ } else if (QUERY_OPS[term.type].mode !== "row") {
180
+ illegalArgs(
181
+ `query op: ${term.type} requires a column name given`
182
+ );
183
+ }
184
+ if (!op.fn(this, term, column)) break;
185
+ }
186
+ return this;
187
+ }
154
188
  /**
155
189
  * If a bitmap is already present, yield iterator of only currently selected
156
190
  * row IDs, otherwise yields row IDs in `[0,table.length)` range.
@@ -163,6 +197,10 @@ class QueryCtx {
163
197
  for (let i = 0; i < n; i++) yield i;
164
198
  }
165
199
  }
200
+ *rows() {
201
+ const table = this.query.table;
202
+ for (const i of this) yield table.getRow(i, false, true);
203
+ }
166
204
  clear() {
167
205
  this.bitmap = void 0;
168
206
  }