@milaboratories/pf-driver 1.4.11 → 1.4.13

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.
@@ -71,7 +71,6 @@ import {
71
71
  PTableCachePlainOpsDefaults,
72
72
  type PTableCachePlainOps,
73
73
  } from "./ptable_cache_plain";
74
- import { createPFrame as createSpecFrame } from "@milaboratories/pframes-rs-wasm";
75
74
 
76
75
  export interface LocalBlobProvider<TreeEntry extends JsonSerializable>
77
76
  extends PoolLocalBlobProvider<TreeEntry>, AsyncDisposable {}
@@ -194,26 +193,31 @@ export class AbstractPFrameDriver<
194
193
  }
195
194
 
196
195
  public createPTable(rawDef: PTableDef<PColumn<PColumnData>>): PoolEntry<PTableHandle> {
197
- using pFrameGuard = new PoolEntryGuard(this.createPFrame(extractAllColumns(rawDef.src)));
196
+ const columns = uniqueBy(extractAllColumns(rawDef.src), (c) => c.id);
197
+ using pFrameGuard = new PoolEntryGuard(this.createPFrame(columns));
198
198
  const sortedDef = sortPTableDef(
199
199
  migrateTableFilter(
200
200
  mapPTableDef(rawDef, (c) => c.id),
201
201
  this.logger,
202
202
  ),
203
203
  );
204
- const pTableEntry = this.pTableDefs.acquire({
205
- type: "v1",
206
- def: sortedDef,
207
- pFrameHandle: pFrameGuard.key,
208
- });
204
+ const pFrameSpec = this.pFrames.getByKey(pFrameGuard.key).pFrameSpec;
205
+ using pTableGuard = new PoolEntryGuard(
206
+ this.pTableDefs.acquireFromLegacy({
207
+ pFrameHandle: pFrameGuard.key,
208
+ def: sortedDef,
209
+ pFrameSpec,
210
+ }).entry,
211
+ );
209
212
  if (logPFrames()) {
210
213
  this.logger(
211
214
  "info",
212
- `Create PTable call (pFrameHandle = ${pFrameGuard.key}; pTableHandle = ${pTableEntry.key})`,
215
+ `Create PTable call (pFrameHandle = ${pFrameGuard.key}; pTableHandle = ${pTableGuard.key})`,
213
216
  );
214
217
  }
215
218
 
216
219
  const pFrameEntry = pFrameGuard.keep();
220
+ const pTableEntry = pTableGuard.keep();
217
221
  const unref = () => {
218
222
  pTableEntry.unref();
219
223
  pFrameEntry.unref();
@@ -228,38 +232,27 @@ export class AbstractPFrameDriver<
228
232
 
229
233
  public createPTableV2(def: PTableDefV2<PColumn<PColumnData>>): PoolEntry<PTableHandle> {
230
234
  const columns = uniqueBy(collectSpecQueryColumns(def.query), (c) => c.id);
231
- const columnsMap = columns.reduce(
232
- (acc, col) => ((acc[col.id] = col.spec), acc),
233
- {} as Record<string, PColumnSpec>,
234
- );
235
-
236
235
  using pFrameGuard = new PoolEntryGuard(this.createPFrame(columns));
237
- const ValueTypes = new Set(Object.values(ValueType));
238
- const specColumnsMap = Object.fromEntries(
239
- Object.entries(columnsMap)
240
- .filter(([, spec]) => ValueTypes.has(spec.valueType))
241
- .map(([id, spec]) => [id, resolveAnnotationParents(spec)]),
242
- );
243
- const specFrame = createSpecFrame(specColumnsMap);
236
+ const pFrameSpec = this.pFrames.getByKey(pFrameGuard.key).pFrameSpec;
244
237
  const sortedQuery = sortSpecQuery(mapSpecQueryColumns(def.query, (c) => c.id));
245
- const { tableSpec, dataQuery } = specFrame.evaluateQuery(sortedQuery);
238
+ const { tableSpec, dataQuery } = pFrameSpec.evaluateQuery(sortedQuery);
246
239
 
247
- const pTableEntry = this.pTableDefs.acquire({
248
- type: "v2",
249
- pFrameHandle: pFrameGuard.key,
250
- def: {
240
+ using pTableGuard = new PoolEntryGuard(
241
+ this.pTableDefs.acquire({
242
+ pFrameHandle: pFrameGuard.key,
251
243
  tableSpec,
252
244
  dataQuery,
253
- },
254
- });
245
+ }),
246
+ );
255
247
  if (logPFrames()) {
256
248
  this.logger(
257
249
  "info",
258
- `Create PTable call (pFrameHandle = ${pFrameGuard.key}; pTableHandle = ${pTableEntry.key})`,
250
+ `Create PTable call (pFrameHandle = ${pFrameGuard.key}; pTableHandle = ${pTableGuard.key})`,
259
251
  );
260
252
  }
261
253
 
262
254
  const pFrameEntry = pFrameGuard.keep();
255
+ const pTableEntry = pTableGuard.keep();
263
256
  const unref = () => {
264
257
  pTableEntry.unref();
265
258
  pFrameEntry.unref();
@@ -293,7 +286,7 @@ export class AbstractPFrameDriver<
293
286
  return await this.tableConcurrencyLimiter.run(async () => {
294
287
  const shape = await pTable.getShape({ signal: combinedSignal });
295
288
  const clippedRange = clipRange(options.range, shape);
296
- const specs = pTable.getSpec();
289
+ const specs = def.tableSpec;
297
290
  const separator = options.format === "tsv" ? "\t" : ",";
298
291
 
299
292
  const iterable = streamPTableRows({
@@ -371,10 +364,8 @@ export class AbstractPFrameDriver<
371
364
  : [],
372
365
  };
373
366
 
374
- const { pFramePromise } = this.pFrames.getByKey(handle);
375
- const pFrame = await pFramePromise;
376
-
377
- const response = await pFrame.findColumns(iRequest);
367
+ const { pFrameSpec } = this.pFrames.getByKey(handle);
368
+ const response = pFrameSpec.findColumns(iRequest);
378
369
  return {
379
370
  hits: response.hits
380
371
  .filter(
@@ -395,15 +386,14 @@ export class AbstractPFrameDriver<
395
386
  handle: PFrameHandle,
396
387
  columnId: PObjectId,
397
388
  ): Promise<PColumnSpec | null> {
398
- const { pFramePromise } = this.pFrames.getByKey(handle);
399
- const pFrame = await pFramePromise;
400
- return await pFrame.getColumnSpec(columnId);
389
+ const { pFrameSpec } = this.pFrames.getByKey(handle);
390
+ const info = pFrameSpec.listColumns().find((c) => c.columnId === columnId);
391
+ return info?.spec ?? null;
401
392
  }
402
393
 
403
394
  public async listColumns(handle: PFrameHandle): Promise<PColumnIdAndSpec[]> {
404
- const { pFramePromise } = this.pFrames.getByKey(handle);
405
- const pFrame = await pFramePromise;
406
- return await pFrame.listColumns();
395
+ const { pFrameSpec } = this.pFrames.getByKey(handle);
396
+ return pFrameSpec.listColumns().map(({ columnId, spec }) => ({ columnId, spec }));
407
397
  }
408
398
 
409
399
  public async calculateTableData(
@@ -419,13 +409,15 @@ export class AbstractPFrameDriver<
419
409
  );
420
410
  }
421
411
 
422
- using tableGuard = new PoolEntryGuard(
423
- this.pTables.acquire({
424
- type: "v1",
425
- pFrameHandle: handle,
426
- def: sortPTableDef(migrateTableFilter(request, this.logger)),
427
- }),
428
- );
412
+ const { pFrameSpec } = this.pFrames.getByKey(handle);
413
+ const sortedDef = sortPTableDef(migrateTableFilter(request, this.logger));
414
+ const { def, entry } = this.pTables.acquireFromLegacy({
415
+ pFrameHandle: handle,
416
+ def: sortedDef,
417
+ pFrameSpec,
418
+ });
419
+ using tableGuard = new PoolEntryGuard(entry);
420
+ const tableSpec = def.tableSpec;
429
421
  const { pTablePromise, disposeSignal } = tableGuard.resource;
430
422
  const pTable = await pTablePromise;
431
423
 
@@ -434,8 +426,7 @@ export class AbstractPFrameDriver<
434
426
  // TODO: throw error when more then 150k rows is requested
435
427
  // after pf-plots migration to stream API
436
428
 
437
- const spec = pTable.getSpec();
438
- const data = await pTable.getData([...spec.keys()], {
429
+ const data = await pTable.getData([...tableSpec.keys()], {
439
430
  range,
440
431
  signal: combinedSignal,
441
432
  });
@@ -445,7 +436,7 @@ export class AbstractPFrameDriver<
445
436
  });
446
437
  this.pTableCachePerFrame.cache(tableGuard.keep(), overallSize);
447
438
 
448
- return spec.map((spec, i) => ({
439
+ return tableSpec.map((spec, i) => ({
449
440
  spec: spec,
450
441
  data: data[i],
451
442
  }));
@@ -464,12 +455,12 @@ export class AbstractPFrameDriver<
464
455
  );
465
456
  }
466
457
 
467
- const { pFramePromise, disposeSignal } = this.pFrames.getByKey(handle);
468
- const pFrame = await pFramePromise;
458
+ const { pFrameDataPromise, disposeSignal } = this.pFrames.getByKey(handle);
459
+ const pFrameData = await pFrameDataPromise;
469
460
 
470
461
  const combinedSignal = AbortSignal.any([signal, disposeSignal].filter((s) => !!s));
471
462
  return await this.frameConcurrencyLimiter.run(async () => {
472
- return await pFrame.getUniqueValues(
463
+ return await pFrameData.getUniqueValues(
473
464
  {
474
465
  ...request,
475
466
  filters: migrateFilters(request.filters, this.logger),
@@ -487,12 +478,7 @@ export class AbstractPFrameDriver<
487
478
 
488
479
  public async getSpec(handle: PTableHandle): Promise<PTableColumnSpec[]> {
489
480
  const { def } = this.pTableDefs.getByKey(handle);
490
- using table = this.pTables.acquire(def);
491
-
492
- const { pTablePromise } = table.resource;
493
- const pTable = await pTablePromise;
494
-
495
- return pTable.getSpec();
481
+ return def.tableSpec;
496
482
  }
497
483
 
498
484
  public async getShape(handle: PTableHandle, signal?: AbortSignal): Promise<PTableShape> {
@@ -5,13 +5,17 @@ import {
5
5
  ensureError,
6
6
  mapPObjectData,
7
7
  PFrameDriverError,
8
+ ValueType,
9
+ resolveAnnotationParents,
8
10
  type JsonSerializable,
9
11
  type PColumn,
12
+ type PColumnSpec,
10
13
  type PFrameHandle,
11
14
  } from "@milaboratories/pl-model-common";
12
15
  import { hashJson, PFrameInternal } from "@milaboratories/pl-model-middle-layer";
13
16
  import { RefCountPoolBase, type PoolEntry } from "@milaboratories/helpers";
14
17
  import { PFrameFactory } from "@milaboratories/pframes-rs-node";
18
+ import { createPFrame as createPFrameSpec } from "@milaboratories/pframes-rs-wasm";
15
19
  import { mapValues } from "es-toolkit";
16
20
  import { logPFrames } from "./logging";
17
21
 
@@ -26,7 +30,13 @@ export interface RemoteBlobProvider<TreeEntry extends JsonSerializable> {
26
30
  }
27
31
 
28
32
  export class PFrameHolder<TreeEntry extends JsonSerializable> implements Disposable {
29
- public readonly pFramePromise: Promise<PFrameInternal.PFrameV13>;
33
+ public readonly pFrameDataPromise: Promise<PFrameInternal.PFrameV13>;
34
+ /**
35
+ * WASM-spec frame built from this PFrame's columns. Source of truth
36
+ * for spec-side operations: column discovery, selector resolution,
37
+ * legacy-query lowering.
38
+ */
39
+ public readonly pFrameSpec: PFrameInternal.PFrameWasmV3;
30
40
  private readonly abortController = new AbortController();
31
41
 
32
42
  private readonly localBlobs: PoolEntry<PFrameInternal.PFrameBlobId>[] = [];
@@ -40,86 +50,114 @@ export class PFrameHolder<TreeEntry extends JsonSerializable> implements Disposa
40
50
  private readonly spillPath: string,
41
51
  columns: PColumn<PFrameInternal.DataInfo<TreeEntry>>[],
42
52
  ) {
43
- const makeLocalBlobId = (blob: TreeEntry): PFrameInternal.PFrameBlobId => {
44
- const localBlob = this.localBlobProvider.acquire(blob);
45
- this.localBlobs.push(localBlob);
46
- return localBlob.key;
47
- };
53
+ const ValueTypes = new Set(Object.values(ValueType));
54
+ const specColumnsMap: Record<string, PColumnSpec> = {};
55
+ for (const c of columns) {
56
+ if (ValueTypes.has(c.spec.valueType)) {
57
+ specColumnsMap[c.id] = resolveAnnotationParents(c.spec);
58
+ }
59
+ }
60
+ this.pFrameSpec = createPFrameSpec(specColumnsMap);
48
61
 
49
- const makeRemoteBlobId = (blob: TreeEntry): PFrameInternal.PFrameBlobId => {
50
- const remoteBlob = this.remoteBlobProvider.acquire(blob);
51
- this.remoteBlobs.push(remoteBlob);
52
- return `${remoteBlob.key}${PFrameInternal.ParquetExtension}` as PFrameInternal.PFrameBlobId;
53
- };
62
+ try {
63
+ const makeLocalBlobId = (blob: TreeEntry): PFrameInternal.PFrameBlobId => {
64
+ const localBlob = this.localBlobProvider.acquire(blob);
65
+ this.localBlobs.push(localBlob);
66
+ return localBlob.key;
67
+ };
54
68
 
55
- const mapColumnData = (
56
- data: PFrameInternal.DataInfo<TreeEntry>,
57
- ): PFrameInternal.DataInfo<PFrameInternal.PFrameBlobId> => {
58
- switch (data.type) {
59
- case "Json":
60
- return { ...data };
61
- case "JsonPartitioned":
62
- return {
63
- ...data,
64
- parts: mapValues(data.parts, makeLocalBlobId),
65
- };
66
- case "BinaryPartitioned":
67
- return {
68
- ...data,
69
- parts: mapValues(data.parts, (v) => ({
70
- index: makeLocalBlobId(v.index),
71
- values: makeLocalBlobId(v.values),
72
- })),
73
- };
74
- case "ParquetPartitioned":
75
- return {
76
- ...data,
77
- parts: mapValues(data.parts, (v) => ({
78
- ...v,
79
- data: makeRemoteBlobId(v.data),
80
- })),
81
- };
82
- default:
83
- assertNever(data);
84
- }
85
- };
69
+ const makeRemoteBlobId = (blob: TreeEntry): PFrameInternal.PFrameBlobId => {
70
+ const remoteBlob = this.remoteBlobProvider.acquire(blob);
71
+ this.remoteBlobs.push(remoteBlob);
72
+ return `${remoteBlob.key}${PFrameInternal.ParquetExtension}` as PFrameInternal.PFrameBlobId;
73
+ };
86
74
 
87
- const jsonifiedColumns = columns.map((column) => ({
88
- ...column,
89
- data: mapColumnData(column.data),
90
- }));
75
+ const mapColumnData = (
76
+ data: PFrameInternal.DataInfo<TreeEntry>,
77
+ ): PFrameInternal.DataInfo<PFrameInternal.PFrameBlobId> => {
78
+ switch (data.type) {
79
+ case "Json":
80
+ return { ...data };
81
+ case "JsonPartitioned":
82
+ return {
83
+ ...data,
84
+ parts: mapValues(data.parts, makeLocalBlobId),
85
+ };
86
+ case "BinaryPartitioned":
87
+ return {
88
+ ...data,
89
+ parts: mapValues(data.parts, (v) => ({
90
+ index: makeLocalBlobId(v.index),
91
+ values: makeLocalBlobId(v.values),
92
+ })),
93
+ };
94
+ case "ParquetPartitioned":
95
+ return {
96
+ ...data,
97
+ parts: mapValues(data.parts, (v) => ({
98
+ ...v,
99
+ data: makeRemoteBlobId(v.data),
100
+ })),
101
+ };
102
+ default:
103
+ assertNever(data);
104
+ }
105
+ };
91
106
 
92
- try {
93
- const pFrame = PFrameFactory.createPFrame({ frameId, spillPath: this.spillPath, logger });
94
- pFrame.setDataSource({
95
- ...this.localBlobProvider.makeDataSource(this.disposeSignal),
96
- parquetServer: this.remoteBlobProvider.httpServerInfo(),
97
- });
107
+ const jsonifiedColumns = columns.map((column) => ({
108
+ ...column,
109
+ data: mapColumnData(column.data),
110
+ }));
98
111
 
112
+ const pFrameData = PFrameFactory.createPFrame({ frameId, spillPath: this.spillPath, logger });
99
113
  const promises: Promise<void>[] = [];
100
- for (const column of jsonifiedColumns) {
101
- pFrame.addColumnSpec(column.id, column.spec);
102
- promises.push(pFrame.setColumnData(column.id, column.data, { signal: this.disposeSignal }));
103
- }
114
+ try {
115
+ pFrameData.setDataSource({
116
+ ...this.localBlobProvider.makeDataSource(this.disposeSignal),
117
+ parquetServer: this.remoteBlobProvider.httpServerInfo(),
118
+ });
104
119
 
105
- this.pFramePromise = Promise.all(promises)
106
- .then(() => pFrame)
107
- .catch((err) => {
108
- this.dispose();
109
- pFrame.dispose();
110
- const error = new PFrameDriverError("PFrame creation failed asynchronously");
111
- error.cause = new Error(
112
- `PFrame cannot be created from columns: ${JSON.stringify(jsonifiedColumns)}`,
113
- { cause: ensureError(err) },
120
+ for (const column of jsonifiedColumns) {
121
+ pFrameData.addColumnSpec(column.id, column.spec);
122
+ promises.push(
123
+ pFrameData.setColumnData(column.id, column.data, { signal: this.disposeSignal }),
114
124
  );
115
- throw error;
116
- });
125
+ }
126
+
127
+ this.pFrameDataPromise = Promise.all(promises)
128
+ .then(() => pFrameData)
129
+ .catch((err) => {
130
+ this.dispose();
131
+ pFrameData.dispose();
132
+ const error = new PFrameDriverError("PFrame creation failed asynchronously");
133
+ error.cause = new Error(
134
+ `PFrame cannot be created from columns: ${JSON.stringify(jsonifiedColumns)}`,
135
+ { cause: ensureError(err) },
136
+ );
137
+ throw error;
138
+ });
139
+ } catch (err: unknown) {
140
+ // setDataSource / addColumnSpec / setColumnData threw synchronously
141
+ // before pFrameDataPromise was assigned — dispose the addon's
142
+ // pFrameData explicitly (the dispose() path can't reach it yet).
143
+ // `allSettled` attaches handlers to any in-flight setColumnData
144
+ // promises so they don't trigger unhandled-rejection warnings
145
+ // when the dispose below rejects them.
146
+ void Promise.allSettled(promises);
147
+ pFrameData.dispose();
148
+ throw err;
149
+ }
117
150
  } catch (err: unknown) {
151
+ // Release everything allocated so far in this constructor:
152
+ // acquired blobs, the WASM spec frame, and the abort signal.
153
+ this.abortController.abort();
154
+ this.localBlobs.forEach((entry) => entry.unref());
155
+ this.remoteBlobs.forEach((entry) => entry.unref());
156
+ this.pFrameSpec[Symbol.dispose]();
118
157
  const error = new PFrameDriverError("PFrame creation failed synchronously");
119
- error.cause = new Error(
120
- `PFrame cannot be created from columns: ${JSON.stringify(jsonifiedColumns)}`,
121
- { cause: ensureError(err) },
122
- );
158
+ error.cause = new Error(`PFrame cannot be created from columns: ${JSON.stringify(columns)}`, {
159
+ cause: ensureError(err),
160
+ });
123
161
  throw error;
124
162
  }
125
163
  }
@@ -132,15 +170,16 @@ export class PFrameHolder<TreeEntry extends JsonSerializable> implements Disposa
132
170
  this.abortController.abort();
133
171
  this.localBlobs.forEach((entry) => entry.unref());
134
172
  this.remoteBlobs.forEach((entry) => entry.unref());
173
+ this.pFrameSpec[Symbol.dispose]();
174
+ void this.pFrameDataPromise
175
+ .then((pFrameData) => pFrameData.dispose())
176
+ .catch(() => {
177
+ /* mute error */
178
+ });
135
179
  }
136
180
 
137
181
  [Symbol.dispose](): void {
138
182
  this.dispose();
139
- void this.pFramePromise
140
- .then((pFrame) => pFrame.dispose())
141
- .catch(() => {
142
- /* mute error */
143
- });
144
183
  }
145
184
  }
146
185
 
@@ -1,9 +1,15 @@
1
- import { PFrameDriverError, type PTableHandle } from "@milaboratories/pl-model-common";
1
+ import {
2
+ PFrameDriverError,
3
+ type PFrameHandle,
4
+ type PObjectId,
5
+ type PTableDef,
6
+ type PTableHandle,
7
+ } from "@milaboratories/pl-model-common";
2
8
  import type { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
3
- import { RefCountPoolBase } from "@milaboratories/helpers";
9
+ import { RefCountPoolBase, type PoolEntry } from "@milaboratories/helpers";
4
10
  import { logPFrames } from "./logging";
5
11
  import type { FullPTableDef } from "./ptable_shared";
6
- import { stableKeyFromFullPTableDef } from "./ptable_shared";
12
+ import { buildFullPTableDefFromLegacy, stableKeyFromFullPTableDef } from "./ptable_shared";
7
13
 
8
14
  export class PTableDefHolder implements Disposable {
9
15
  private readonly abortController = new AbortController();
@@ -52,4 +58,21 @@ export class PTableDefPool extends RefCountPoolBase<FullPTableDef, PTableHandle,
52
58
  }
53
59
  return resource;
54
60
  }
61
+
62
+ /**
63
+ * Acquire a def from a legacy `PTableDef` + column specs map.
64
+ * Lowers the input via WASM-spec and stores the resulting
65
+ * `{ tableSpec, dataQuery }` shape. Returns the lowered def
66
+ * alongside the pool entry — callers that need `tableSpec`
67
+ * (e.g. `calculateTableData`) read it from `def.tableSpec`
68
+ * without re-deriving.
69
+ */
70
+ public acquireFromLegacy(opts: {
71
+ pFrameHandle: PFrameHandle;
72
+ def: PTableDef<PObjectId>;
73
+ pFrameSpec: PFrameInternal.PFrameWasmV3;
74
+ }): { def: FullPTableDef; entry: PoolEntry<PTableHandle> } {
75
+ const def = buildFullPTableDefFromLegacy(opts);
76
+ return { def, entry: this.acquire(def) };
77
+ }
55
78
  }