@gscdump/engine 7.3.2 → 7.4.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 +29 -0
- package/dist/adapters/hyparquet.d.mts +57 -1
- package/dist/adapters/hyparquet.mjs +175 -3
- package/dist/node_modules/.pnpm/hyparquet-writer@0.16.10_patch_hash_d26d002aede61ad95dd633d6557b56897cdcbc45dfc3a5d93fa0e9075ad5425f/node_modules/hyparquet-writer/src/index.mjs +2 -2
- package/dist/node_modules/.pnpm/hyparquet-writer@0.16.10_patch_hash_d26d002aede61ad95dd633d6557b56897cdcbc45dfc3a5d93fa0e9075ad5425f/node_modules/hyparquet-writer/src/node.mjs +2 -2
- package/dist/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/index.mjs +2 -2
- package/dist/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/node.mjs +2 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -30,6 +30,35 @@ different publications produce different bytes. It must retain and replay the
|
|
|
30
30
|
exact encoded bytes for retries; this option does not provide publication or
|
|
31
31
|
recovery coordination itself.
|
|
32
32
|
|
|
33
|
+
For a bounded, already ordered source, `/hyparquet` also exports
|
|
34
|
+
`encodeParquetRowBatches(batches, options, write)`. It consumes sync or async
|
|
35
|
+
batches, coerces the declared `columns` with the same physical types as the
|
|
36
|
+
array encoder, and emits one Parquet file through an awaited byte callback.
|
|
37
|
+
It does not sort, deduplicate, retry, or publish an object. The callback borrows
|
|
38
|
+
its bytes until its promise settles; copy a chunk if retaining it. The caller
|
|
39
|
+
must abort/discard the entire destination on any error, including an error
|
|
40
|
+
while writing the footer. Reopening the same immutable source with identical
|
|
41
|
+
options produces identical bytes regardless of producer batch boundaries.
|
|
42
|
+
This supports a counting/digest pass followed by a separately coordinated
|
|
43
|
+
fixed-length upload, without the codec owning transport or commit semantics.
|
|
44
|
+
|
|
45
|
+
`decodeParquetRowGroups({ byteLength, slice }, options)` reads exact byte ranges
|
|
46
|
+
and yields one physical row group at a time. The caller must pin every range
|
|
47
|
+
to the same immutable object or ETag. It preserves the existing date, column
|
|
48
|
+
projection, row-window and filter behavior. An absent, corrupt, oversized or
|
|
49
|
+
incompletely read file throws; only valid empty Parquet yields no rows.
|
|
50
|
+
|
|
51
|
+
Both APIs have configurable admission limits. The encoder bounds producer
|
|
52
|
+
batch rows/value bytes, output group rows/value bytes, retained descriptor counts
|
|
53
|
+
and scalar bytes (including statistics), and the current encoded group/footer
|
|
54
|
+
buffer. The decoder checks footer size,
|
|
55
|
+
descriptor count, declared group rows/uncompressed bytes, and every range request.
|
|
56
|
+
The default decoder accepts the existing encoder's 25,000-row groups. These
|
|
57
|
+
limits do not establish a Worker heap guarantee: caller-retained rows, codec
|
|
58
|
+
transient allocations, footer descriptors, and concurrent requests still use
|
|
59
|
+
memory. Metadata limits are not protection against malicious Parquet contents.
|
|
60
|
+
The existing array APIs and their default bytes are unchanged.
|
|
61
|
+
|
|
33
62
|
## Subpath exports
|
|
34
63
|
|
|
35
64
|
| Subpath | Purpose |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParquetQueryFilter } from "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/types/index.mjs";
|
|
2
2
|
import "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/types/node.mjs";
|
|
3
|
-
import { CodecCtx, DataSource, ParquetCodec, Row, TableName } from "../storage.mjs";
|
|
3
|
+
import { CodecCtx, DataSource, ParquetCodec, Row, TableName, WriteResult } from "../storage.mjs";
|
|
4
4
|
import { ColumnDef } from "../schema.mjs";
|
|
5
5
|
export interface EncodeParquetOptions {
|
|
6
6
|
/** File footer metadata, without adding fact columns. Keys are encoded in sorted order. */
|
|
@@ -23,6 +23,36 @@ export interface EncodeFlexOptions extends EncodeParquetOptions {
|
|
|
23
23
|
* merges cleanly with fact-table reads.
|
|
24
24
|
*/
|
|
25
25
|
export declare function encodeRowsToParquetFlex(rows: readonly Row[], opts: EncodeFlexOptions): Uint8Array;
|
|
26
|
+
export interface EncodeParquetBatchesOptions extends EncodeParquetOptions {
|
|
27
|
+
/** Output schema and column order. Input rows must already be ordered/deduplicated. */
|
|
28
|
+
columns: readonly ColumnDef[];
|
|
29
|
+
/** Maximum rows per output group. Default 25000, matching the array codec. */
|
|
30
|
+
rowGroupSize?: number;
|
|
31
|
+
limits?: {
|
|
32
|
+
/** Refuse a producer batch with more rows. Default 25000. */
|
|
33
|
+
maxBatchRows?: number;
|
|
34
|
+
/** Conservative scalar-value bytes per producer batch. Default 8 MiB. */
|
|
35
|
+
maxBatchBytes?: number;
|
|
36
|
+
/** Split groups at this conservative scalar-value budget. Default 2 MiB. */
|
|
37
|
+
maxRowGroupBytes?: number;
|
|
38
|
+
/** Bound retained footer/index descriptors. Default 4096. */
|
|
39
|
+
maxRowGroups?: number;
|
|
40
|
+
/** Bound retained descriptor/scalar bytes, including statistics. Default 8 MiB. */
|
|
41
|
+
maxRetainedMetadataBytes?: number;
|
|
42
|
+
/** Bound the current encoded group or footer buffer. Default 8 MiB. */
|
|
43
|
+
maxOutputBufferBytes?: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Encode ordered batches into ONE Parquet file with sequential backpressure.
|
|
48
|
+
* Group boundaries depend on rows/options, never producer batch boundaries.
|
|
49
|
+
* `write` borrows its bytes only until its returned promise settles; copy them
|
|
50
|
+
* if retaining them. This function does not close/commit a destination. On ANY
|
|
51
|
+
* rejection the caller must abort/discard the entire partial output, including
|
|
52
|
+
* after a footer callback failure. It performs no sorting, dedupe or retries.
|
|
53
|
+
* Memory includes one input batch, one group, and bounded footer descriptors.
|
|
54
|
+
*/
|
|
55
|
+
export declare function encodeParquetRowBatches(batches: AsyncIterable<readonly Row[]> | Iterable<readonly Row[]>, opts: EncodeParquetBatchesOptions, write: (chunk: Uint8Array) => Promise<void>): Promise<WriteResult>;
|
|
26
56
|
export interface DecodeParquetOptions {
|
|
27
57
|
/** First physical row to decode, inclusive. */
|
|
28
58
|
rowStart?: number;
|
|
@@ -49,6 +79,32 @@ export interface DecodeParquetOptions {
|
|
|
49
79
|
columns?: readonly string[];
|
|
50
80
|
}
|
|
51
81
|
export declare function decodeParquetToRows(bytes: Uint8Array, opts?: DecodeParquetOptions): Promise<Row[]>;
|
|
82
|
+
/** A caller-owned, immutable/pinned object; ranges must return exactly the requested bytes. */
|
|
83
|
+
export interface ParquetRangeSource {
|
|
84
|
+
byteLength: number;
|
|
85
|
+
slice: (start: number, end?: number) => ArrayBuffer | Promise<ArrayBuffer>;
|
|
86
|
+
}
|
|
87
|
+
export interface DecodeParquetGroupsOptions extends DecodeParquetOptions {
|
|
88
|
+
limits?: {
|
|
89
|
+
/** Footer bytes before deserialization. Default 1 MiB. */
|
|
90
|
+
maxMetadataBytes?: number;
|
|
91
|
+
/** Includes the existing encoder's 25000-row groups. Default 25000. */
|
|
92
|
+
maxRowGroupRows?: number;
|
|
93
|
+
/** Declared uncompressed bytes across all columns in one group. Default 16 MiB. */
|
|
94
|
+
maxRowGroupBytes?: number;
|
|
95
|
+
/** Bound metadata descriptors. Default 4096. */
|
|
96
|
+
maxRowGroups?: number;
|
|
97
|
+
/** Maximum individual range request. Default 16 MiB. */
|
|
98
|
+
maxReadBytes?: number;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Pull one physical group at a time without downloading the complete object.
|
|
103
|
+
* The limits are admission bounds over Parquet metadata, not a memory-safety
|
|
104
|
+
* guarantee for malicious files or caller-retained batches. No cross-group
|
|
105
|
+
* read-ahead is started. Missing/corrupt/range-error input throws; it is not empty.
|
|
106
|
+
*/
|
|
107
|
+
export declare function decodeParquetRowGroups(file: ParquetRangeSource, opts?: DecodeParquetGroupsOptions): AsyncGenerator<Row[]>;
|
|
52
108
|
export interface HyparquetCodecOptions {
|
|
53
109
|
/**
|
|
54
110
|
* Override `readRows`. Useful when reads should be delegated to a faster
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { TABLE_METADATA } from "../drizzle-schema.mjs";
|
|
2
2
|
import { SCHEMAS } from "../schema.mjs";
|
|
3
3
|
import { decompress } from "../node_modules/.pnpm/fzstd@0.1.1/node_modules/fzstd/esm/index.mjs";
|
|
4
|
-
import { parquetMetadataAsync, parquetSchema } from "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/metadata.mjs";
|
|
4
|
+
import { parquetMetadata, parquetMetadataAsync, parquetSchema } from "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/metadata.mjs";
|
|
5
5
|
import { parquetReadObjects } from "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/read.mjs";
|
|
6
6
|
import "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/node.mjs";
|
|
7
7
|
import { ByteWriter } from "../node_modules/.pnpm/hyparquet-writer@0.16.10_patch_hash_d26d002aede61ad95dd633d6557b56897cdcbc45dfc3a5d93fa0e9075ad5425f/node_modules/hyparquet-writer/src/bytewriter.mjs";
|
|
8
|
+
import { ParquetWriter } from "../node_modules/.pnpm/hyparquet-writer@0.16.10_patch_hash_d26d002aede61ad95dd633d6557b56897cdcbc45dfc3a5d93fa0e9075ad5425f/node_modules/hyparquet-writer/src/parquet-writer.mjs";
|
|
8
9
|
import { parquetWriteRows } from "../node_modules/.pnpm/hyparquet-writer@0.16.10_patch_hash_d26d002aede61ad95dd633d6557b56897cdcbc45dfc3a5d93fa0e9075ad5425f/node_modules/hyparquet-writer/src/write-rows.mjs";
|
|
9
10
|
import "../node_modules/.pnpm/hyparquet-writer@0.16.10_patch_hash_d26d002aede61ad95dd633d6557b56897cdcbc45dfc3a5d93fa0e9075ad5425f/node_modules/hyparquet-writer/src/node.mjs";
|
|
10
11
|
const ROW_GROUP_SIZE = 25e3;
|
|
@@ -206,6 +207,139 @@ function encodeRowsToParquetFlex(rows, opts) {
|
|
|
206
207
|
return 0;
|
|
207
208
|
}), columns, rowGroupSize, opts.fileMetadata);
|
|
208
209
|
}
|
|
210
|
+
function streamLimit(value, fallback, name) {
|
|
211
|
+
const limit = value ?? fallback;
|
|
212
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) throw new RangeError(`parquet stream ${name} must be a positive safe integer`);
|
|
213
|
+
return limit;
|
|
214
|
+
}
|
|
215
|
+
function scalarBytes(value) {
|
|
216
|
+
return typeof value === "string" ? value.length * 3 + 8 : 16;
|
|
217
|
+
}
|
|
218
|
+
function retainedDescriptorBytes(value, remaining) {
|
|
219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
220
|
+
let bytes = 0;
|
|
221
|
+
const visit = (value) => {
|
|
222
|
+
if (value !== null && typeof value === "object") {
|
|
223
|
+
if (seen.has(value)) return;
|
|
224
|
+
seen.add(value);
|
|
225
|
+
bytes += 64;
|
|
226
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) bytes += value.byteLength;
|
|
227
|
+
else for (const key in value) if (Object.hasOwn(value, key)) {
|
|
228
|
+
bytes += scalarBytes(key);
|
|
229
|
+
visit(value[key]);
|
|
230
|
+
}
|
|
231
|
+
} else bytes += scalarBytes(value);
|
|
232
|
+
if (bytes > remaining) throw new RangeError("parquet stream exceeds maxRetainedMetadataBytes");
|
|
233
|
+
};
|
|
234
|
+
visit(value);
|
|
235
|
+
return bytes;
|
|
236
|
+
}
|
|
237
|
+
async function encodeParquetRowBatches(batches, opts, write) {
|
|
238
|
+
const rowGroupSize = streamLimit(opts.rowGroupSize, ROW_GROUP_SIZE, "rowGroupSize");
|
|
239
|
+
const maxBatchRows = streamLimit(opts.limits?.maxBatchRows, ROW_GROUP_SIZE, "maxBatchRows");
|
|
240
|
+
const maxBatchBytes = streamLimit(opts.limits?.maxBatchBytes, 8388608, "maxBatchBytes");
|
|
241
|
+
const maxRowGroupBytes = streamLimit(opts.limits?.maxRowGroupBytes, 2097152, "maxRowGroupBytes");
|
|
242
|
+
const maxRowGroups = streamLimit(opts.limits?.maxRowGroups, 4096, "maxRowGroups");
|
|
243
|
+
const maxRetainedMetadataBytes = streamLimit(opts.limits?.maxRetainedMetadataBytes, 8388608, "maxRetainedMetadataBytes");
|
|
244
|
+
const maxOutputBufferBytes = streamLimit(opts.limits?.maxOutputBufferBytes, 8388608, "maxOutputBufferBytes");
|
|
245
|
+
if (opts.columns.length === 0 || opts.columns.length > 64 || new Set(opts.columns.map((col) => col.name)).size !== opts.columns.length) throw new RangeError("parquet stream requires 1 to 64 uniquely named columns");
|
|
246
|
+
const codecs = opts.columns.map((col) => ({
|
|
247
|
+
name: col.name,
|
|
248
|
+
isDate: col.type === "DATE",
|
|
249
|
+
type: basicTypeFor(col.type),
|
|
250
|
+
nullable: col.nullable
|
|
251
|
+
}));
|
|
252
|
+
const schema = buildWriteSchema(opts.columns);
|
|
253
|
+
const metadataKeys = Object.keys(opts.fileMetadata ?? {}).sort();
|
|
254
|
+
let metadataBytes = 0;
|
|
255
|
+
for (const key of metadataKeys) {
|
|
256
|
+
metadataBytes += scalarBytes(key) + scalarBytes(opts.fileMetadata[key]);
|
|
257
|
+
if (metadataBytes > maxOutputBufferBytes) throw new RangeError("parquet stream file metadata exceeds maxOutputBufferBytes");
|
|
258
|
+
}
|
|
259
|
+
let retainedMetadataBytes = retainedDescriptorBytes([schema, opts.fileMetadata], maxRetainedMetadataBytes);
|
|
260
|
+
const writer = new ByteWriter(Math.min(65536, maxOutputBufferBytes));
|
|
261
|
+
writer.ensure = (size) => {
|
|
262
|
+
const required = writer.index + size;
|
|
263
|
+
if (required > maxOutputBufferBytes) throw new RangeError("parquet stream encoded group/footer exceeds maxOutputBufferBytes");
|
|
264
|
+
if (required > writer.buffer.byteLength) {
|
|
265
|
+
const next = new ArrayBuffer(Math.min(maxOutputBufferBytes, Math.max(required, writer.buffer.byteLength * 2)));
|
|
266
|
+
new Uint8Array(next).set(new Uint8Array(writer.buffer, 0, writer.index));
|
|
267
|
+
writer.buffer = next;
|
|
268
|
+
writer.view = new DataView(next);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const flush = async () => {
|
|
272
|
+
if (writer.index > 0) await write(new Uint8Array(writer.buffer, 0, writer.index));
|
|
273
|
+
writer.index = 0;
|
|
274
|
+
};
|
|
275
|
+
const output = Object.assign(writer, {
|
|
276
|
+
flush,
|
|
277
|
+
finish: flush
|
|
278
|
+
});
|
|
279
|
+
const parquet = new ParquetWriter({
|
|
280
|
+
writer: output,
|
|
281
|
+
schema,
|
|
282
|
+
kvMetadata: metadataKeys.length ? metadataKeys.map((key) => ({
|
|
283
|
+
key,
|
|
284
|
+
value: opts.fileMetadata[key]
|
|
285
|
+
})) : void 0
|
|
286
|
+
});
|
|
287
|
+
let values = codecs.map(() => []);
|
|
288
|
+
let groupRows = 0;
|
|
289
|
+
let groupBytes = 0;
|
|
290
|
+
let groups = 0;
|
|
291
|
+
let rowCount = 0;
|
|
292
|
+
const flushGroup = async () => {
|
|
293
|
+
if (!groupRows) return;
|
|
294
|
+
if (++groups > maxRowGroups) throw new RangeError("parquet stream exceeds maxRowGroups");
|
|
295
|
+
const firstIndex = parquet.pendingIndexes.length;
|
|
296
|
+
await parquet.write({
|
|
297
|
+
columnData: codecs.map((col, index) => ({
|
|
298
|
+
name: col.name,
|
|
299
|
+
nullable: col.nullable,
|
|
300
|
+
columnIndex: true,
|
|
301
|
+
data: values[index]
|
|
302
|
+
})),
|
|
303
|
+
rowGroupSize: groupRows
|
|
304
|
+
});
|
|
305
|
+
retainedMetadataBytes += retainedDescriptorBytes([parquet.row_groups[parquet.row_groups.length - 1], parquet.pendingIndexes.slice(firstIndex)], maxRetainedMetadataBytes - retainedMetadataBytes);
|
|
306
|
+
values = codecs.map(() => []);
|
|
307
|
+
groupRows = 0;
|
|
308
|
+
groupBytes = 0;
|
|
309
|
+
};
|
|
310
|
+
try {
|
|
311
|
+
for await (const batch of batches) {
|
|
312
|
+
if (batch.length > maxBatchRows) throw new RangeError("parquet stream exceeds maxBatchRows");
|
|
313
|
+
let batchBytes = 0;
|
|
314
|
+
for (const row of batch) {
|
|
315
|
+
const converted = codecs.map((col) => col.isDate ? toEpochDays(row[col.name]) : coerceValue(row[col.name], col.type));
|
|
316
|
+
const bytes = converted.reduce((sum, value) => sum + scalarBytes(value), 0);
|
|
317
|
+
batchBytes += bytes;
|
|
318
|
+
if (batchBytes > maxBatchBytes) throw new RangeError("parquet stream exceeds maxBatchBytes");
|
|
319
|
+
if (bytes > maxRowGroupBytes) throw new RangeError("parquet stream single row exceeds maxRowGroupBytes");
|
|
320
|
+
if (groupRows && (groupRows >= rowGroupSize || groupBytes + bytes > maxRowGroupBytes)) await flushGroup();
|
|
321
|
+
for (let index = 0; index < converted.length; index++) values[index].push(converted[index]);
|
|
322
|
+
groupRows++;
|
|
323
|
+
groupBytes += bytes;
|
|
324
|
+
rowCount++;
|
|
325
|
+
if (groupRows === rowGroupSize) await flushGroup();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
await flushGroup();
|
|
329
|
+
await parquet.finish();
|
|
330
|
+
return {
|
|
331
|
+
bytes: writer.offset,
|
|
332
|
+
rowCount
|
|
333
|
+
};
|
|
334
|
+
} finally {
|
|
335
|
+
values = [];
|
|
336
|
+
parquet.row_groups.length = 0;
|
|
337
|
+
parquet.pendingIndexes.length = 0;
|
|
338
|
+
writer.buffer = /* @__PURE__ */ new ArrayBuffer(0);
|
|
339
|
+
writer.view = new DataView(writer.buffer);
|
|
340
|
+
writer.index = 0;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
209
343
|
function asyncBufferFromBytes(bytes) {
|
|
210
344
|
const base = bytes.byteOffset;
|
|
211
345
|
const buf = bytes.buffer;
|
|
@@ -226,7 +360,45 @@ async function decodeParquetToRows(bytes, opts = {}) {
|
|
|
226
360
|
async function* decodeParquetGroups(bytes, opts = {}) {
|
|
227
361
|
if (bytes.byteLength === 0) return;
|
|
228
362
|
const file = asyncBufferFromBytes(bytes);
|
|
229
|
-
|
|
363
|
+
yield* decodeGroupsFromMetadata(file, await parquetMetadataAsync(file), opts);
|
|
364
|
+
}
|
|
365
|
+
async function* decodeParquetRowGroups(file, opts = {}) {
|
|
366
|
+
const maxMetadataBytes = streamLimit(opts.limits?.maxMetadataBytes, 1048576, "maxMetadataBytes");
|
|
367
|
+
const maxRowGroupRows = streamLimit(opts.limits?.maxRowGroupRows, ROW_GROUP_SIZE, "maxRowGroupRows");
|
|
368
|
+
const maxRowGroupBytes = streamLimit(opts.limits?.maxRowGroupBytes, 16777216, "maxRowGroupBytes");
|
|
369
|
+
const maxRowGroups = streamLimit(opts.limits?.maxRowGroups, 4096, "maxRowGroups");
|
|
370
|
+
const maxReadBytes = streamLimit(opts.limits?.maxReadBytes, 16777216, "maxReadBytes");
|
|
371
|
+
if (!Number.isSafeInteger(file.byteLength) || file.byteLength < 12) throw new RangeError("parquet stream requires a valid file length");
|
|
372
|
+
const bounded = {
|
|
373
|
+
byteLength: file.byteLength,
|
|
374
|
+
async slice(start, end = file.byteLength) {
|
|
375
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > file.byteLength || end - start > maxReadBytes) throw new RangeError("parquet stream range exceeds maxReadBytes or file bounds");
|
|
376
|
+
const bytes = await file.slice(start, end);
|
|
377
|
+
if (bytes.byteLength !== end - start) throw new Error("parquet stream returned an incomplete range");
|
|
378
|
+
return bytes;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
const trailer = new DataView(await bounded.slice(file.byteLength - 8, file.byteLength));
|
|
382
|
+
if (trailer.getUint32(4, true) !== 827474256) throw new Error("parquet stream invalid footer");
|
|
383
|
+
const metadataBytes = trailer.getUint32(0, true);
|
|
384
|
+
if (metadataBytes > maxMetadataBytes || metadataBytes + 12 > file.byteLength) throw new RangeError("parquet stream footer exceeds maxMetadataBytes or file bounds");
|
|
385
|
+
const metadata = parquetMetadata(await bounded.slice(file.byteLength - metadataBytes - 8, file.byteLength));
|
|
386
|
+
if (metadata.row_groups.length > maxRowGroups || metadata.schema.length > 65) throw new RangeError("parquet stream exceeds row-group/schema descriptor limits");
|
|
387
|
+
let totalRows = 0;
|
|
388
|
+
for (const group of metadata.row_groups) {
|
|
389
|
+
if (group.columns.length > 64) throw new RangeError("parquet stream exceeds column descriptor limits");
|
|
390
|
+
const rows = Number(group.num_rows);
|
|
391
|
+
if (group.columns.some((col) => [col.meta_data?.total_uncompressed_size, col.meta_data?.total_compressed_size].some((size) => !Number.isSafeInteger(Number(size)) || Number(size) < 0))) throw new RangeError("parquet stream invalid declared group bytes");
|
|
392
|
+
const uncompressed = group.columns.reduce((sum, col) => sum + Number(col.meta_data?.total_uncompressed_size ?? NaN), 0);
|
|
393
|
+
const compressed = group.columns.reduce((sum, col) => sum + Number(col.meta_data?.total_compressed_size ?? NaN), 0);
|
|
394
|
+
if (!Number.isSafeInteger(rows) || rows < 0 || rows > maxRowGroupRows) throw new RangeError("parquet stream exceeds maxRowGroupRows");
|
|
395
|
+
if (!Number.isSafeInteger(uncompressed) || uncompressed < 0 || uncompressed > maxRowGroupBytes || !Number.isSafeInteger(compressed) || compressed < 0 || compressed > maxRowGroupBytes) throw new RangeError("parquet stream declared group bytes exceed maxRowGroupBytes");
|
|
396
|
+
totalRows += rows;
|
|
397
|
+
}
|
|
398
|
+
if (!Number.isSafeInteger(totalRows) || BigInt(totalRows) !== metadata.num_rows) throw new Error("parquet stream row-count metadata mismatch");
|
|
399
|
+
yield* decodeGroupsFromMetadata(bounded, metadata, opts);
|
|
400
|
+
}
|
|
401
|
+
async function* decodeGroupsFromMetadata(file, metadata, opts) {
|
|
230
402
|
const tree = parquetSchema(metadata);
|
|
231
403
|
const present = /* @__PURE__ */ new Set();
|
|
232
404
|
const dateColumns = [];
|
|
@@ -339,4 +511,4 @@ function createHyparquetCodec(options = {}) {
|
|
|
339
511
|
}
|
|
340
512
|
};
|
|
341
513
|
}
|
|
342
|
-
export { createHyparquetCodec, decodeParquetToRows, encodeRowsToParquet, encodeRowsToParquetFlex };
|
|
514
|
+
export { createHyparquetCodec, decodeParquetRowGroups, decodeParquetToRows, encodeParquetRowBatches, encodeRowsToParquet, encodeRowsToParquetFlex };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ByteWriter } from "./bytewriter.mjs";
|
|
2
2
|
import "./schema.mjs";
|
|
3
3
|
import "./wkb.mjs";
|
|
4
|
-
import "./parquet-writer.mjs";
|
|
4
|
+
import { ParquetWriter } from "./parquet-writer.mjs";
|
|
5
5
|
import "./write.mjs";
|
|
6
6
|
import { parquetWriteRows } from "./write-rows.mjs";
|
|
7
|
-
export { ByteWriter, parquetWriteRows };
|
|
7
|
+
export { ByteWriter, ParquetWriter, parquetWriteRows };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ByteWriter } from "./bytewriter.mjs";
|
|
2
2
|
import "./schema.mjs";
|
|
3
3
|
import "./wkb.mjs";
|
|
4
|
-
import "./parquet-writer.mjs";
|
|
4
|
+
import { ParquetWriter } from "./parquet-writer.mjs";
|
|
5
5
|
import "./write.mjs";
|
|
6
6
|
import { parquetWriteRows } from "./write-rows.mjs";
|
|
7
7
|
import "./index.mjs";
|
|
8
|
-
export { ByteWriter, parquetWriteRows };
|
|
8
|
+
export { ByteWriter, ParquetWriter, parquetWriteRows };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { parquetMetadataAsync, parquetSchema } from "./metadata.mjs";
|
|
1
|
+
import { parquetMetadata, parquetMetadataAsync, parquetSchema } from "./metadata.mjs";
|
|
2
2
|
import "./indexes.mjs";
|
|
3
3
|
import { toJson } from "./utils.mjs";
|
|
4
4
|
import "./snappy.mjs";
|
|
5
5
|
import "./scan.mjs";
|
|
6
6
|
import { parquetReadObjects } from "./read.mjs";
|
|
7
7
|
import "./query.mjs";
|
|
8
|
-
export { parquetMetadataAsync, parquetReadObjects, parquetSchema, toJson };
|
|
8
|
+
export { parquetMetadata, parquetMetadataAsync, parquetReadObjects, parquetSchema, toJson };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { parquetMetadataAsync, parquetSchema } from "./metadata.mjs";
|
|
1
|
+
import { parquetMetadata, parquetMetadataAsync, parquetSchema } from "./metadata.mjs";
|
|
2
2
|
import "./indexes.mjs";
|
|
3
3
|
import { toJson } from "./utils.mjs";
|
|
4
4
|
import "./snappy.mjs";
|
|
5
5
|
import { parquetReadObjects } from "./read.mjs";
|
|
6
6
|
import "./index.mjs";
|
|
7
|
-
export { parquetMetadataAsync, parquetReadObjects, parquetSchema, toJson };
|
|
7
|
+
export { parquetMetadata, parquetMetadataAsync, parquetReadObjects, parquetSchema, toJson };
|
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.4.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.4.0",
|
|
193
|
+
"@gscdump/lakehouse": "^7.4.0",
|
|
194
194
|
"drizzle-orm": "1.0.0-rc.4",
|
|
195
|
-
"gscdump": "^7.
|
|
195
|
+
"gscdump": "^7.4.0",
|
|
196
196
|
"pathe": "^2.0.3",
|
|
197
197
|
"proper-lockfile": "^4.1.2"
|
|
198
198
|
},
|