@gscdump/engine 7.5.1 → 7.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 +13 -0
- package/dist/adapters/hyparquet.d.mts +7 -0
- package/dist/adapters/hyparquet.mjs +33 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -42,6 +42,19 @@ options produces identical bytes regardless of producer batch boundaries.
|
|
|
42
42
|
This supports a counting/digest pass followed by a separately coordinated
|
|
43
43
|
fixed-length upload, without the codec owning transport or commit semantics.
|
|
44
44
|
|
|
45
|
+
The streaming encoder normally refuses when retained footer/index descriptors
|
|
46
|
+
exceed `limits.maxRetainedMetadataBytes` (8 MiB by default). For wide values,
|
|
47
|
+
callers can explicitly choose `metadataOverflow: 'omit-statistics'`. On the
|
|
48
|
+
first overflow it omits column-chunk statistics and ColumnIndexes throughout
|
|
49
|
+
that file, including earlier groups. OffsetIndexes, encoding descriptors,
|
|
50
|
+
schema, row counts, data/dictionary pages and explicit `fileMetadata` remain
|
|
51
|
+
unchanged. Required descriptors must still fit the same budget, and the
|
|
52
|
+
group/footer output-buffer limit still applies. Missing statistics mean unknown
|
|
53
|
+
bounds, so readers may scan more pages or groups; they do not mean empty data.
|
|
54
|
+
If no overflow occurs, this option preserves the normal bytes. It bounds
|
|
55
|
+
retained optional descriptors, not their transient construction or total heap.
|
|
56
|
+
The array encoders and all omitted-option behavior remain unchanged.
|
|
57
|
+
|
|
45
58
|
`decodeParquetRowGroups({ byteLength, slice }, options)` reads exact byte ranges
|
|
46
59
|
and by default yields one physical row group at a time. The caller must pin every range
|
|
47
60
|
to the same immutable object or ETag. It preserves the existing date, column
|
|
@@ -29,6 +29,13 @@ export interface EncodeParquetBatchesOptions extends EncodeParquetOptions {
|
|
|
29
29
|
columns: readonly ColumnDef[];
|
|
30
30
|
/** Maximum rows per output group. Default 25000, matching the array codec. */
|
|
31
31
|
rowGroupSize?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Opt in to omitting all footer column statistics and ColumnIndexes if they
|
|
34
|
+
* exceed the retained metadata limit. Default: refuse. OffsetIndexes,
|
|
35
|
+
* required descriptors and explicit file metadata remain bounded/preserved.
|
|
36
|
+
* Missing statistics reduce pruning; this does not bound transient codec heap.
|
|
37
|
+
*/
|
|
38
|
+
metadataOverflow?: 'omit-statistics';
|
|
32
39
|
limits?: {
|
|
33
40
|
/** Refuse a producer batch with more rows. Default 25000. */
|
|
34
41
|
maxBatchRows?: number;
|
|
@@ -219,6 +219,11 @@ function streamLimit(value, fallback, name) {
|
|
|
219
219
|
function scalarBytes(value) {
|
|
220
220
|
return typeof value === "string" ? value.length * 3 + 8 : 16;
|
|
221
221
|
}
|
|
222
|
+
var RetainedMetadataLimitError = class extends RangeError {
|
|
223
|
+
constructor() {
|
|
224
|
+
super("parquet stream exceeds maxRetainedMetadataBytes");
|
|
225
|
+
}
|
|
226
|
+
};
|
|
222
227
|
function retainedDescriptorBytes(value, remaining) {
|
|
223
228
|
const seen = /* @__PURE__ */ new Set();
|
|
224
229
|
let bytes = 0;
|
|
@@ -233,12 +238,14 @@ function retainedDescriptorBytes(value, remaining) {
|
|
|
233
238
|
visit(value[key]);
|
|
234
239
|
}
|
|
235
240
|
} else bytes += scalarBytes(value);
|
|
236
|
-
if (bytes > remaining) throw new
|
|
241
|
+
if (bytes > remaining) throw new RetainedMetadataLimitError();
|
|
237
242
|
};
|
|
238
243
|
visit(value);
|
|
239
244
|
return bytes;
|
|
240
245
|
}
|
|
241
246
|
async function encodeParquetRowBatches(batches, opts, write) {
|
|
247
|
+
const metadataOverflow = opts.metadataOverflow;
|
|
248
|
+
if (metadataOverflow !== void 0 && metadataOverflow !== "omit-statistics") throw new RangeError("parquet stream metadataOverflow must be omit-statistics when provided");
|
|
242
249
|
const rowGroupSize = streamLimit(opts.rowGroupSize, ROW_GROUP_SIZE, "rowGroupSize");
|
|
243
250
|
const maxBatchRows = streamLimit(opts.limits?.maxBatchRows, ROW_GROUP_SIZE, "maxBatchRows");
|
|
244
251
|
const maxBatchBytes = streamLimit(opts.limits?.maxBatchBytes, 8388608, "maxBatchBytes");
|
|
@@ -260,7 +267,8 @@ async function encodeParquetRowBatches(batches, opts, write) {
|
|
|
260
267
|
metadataBytes += scalarBytes(key) + scalarBytes(opts.fileMetadata[key]);
|
|
261
268
|
if (metadataBytes > maxOutputBufferBytes) throw new RangeError("parquet stream file metadata exceeds maxOutputBufferBytes");
|
|
262
269
|
}
|
|
263
|
-
|
|
270
|
+
const initialMetadataBytes = retainedDescriptorBytes([schema, opts.fileMetadata], maxRetainedMetadataBytes);
|
|
271
|
+
let retainedMetadataBytes = initialMetadataBytes;
|
|
264
272
|
const writer = new ByteWriter(Math.min(65536, maxOutputBufferBytes));
|
|
265
273
|
writer.ensure = (size) => {
|
|
266
274
|
const required = writer.index + size;
|
|
@@ -293,6 +301,14 @@ async function encodeParquetRowBatches(batches, opts, write) {
|
|
|
293
301
|
let groupBytes = 0;
|
|
294
302
|
let groups = 0;
|
|
295
303
|
let rowCount = 0;
|
|
304
|
+
let omitStatistics = false;
|
|
305
|
+
const omitOptionalMetadata = (firstIndex) => {
|
|
306
|
+
for (let index = firstIndex; index < parquet.pendingIndexes.length; index++) {
|
|
307
|
+
const pending = parquet.pendingIndexes[index];
|
|
308
|
+
if (pending.chunk.meta_data) delete pending.chunk.meta_data.statistics;
|
|
309
|
+
delete pending.columnIndex;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
296
312
|
const flushGroup = async () => {
|
|
297
313
|
if (!groupRows) return;
|
|
298
314
|
if (++groups > maxRowGroups) throw new RangeError("parquet stream exceeds maxRowGroups");
|
|
@@ -306,7 +322,21 @@ async function encodeParquetRowBatches(batches, opts, write) {
|
|
|
306
322
|
})),
|
|
307
323
|
rowGroupSize: groupRows
|
|
308
324
|
});
|
|
309
|
-
|
|
325
|
+
if (omitStatistics) omitOptionalMetadata(firstIndex);
|
|
326
|
+
try {
|
|
327
|
+
retainedMetadataBytes += retainedDescriptorBytes([parquet.row_groups[parquet.row_groups.length - 1], parquet.pendingIndexes.slice(firstIndex)], maxRetainedMetadataBytes - retainedMetadataBytes);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (!(error instanceof RetainedMetadataLimitError) || metadataOverflow !== "omit-statistics" || omitStatistics) throw error;
|
|
330
|
+
omitStatistics = true;
|
|
331
|
+
omitOptionalMetadata(0);
|
|
332
|
+
retainedMetadataBytes = initialMetadataBytes;
|
|
333
|
+
let index = 0;
|
|
334
|
+
for (const group of parquet.row_groups) {
|
|
335
|
+
const end = index + group.columns.length;
|
|
336
|
+
retainedMetadataBytes += retainedDescriptorBytes([group, parquet.pendingIndexes.slice(index, end)], maxRetainedMetadataBytes - retainedMetadataBytes);
|
|
337
|
+
index = end;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
310
340
|
values = codecs.map(() => []);
|
|
311
341
|
groupRows = 0;
|
|
312
342
|
groupBytes = 0;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/engine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.6.0",
|
|
5
5
|
"description": "Append-only Parquet/DuckDB storage engine + planner + adapters for the gscdump pipeline. Node + edge runtimes; opt-in heavy peers.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -189,10 +189,10 @@
|
|
|
189
189
|
}
|
|
190
190
|
},
|
|
191
191
|
"dependencies": {
|
|
192
|
-
"@gscdump/contracts": "^7.
|
|
193
|
-
"@gscdump/lakehouse": "^7.
|
|
192
|
+
"@gscdump/contracts": "^7.6.0",
|
|
193
|
+
"@gscdump/lakehouse": "^7.6.0",
|
|
194
194
|
"drizzle-orm": "1.0.0-rc.4",
|
|
195
|
-
"gscdump": "^7.
|
|
195
|
+
"gscdump": "^7.6.0",
|
|
196
196
|
"pathe": "^2.0.3",
|
|
197
197
|
"proper-lockfile": "^4.1.2"
|
|
198
198
|
},
|