@milaboratories/pf-driver 1.0.44 → 1.0.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pf-driver",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
4
4
  "description": "PFrameDriver implementation abstracted from Middle Layer",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -20,22 +20,22 @@
20
20
  }
21
21
  },
22
22
  "dependencies": {
23
- "@milaboratories/pframes-rs-node": "1.1.2",
24
- "@milaboratories/pframes-rs-wasm": "0.1.0",
23
+ "@milaboratories/pframes-rs-node": "1.1.3",
24
+ "@milaboratories/pframes-rs-wasm": "0.1.2",
25
25
  "es-toolkit": "^1.39.10",
26
26
  "lru-cache": "^11.2.2",
27
27
  "@milaboratories/ts-helpers": "1.7.2",
28
- "@milaboratories/pl-model-middle-layer": "1.11.8",
29
- "@platforma-sdk/model": "1.53.15"
28
+ "@platforma-sdk/model": "1.54.8",
29
+ "@milaboratories/pl-model-middle-layer": "1.11.10"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "~24.5.2",
33
33
  "@vitest/coverage-istanbul": "^4.0.16",
34
34
  "typescript": "~5.6.3",
35
35
  "vitest": "^4.0.16",
36
+ "@milaboratories/build-configs": "1.4.4",
36
37
  "@milaboratories/ts-configs": "1.2.1",
37
- "@milaboratories/ts-builder": "1.2.9",
38
- "@milaboratories/build-configs": "1.4.3"
38
+ "@milaboratories/ts-builder": "1.2.10"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=22.19.0"
@@ -7,6 +7,7 @@ import type {
7
7
  PFrameHandle,
8
8
  PObjectId,
9
9
  PTableDef,
10
+ PTableDefV2,
10
11
  PTableHandle,
11
12
  PTableShape,
12
13
  PTableVector,
@@ -40,7 +41,7 @@ export interface AbstractInternalPFrameDriver<PColumnData> extends PFrameDriver,
40
41
  createPTable(def: PTableDef<PColumn<PColumnData>>): PoolEntry<PTableHandle>;
41
42
 
42
43
  /** Create a new PTable by new Pframe-rs api */
43
- createPTableV2(def: PTableDef<PColumn<PColumnData>>): PoolEntry<PTableHandle>;
44
+ createPTableV2(def: PTableDefV2<PColumn<PColumnData>>): PoolEntry<PTableHandle>;
44
45
 
45
46
  /** Calculates data for the table and returns complete data representation of it */
46
47
  calculateTableData(
@@ -1,8 +1,14 @@
1
1
  import {
2
2
  pTableValue,
3
+ canonicalizeJson,
4
+ filterSpecToSpecQueryExpr,
3
5
  type CalculateTableDataResponse,
4
6
  type PFrameDriver,
5
7
  type PObjectId,
8
+ type PTableColumnId,
9
+ type SpecQuery,
10
+ type SpecQueryExpression,
11
+ SpecQueryBooleanExpression,
6
12
  } from "@platforma-sdk/model";
7
13
  import { readJson, PFrameInternal } from "@milaboratories/pl-model-middle-layer";
8
14
  import { test } from "vitest";
@@ -143,7 +149,7 @@ test.for([{ testCase: "01_json" }, { testCase: "02_binary" }, { testCase: "03_pa
143
149
  },
144
150
  );
145
151
 
146
- test.skip("createTableV2 support", async ({ expect }) => {
152
+ test("createTableV2 support", async ({ expect }) => {
147
153
  await using driver = await createPFrameDriverDouble({});
148
154
 
149
155
  const columnId = "column1" as PObjectId;
@@ -159,33 +165,144 @@ test.skip("createTableV2 support", async ({ expect }) => {
159
165
  valueType: "Int" as const,
160
166
  };
161
167
 
162
- using pTable = driver.createPTableV2({
163
- src: {
164
- type: "column",
165
- column: {
166
- id: columnId,
167
- spec: columnSpec,
168
- data: [
169
- { key: ["a"], val: 10 },
170
- { key: ["b"], val: 20 },
168
+ const axisColumnStr = canonicalizeJson<PTableColumnId>({
169
+ type: "axis",
170
+ id: { name: "axis1", type: "String" },
171
+ }) as string;
172
+
173
+ const valueColumnStr = canonicalizeJson<PTableColumnId>({
174
+ type: "column",
175
+ id: columnId,
176
+ }) as string;
177
+
178
+ const inlineData = [
179
+ { key: ["a"], val: 10 },
180
+ { key: ["b"], val: 20 },
181
+ { key: ["c"], val: 30 },
182
+ { key: ["d"], val: 5 },
183
+ ];
184
+
185
+ const column = { id: columnId, spec: columnSpec, data: inlineData };
186
+
187
+ const columnRef: SpecQueryExpression = { type: "columnRef", value: columnId };
188
+
189
+ const baseQuery: SpecQuery<typeof column> = { type: "column", column };
190
+
191
+ const uiDriver: PFrameDriver = driver;
192
+
193
+ // --- No filters, no sorting ---
194
+ {
195
+ using pTable = driver.createPTableV2({
196
+ query: baseQuery,
197
+ });
198
+
199
+ const shape = await uiDriver.getShape(pTable.key);
200
+ expect(shape.rows).toBe(4);
201
+ expect(shape.columns).toBe(2); // 1 axis + 1 value column
202
+
203
+ const data = await uiDriver.getData(pTable.key, [0, 1]);
204
+ expect(data[0].type).toBe("String");
205
+ expect([...data[0].data]).toEqual(["a", "b", "c", "d"]);
206
+ expect(data[1].type).toBe("Int");
207
+ expect([...data[1].data]).toEqual([10, 20, 30, 5]);
208
+ }
209
+
210
+ // --- With patternEquals filter on axis ---
211
+ {
212
+ using pTable = driver.createPTableV2({
213
+ query: {
214
+ type: "filter",
215
+ input: baseQuery,
216
+ predicate: filterSpecToSpecQueryExpr({
217
+ type: "patternEquals",
218
+ column: axisColumnStr,
219
+ value: "b",
220
+ }) as SpecQueryBooleanExpression,
221
+ },
222
+ });
223
+
224
+ const shape = await uiDriver.getShape(pTable.key);
225
+ expect(shape.rows).toBe(1);
226
+
227
+ const data = await uiDriver.getData(pTable.key, [0, 1]);
228
+ expect([...data[0].data]).toEqual(["b"]);
229
+ expect([...data[1].data]).toEqual([20]);
230
+ }
231
+
232
+ // --- With greaterThan filter on value column ---
233
+ {
234
+ using pTable = driver.createPTableV2({
235
+ query: {
236
+ type: "filter",
237
+ input: baseQuery,
238
+ predicate: filterSpecToSpecQueryExpr({
239
+ type: "greaterThan",
240
+ column: valueColumnStr,
241
+ x: 15,
242
+ }) as SpecQueryBooleanExpression,
243
+ },
244
+ });
245
+
246
+ const shape = await uiDriver.getShape(pTable.key);
247
+ expect(shape.rows).toBe(2);
248
+
249
+ const data = await uiDriver.getData(pTable.key, [0, 1]);
250
+ expect([...data[0].data]).toEqual(["b", "c"]);
251
+ expect([...data[1].data]).toEqual([20, 30]);
252
+ }
253
+
254
+ // --- With sorting descending by value column ---
255
+ {
256
+ using pTable = driver.createPTableV2({
257
+ query: {
258
+ type: "sort",
259
+ input: baseQuery,
260
+ sortBy: [
261
+ {
262
+ expression: columnRef,
263
+ ascending: false,
264
+ nullsFirst: true,
265
+ },
171
266
  ],
172
267
  },
173
- },
174
- partitionFilters: [],
175
- filters: [],
176
- sorting: [],
177
- });
268
+ });
178
269
 
179
- const uiDriver: PFrameDriver = driver;
180
- const shape = await uiDriver.getShape(pTable.key);
181
- expect(shape.rows).toBe(2);
182
- expect(shape.columns).toBe(2); // 1 axis + 1 value column
183
-
184
- const data = await uiDriver.getData(pTable.key, [0, 1]);
185
- // axis column
186
- expect(data[0].type).toBe("String");
187
- expect([...data[0].data]).toEqual(["a", "b"]);
188
- // value column
189
- expect(data[1].type).toBe("Int");
190
- expect([...data[1].data]).toEqual([10, 20]);
270
+ const data = await uiDriver.getData(pTable.key, [0, 1]);
271
+ expect([...data[0].data]).toEqual(["c", "b", "a", "d"]);
272
+ expect([...data[1].data]).toEqual([30, 20, 10, 5]);
273
+ }
274
+
275
+ // --- With combined filter + sorting ---
276
+ {
277
+ using pTable = driver.createPTableV2({
278
+ query: {
279
+ type: "sort",
280
+ input: {
281
+ type: "filter",
282
+ input: baseQuery,
283
+ predicate: filterSpecToSpecQueryExpr({
284
+ type: "and",
285
+ filters: [
286
+ { type: "greaterThan", column: valueColumnStr, x: 5 },
287
+ { type: "patternNotEquals", column: axisColumnStr, value: "c" },
288
+ ],
289
+ }) as SpecQueryBooleanExpression,
290
+ },
291
+ sortBy: [
292
+ {
293
+ expression: columnRef,
294
+ ascending: false,
295
+ nullsFirst: true,
296
+ },
297
+ ],
298
+ },
299
+ });
300
+
301
+ const shape = await uiDriver.getShape(pTable.key);
302
+ expect(shape.rows).toBe(2);
303
+
304
+ const data = await uiDriver.getData(pTable.key, [0, 1]);
305
+ expect([...data[0].data]).toEqual(["b", "a"]);
306
+ expect([...data[1].data]).toEqual([20, 10]);
307
+ }
191
308
  });
@@ -3,7 +3,6 @@ import {
3
3
  mapPTableDef,
4
4
  extractAllColumns,
5
5
  uniqueBy,
6
- getAxisId,
7
6
  canonicalizeJson,
8
7
  bigintReplacer,
9
8
  ValueType,
@@ -24,11 +23,16 @@ import {
24
23
  type UniqueValuesResponse,
25
24
  type PColumn,
26
25
  type PFrameDef,
27
- type JoinEntry,
28
26
  type PTableDef,
29
27
  type PTableRecordSingleValueFilterV2,
30
28
  type PTableRecordFilter,
31
29
  type JsonSerializable,
30
+ type PTableDefV2,
31
+ type SpecQuery,
32
+ mapQuerySpec,
33
+ collectQueryColumns,
34
+ sortQuerySpec,
35
+ sortPTableDef,
32
36
  } from "@platforma-sdk/model";
33
37
  import type { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
34
38
  import {
@@ -57,9 +61,8 @@ import {
57
61
  PTableCachePlainOpsDefaults,
58
62
  type PTableCachePlainOps,
59
63
  } from "./ptable_cache_plain";
60
- // import { createPFrame as createSpecFrame } from "@milaboratories/pframes-rs-wasm";
64
+ import { createPFrame as createSpecFrame } from "@milaboratories/pframes-rs-wasm";
61
65
 
62
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
63
66
  export interface LocalBlobProvider<
64
67
  TreeEntry extends JsonSerializable,
65
68
  > extends PoolLocalBlobProvider<TreeEntry> {}
@@ -177,7 +180,7 @@ export class AbstractPFrameDriver<
177
180
  public createPTable(rawDef: PTableDef<PColumn<PColumnData>>): PoolEntry<PTableHandle> {
178
181
  const pFrameEntry = this.createPFrame(extractAllColumns(rawDef.src));
179
182
  const sortedDef = sortPTableDef(
180
- migratePTableFilters(
183
+ migrateTableFilter(
181
184
  mapPTableDef(rawDef, (c) => c.id),
182
185
  this.logger,
183
186
  ),
@@ -206,50 +209,47 @@ export class AbstractPFrameDriver<
206
209
  };
207
210
  }
208
211
 
209
- public createPTableV2(_rawDef: PTableDef<PColumn<PColumnData>>): PoolEntry<PTableHandle> {
210
- throw new Error("createPTableV2 is not implemented yet");
211
- // const columns = extractAllColumns(rawDef.src);
212
- // const pFrameEntry = this.createPFrame(columns);
213
-
214
- // const columnsMap = columns.reduce(
215
- // (acc, col) => ((acc[col.id] = col.spec), acc),
216
- // {} as Record<string, PColumnSpec>,
217
- // );
218
- // const sortedDef = sortPTableDef(
219
- // migratePTableFilters(
220
- // mapPTableDef(rawDef, (c) => c.id),
221
- // this.logger,
222
- // ),
223
- // );
224
- // const specFrame = createSpecFrame(columnsMap);
225
- // const specQuery = specFrame.rewriteLegacyQuery(sortedDef);
226
- // const { tableSpec, dataQuery } = specFrame.evaluateQuery(specQuery);
227
-
228
- // const pTableEntry = this.pTableDefs.acquire({
229
- // type: "v2",
230
- // pFrameHandle: pFrameEntry.key,
231
- // def: {
232
- // tableSpec,
233
- // dataQuery,
234
- // },
235
- // });
236
- // if (logPFrames()) {
237
- // this.logger(
238
- // "info",
239
- // `Create PTable call (pFrameHandle = ${pFrameEntry.key}; pTableHandle = ${pTableEntry.key})`,
240
- // );
241
- // }
242
-
243
- // const unref = () => {
244
- // pTableEntry.unref();
245
- // pFrameEntry.unref();
246
- // };
247
- // return {
248
- // key: pTableEntry.key,
249
- // resource: pTableEntry.resource,
250
- // unref,
251
- // [Symbol.dispose]: unref,
252
- // };
212
+ public createPTableV2(def: PTableDefV2<PColumn<PColumnData>>): PoolEntry<PTableHandle> {
213
+ const columns = uniqueBy(collectQueryColumns(def.query), (c) => c.id);
214
+ const columnsMap = columns.reduce(
215
+ (acc, col) => ((acc[col.id] = col.spec), acc),
216
+ {} as Record<string, PColumnSpec>,
217
+ );
218
+
219
+ const pFrameEntry = this.createPFrame(columns);
220
+ const specFrame = createSpecFrame(columnsMap);
221
+ const sortedQuery = sortQuerySpec(mapQuerySpec(def.query, (c) => c.id));
222
+ const { tableSpec, dataQuery } = specFrame.evaluateQuery(
223
+ // WASM crate expects `columnId` field name, our types use `column`
224
+ // @todo: remove it after update wasm package
225
+ querySpecToWasm(sortedQuery) as SpecQuery,
226
+ );
227
+
228
+ const pTableEntry = this.pTableDefs.acquire({
229
+ type: "v2",
230
+ pFrameHandle: pFrameEntry.key,
231
+ def: {
232
+ tableSpec,
233
+ dataQuery,
234
+ },
235
+ });
236
+ if (logPFrames()) {
237
+ this.logger(
238
+ "info",
239
+ `Create PTable call (pFrameHandle = ${pFrameEntry.key}; pTableHandle = ${pTableEntry.key})`,
240
+ );
241
+ }
242
+
243
+ const unref = () => {
244
+ pTableEntry.unref();
245
+ pFrameEntry.unref();
246
+ };
247
+ return {
248
+ key: pTableEntry.key,
249
+ resource: pTableEntry.resource,
250
+ unref,
251
+ [Symbol.dispose]: unref,
252
+ };
253
253
  }
254
254
 
255
255
  //
@@ -328,7 +328,7 @@ export class AbstractPFrameDriver<
328
328
  const table = this.pTables.acquire({
329
329
  type: "v1",
330
330
  pFrameHandle: handle,
331
- def: sortPTableDef(migratePTableFilters(request, this.logger)),
331
+ def: sortPTableDef(migrateTableFilter(request, this.logger)),
332
332
  });
333
333
  const { pTablePromise, disposeSignal } = table.resource;
334
334
  const pTable = await pTablePromise;
@@ -459,110 +459,47 @@ export class AbstractPFrameDriver<
459
459
  }
460
460
  }
461
461
 
462
- function sortPTableDef(def: PTableDef<PObjectId>): PTableDef<PObjectId> {
463
- function cmpJoinEntries(lhs: JoinEntry<PObjectId>, rhs: JoinEntry<PObjectId>): number {
464
- if (lhs.type !== rhs.type) {
465
- return lhs.type < rhs.type ? -1 : 1;
466
- }
467
- const type = lhs.type;
468
- switch (type) {
469
- case "column":
470
- return lhs.column < (rhs as typeof lhs).column ? -1 : 1;
471
- case "slicedColumn":
472
- case "artificialColumn":
473
- return lhs.newId < (rhs as typeof lhs).newId ? -1 : 1;
474
- case "inlineColumn": {
475
- return lhs.column.id < (rhs as typeof lhs).column.id ? -1 : 1;
476
- }
477
- case "inner":
478
- case "full": {
479
- const rhsInner = rhs as typeof lhs;
480
- if (lhs.entries.length !== rhsInner.entries.length) {
481
- return lhs.entries.length - rhsInner.entries.length;
482
- }
483
- for (let i = 0; i < lhs.entries.length; i++) {
484
- const cmp = cmpJoinEntries(lhs.entries[i], rhsInner.entries[i]);
485
- if (cmp !== 0) {
486
- return cmp;
487
- }
488
- }
489
- return 0;
490
- }
491
- case "outer": {
492
- const rhsOuter = rhs as typeof lhs;
493
- const cmp = cmpJoinEntries(lhs.primary, rhsOuter.primary);
494
- if (cmp !== 0) {
495
- return cmp;
496
- }
497
- if (lhs.secondary.length !== rhsOuter.secondary.length) {
498
- return lhs.secondary.length - rhsOuter.secondary.length;
499
- }
500
- for (let i = 0; i < lhs.secondary.length; i++) {
501
- const cmp = cmpJoinEntries(lhs.secondary[i], rhsOuter.secondary[i]);
502
- if (cmp !== 0) {
503
- return cmp;
504
- }
505
- }
506
- return 0;
507
- }
508
- default:
509
- assertNever(type);
462
+ /**
463
+ * Converts a SpecQuery to the format expected by the WASM crate.
464
+ * Renames `column` → `columnId` in leaf nodes (QueryColumn, QuerySparseToDenseColumn).
465
+ */
466
+ function querySpecToWasm(query: SpecQuery): unknown {
467
+ switch (query.type) {
468
+ case "column":
469
+ return { type: "column", columnId: query.column };
470
+ case "sparseToDenseColumn": {
471
+ const { column, ...rest } = query;
472
+ return { ...rest, columnId: column };
510
473
  }
474
+ case "inlineColumn":
475
+ return query;
476
+ case "innerJoin":
477
+ case "fullJoin":
478
+ return {
479
+ ...query,
480
+ entries: query.entries.map((e) => ({
481
+ ...e,
482
+ entry: querySpecToWasm(e.entry),
483
+ })),
484
+ };
485
+ case "outerJoin":
486
+ return {
487
+ ...query,
488
+ primary: { ...query.primary, entry: querySpecToWasm(query.primary.entry) },
489
+ secondary: query.secondary.map((e) => ({
490
+ ...e,
491
+ entry: querySpecToWasm(e.entry),
492
+ })),
493
+ };
494
+ case "filter":
495
+ return { ...query, input: querySpecToWasm(query.input) };
496
+ case "sort":
497
+ return { ...query, input: querySpecToWasm(query.input) };
498
+ case "sliceAxes":
499
+ return { ...query, input: querySpecToWasm(query.input) };
500
+ default:
501
+ assertNever(query);
511
502
  }
512
- function sortJoinEntry(entry: JoinEntry<PObjectId>): JoinEntry<PObjectId> {
513
- switch (entry.type) {
514
- case "column":
515
- case "slicedColumn":
516
- case "inlineColumn":
517
- return entry;
518
- case "artificialColumn": {
519
- const sortedAxesIndices = entry.axesIndices.toSorted((lhs, rhs) => lhs - rhs);
520
- return {
521
- ...entry,
522
- axesIndices: sortedAxesIndices,
523
- };
524
- }
525
- case "inner":
526
- case "full": {
527
- const sortedEntries = entry.entries.map(sortJoinEntry);
528
- sortedEntries.sort(cmpJoinEntries);
529
- return {
530
- ...entry,
531
- entries: sortedEntries,
532
- };
533
- }
534
- case "outer": {
535
- const sortedSecondary = entry.secondary.map(sortJoinEntry);
536
- sortedSecondary.sort(cmpJoinEntries);
537
- return {
538
- ...entry,
539
- primary: sortJoinEntry(entry.primary),
540
- secondary: sortedSecondary,
541
- };
542
- }
543
- default:
544
- assertNever(entry);
545
- }
546
- }
547
- function sortFilters(filters: PTableRecordFilter[]): PTableRecordFilter[] {
548
- return filters.toSorted((lhs, rhs) => {
549
- if (lhs.column.type === "axis" && rhs.column.type === "axis") {
550
- const lhsId = canonicalizeJson(getAxisId(lhs.column.id));
551
- const rhsId = canonicalizeJson(getAxisId(rhs.column.id));
552
- return lhsId < rhsId ? -1 : 1;
553
- } else if (lhs.column.type === "column" && rhs.column.type === "column") {
554
- return lhs.column.id < rhs.column.id ? -1 : 1;
555
- } else {
556
- return lhs.column.type === "axis" ? -1 : 1;
557
- }
558
- });
559
- }
560
- return {
561
- src: sortJoinEntry(def.src),
562
- partitionFilters: sortFilters(def.partitionFilters),
563
- filters: sortFilters(def.filters),
564
- sorting: def.sorting,
565
- };
566
503
  }
567
504
 
568
505
  function migrateFilters(
@@ -592,7 +529,7 @@ function migrateFilters(
592
529
  return filtersV2;
593
530
  }
594
531
 
595
- function migratePTableFilters<T>(
532
+ function migrateTableFilter<T>(
596
533
  def: Omit<PTableDef<T>, "partitionFilters"> | PTableDef<T>,
597
534
  logger: PFrameInternal.Logger,
598
535
  ): PTableDef<T> {
@@ -237,7 +237,6 @@ function joinEntryToInternal(entry: JoinEntry<PObjectId>): PFrameInternal.JoinEn
237
237
  secondary: entry.secondary.map((col) => joinEntryToInternal(col)),
238
238
  };
239
239
  default:
240
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
241
240
  throw new PFrameDriverError(`unsupported PFrame join entry type: ${type satisfies never}`);
242
241
  }
243
242
  }
@@ -3,7 +3,7 @@ import type {
3
3
  PFrameHandle,
4
4
  PTableDef,
5
5
  PTableHandle,
6
- QueryData,
6
+ DataQuery,
7
7
  PTableColumnSpec,
8
8
  } from "@platforma-sdk/model";
9
9
  import { hashJson } from "@milaboratories/pl-model-middle-layer";
@@ -19,7 +19,7 @@ export type FullPTableDefV2 = {
19
19
  pFrameHandle: PFrameHandle;
20
20
  def: {
21
21
  tableSpec: PTableColumnSpec[];
22
- dataQuery: QueryData;
22
+ dataQuery: DataQuery;
23
23
  };
24
24
  };
25
25