@loaders.gl/scan 5.0.0-alpha.3 → 5.0.0-alpha.4

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/dist/delta.cjs CHANGED
@@ -20676,6 +20676,201 @@ var init_decoders = __esm({
20676
20676
  }
20677
20677
  });
20678
20678
 
20679
+ // ../parquet/src/parquetjs/parser/encoded-pages.ts
20680
+ async function scanParquetEncodedPages(buffer, context) {
20681
+ const pages = [];
20682
+ let offset = 0;
20683
+ let dataPageOrdinal = 0;
20684
+ while (offset < buffer.byteLength) {
20685
+ const { pageHeader, length: headerByteLength } = decodePageHeader(buffer, offset);
20686
+ const bodyOffset = offset + headerByteLength;
20687
+ const bodyEnd = bodyOffset + pageHeader.compressed_page_size;
20688
+ if (pageHeader.compressed_page_size < 0 || pageHeader.uncompressed_page_size < 0 || bodyEnd > buffer.byteLength) {
20689
+ throw new Error("Parquet page extends beyond the available column-chunk buffer");
20690
+ }
20691
+ const compressedBody = buffer.subarray(bodyOffset, bodyEnd);
20692
+ verifyEncodedPageChecksum(compressedBody, pageHeader, context.verifyPageChecksums);
20693
+ const pageType = getThriftEnum(PageType, pageHeader.type);
20694
+ if (pageType === "DICTIONARY_PAGE") {
20695
+ pages.push(await createDictionaryPage(compressedBody, pageHeader, context));
20696
+ } else if (pageType === "DATA_PAGE") {
20697
+ pages.push(await createDataPageV1(compressedBody, pageHeader, context, dataPageOrdinal++));
20698
+ } else if (pageType === "DATA_PAGE_V2") {
20699
+ pages.push(await createDataPageV2(compressedBody, pageHeader, context, dataPageOrdinal++));
20700
+ } else {
20701
+ throw new Error(`Unsupported Parquet page type ${pageType}`);
20702
+ }
20703
+ offset = bodyEnd;
20704
+ }
20705
+ return pages;
20706
+ }
20707
+ async function createDictionaryPage(compressedBody, pageHeader, context) {
20708
+ const dictionaryHeader = pageHeader.dictionary_page_header;
20709
+ if (!dictionaryHeader) {
20710
+ throw new Error("Parquet dictionary page is missing its dictionary header");
20711
+ }
20712
+ const preserveCompression = shouldPreserveCompression(context);
20713
+ const data = preserveCompression ? compressedBody : await decompressPageBody(compressedBody, pageHeader.uncompressed_page_size, context);
20714
+ return Object.freeze({
20715
+ type: "dictionary",
20716
+ pageOrdinal: -1,
20717
+ encoding: getThriftEnum(Encoding, dictionaryHeader.encoding),
20718
+ compression: context.compression,
20719
+ compressionState: preserveCompression ? "compressed" : "decompressed",
20720
+ valueCount: dictionaryHeader.num_values,
20721
+ nonNullValueCount: dictionaryHeader.num_values,
20722
+ data,
20723
+ values: preserveCompression ? void 0 : createSection(0, data.byteLength),
20724
+ compressedByteLength: pageHeader.compressed_page_size,
20725
+ uncompressedByteLength: pageHeader.uncompressed_page_size
20726
+ });
20727
+ }
20728
+ async function createDataPageV1(compressedBody, pageHeader, context, pageOrdinal) {
20729
+ const dataHeader = pageHeader.data_page_header;
20730
+ if (!dataHeader) {
20731
+ throw new Error("Parquet V1 data page is missing its data header");
20732
+ }
20733
+ const preserveCompression = shouldPreserveCompression(context);
20734
+ const data = preserveCompression ? compressedBody : await decompressPageBody(compressedBody, pageHeader.uncompressed_page_size, context);
20735
+ let repetitionLevels;
20736
+ let definitionLevels;
20737
+ let values;
20738
+ if (!preserveCompression) {
20739
+ let sectionOffset = 0;
20740
+ const repetitionLevelEncoding = getThriftEnum(Encoding, dataHeader.repetition_level_encoding);
20741
+ repetitionLevels = readV1LevelSection(
20742
+ data,
20743
+ sectionOffset,
20744
+ dataHeader.num_values,
20745
+ context.column.rLevelMax,
20746
+ repetitionLevelEncoding
20747
+ );
20748
+ sectionOffset = repetitionLevels.byteOffset + repetitionLevels.byteLength;
20749
+ const definitionLevelEncoding = getThriftEnum(Encoding, dataHeader.definition_level_encoding);
20750
+ definitionLevels = readV1LevelSection(
20751
+ data,
20752
+ sectionOffset,
20753
+ dataHeader.num_values,
20754
+ context.column.dLevelMax,
20755
+ definitionLevelEncoding
20756
+ );
20757
+ sectionOffset = definitionLevels.byteOffset + definitionLevels.byteLength;
20758
+ values = createSection(sectionOffset, data.byteLength - sectionOffset);
20759
+ }
20760
+ return Object.freeze({
20761
+ type: "data-v1",
20762
+ pageOrdinal,
20763
+ encoding: getThriftEnum(Encoding, dataHeader.encoding),
20764
+ repetitionLevelEncoding: getThriftEnum(Encoding, dataHeader.repetition_level_encoding),
20765
+ definitionLevelEncoding: getThriftEnum(Encoding, dataHeader.definition_level_encoding),
20766
+ compression: context.compression,
20767
+ compressionState: preserveCompression ? "compressed" : "decompressed",
20768
+ valueCount: dataHeader.num_values,
20769
+ data,
20770
+ repetitionLevels,
20771
+ definitionLevels,
20772
+ values,
20773
+ compressedByteLength: pageHeader.compressed_page_size,
20774
+ uncompressedByteLength: pageHeader.uncompressed_page_size
20775
+ });
20776
+ }
20777
+ async function createDataPageV2(compressedBody, pageHeader, context, pageOrdinal) {
20778
+ const dataHeader = pageHeader.data_page_header_v2;
20779
+ if (!dataHeader) {
20780
+ throw new Error("Parquet V2 data page is missing its data header");
20781
+ }
20782
+ const repetitionByteLength = dataHeader.repetition_levels_byte_length;
20783
+ const definitionByteLength = dataHeader.definition_levels_byte_length;
20784
+ const levelsByteLength = repetitionByteLength + definitionByteLength;
20785
+ if (levelsByteLength > compressedBody.byteLength) {
20786
+ throw new Error("Parquet V2 level sections extend beyond the page body");
20787
+ }
20788
+ const valuesAreCompressed = dataHeader.is_compressed !== false && context.compression !== "UNCOMPRESSED";
20789
+ const preserveCompression = valuesAreCompressed && shouldPreserveCompression(context);
20790
+ let data = compressedBody;
20791
+ if (valuesAreCompressed && !preserveCompression) {
20792
+ const uncompressedValuesByteLength = pageHeader.uncompressed_page_size - levelsByteLength;
20793
+ if (uncompressedValuesByteLength < 0) {
20794
+ throw new Error("Invalid Parquet V2 uncompressed value length");
20795
+ }
20796
+ const values = await context.decompressPage(
20797
+ compressedBody.subarray(levelsByteLength),
20798
+ uncompressedValuesByteLength
20799
+ );
20800
+ data = new Uint8Array(levelsByteLength + values.byteLength);
20801
+ data.set(compressedBody.subarray(0, levelsByteLength));
20802
+ data.set(values, levelsByteLength);
20803
+ }
20804
+ return Object.freeze({
20805
+ type: "data-v2",
20806
+ pageOrdinal,
20807
+ encoding: getThriftEnum(Encoding, dataHeader.encoding),
20808
+ repetitionLevelEncoding: "RLE",
20809
+ definitionLevelEncoding: "RLE",
20810
+ compression: context.compression,
20811
+ compressionState: preserveCompression ? "compressed" : "decompressed",
20812
+ valueCount: dataHeader.num_values,
20813
+ nonNullValueCount: dataHeader.num_values - dataHeader.num_nulls,
20814
+ data,
20815
+ repetitionLevels: createSection(0, repetitionByteLength),
20816
+ definitionLevels: createSection(repetitionByteLength, definitionByteLength),
20817
+ values: createSection(levelsByteLength, data.byteLength - levelsByteLength),
20818
+ compressedByteLength: pageHeader.compressed_page_size,
20819
+ uncompressedByteLength: pageHeader.uncompressed_page_size
20820
+ });
20821
+ }
20822
+ function shouldPreserveCompression(context) {
20823
+ var _a;
20824
+ return context.compression !== "UNCOMPRESSED" && Boolean((_a = context.preserveCompression) == null ? void 0 : _a.has(context.compression));
20825
+ }
20826
+ async function decompressPageBody(compressedBody, uncompressedByteLength, context) {
20827
+ if (context.compression === "UNCOMPRESSED") return compressedBody;
20828
+ return await context.decompressPage(compressedBody, uncompressedByteLength);
20829
+ }
20830
+ function readV1LevelSection(data, offset, valueCount, maxLevel, encoding) {
20831
+ if (maxLevel === 0) return createSection(offset, 0);
20832
+ if (encoding === "RLE") {
20833
+ if (offset + 4 > data.byteLength) {
20834
+ throw new Error("Parquet V1 RLE level stream is missing its length prefix");
20835
+ }
20836
+ const byteLength = readUInt32LE(data, offset);
20837
+ const byteOffset = offset + 4;
20838
+ assertSectionBounds(data, byteOffset, byteLength);
20839
+ return createSection(byteOffset, byteLength);
20840
+ }
20841
+ if (encoding === "BIT_PACKED") {
20842
+ const byteLength = Math.ceil(valueCount / 8) * getBitWidth(maxLevel);
20843
+ assertSectionBounds(data, offset, byteLength);
20844
+ return createSection(offset, byteLength);
20845
+ }
20846
+ throw new Error(`Unsupported Parquet V1 level encoding ${encoding}`);
20847
+ }
20848
+ function createSection(byteOffset, byteLength) {
20849
+ return Object.freeze({ byteOffset, byteLength });
20850
+ }
20851
+ function assertSectionBounds(data, byteOffset, byteLength) {
20852
+ if (byteOffset < 0 || byteLength < 0 || byteOffset + byteLength > data.byteLength) {
20853
+ throw new Error("Parquet page section extends beyond the page body");
20854
+ }
20855
+ }
20856
+ function verifyEncodedPageChecksum(compressedBody, pageHeader, verifyPageChecksums = false) {
20857
+ if (!verifyPageChecksums || pageHeader.crc === void 0) return;
20858
+ const expected = pageHeader.crc >>> 0;
20859
+ const actual = crc32(compressedBody);
20860
+ if (actual !== expected) {
20861
+ throw new Error(`Parquet page checksum mismatch: expected ${expected}, calculated ${actual}`);
20862
+ }
20863
+ }
20864
+ var init_encoded_pages = __esm({
20865
+ "../parquet/src/parquetjs/parser/encoded-pages.ts"() {
20866
+ "use strict";
20867
+ init_parquet_thrift();
20868
+ init_crc32();
20869
+ init_binary_utils();
20870
+ init_read_utils();
20871
+ }
20872
+ });
20873
+
20679
20874
  // ../parquet/src/lib/parquet-encryption.ts
20680
20875
  function readParquetEncryptedModule(buffer, offset = 0) {
20681
20876
  if (offset + 4 > buffer.length) {
@@ -20918,6 +21113,7 @@ var init_parquet_reader = __esm({
20918
21113
  "use strict";
20919
21114
  init_schema();
20920
21115
  init_decoders();
21116
+ init_encoded_pages();
20921
21117
  init_shred();
20922
21118
  init_compression2();
20923
21119
  init_constants();
@@ -21404,6 +21600,50 @@ var init_parquet_reader = __esm({
21404
21600
  columnData: Object.fromEntries(columnEntries)
21405
21601
  };
21406
21602
  }
21603
+ /**
21604
+ * Reads selected column chunks as encoded page descriptors without materializing values.
21605
+ *
21606
+ * Page headers, encryption, checksums, and optional decompression remain reader concerns. Value,
21607
+ * dictionary, and level decoding is deliberately left to the downstream consumer.
21608
+ */
21609
+ async readRowGroupEncodedPages(schema, rowGroup, columnList, preserveCompression, signal, rowGroupOrdinal = 0) {
21610
+ await this.resolveColumnMetadata(rowGroup, rowGroupOrdinal, columnList);
21611
+ const selectedColumnChunks = [];
21612
+ for (let columnOrdinal = 0; columnOrdinal < rowGroup.columns.length; columnOrdinal++) {
21613
+ const columnChunk = rowGroup.columns[columnOrdinal];
21614
+ const path = getColumnChunkPath(columnChunk);
21615
+ if (columnList.length > 0 && (!path || fieldIndexOf(columnList, path) < 0)) continue;
21616
+ const physicalColumnOrdinal = getPhysicalColumnOrdinal(columnChunk, columnOrdinal);
21617
+ const metadata = columnChunk.meta_data || await this.getColumnMetadata(columnChunk, rowGroupOrdinal, physicalColumnOrdinal);
21618
+ if (columnList.length === 0 || fieldIndexOf(columnList, metadata.path_in_schema) >= 0) {
21619
+ selectedColumnChunks.push({
21620
+ columnChunk,
21621
+ metadata,
21622
+ columnOrdinal: physicalColumnOrdinal
21623
+ });
21624
+ }
21625
+ }
21626
+ const columns = [];
21627
+ for (let batchStart = 0; batchStart < selectedColumnChunks.length; batchStart += MAXIMUM_CONCURRENT_COLUMN_READS) {
21628
+ columns.push(
21629
+ ...await Promise.all(
21630
+ selectedColumnChunks.slice(batchStart, batchStart + MAXIMUM_CONCURRENT_COLUMN_READS).map(async ({ columnChunk, metadata, columnOrdinal }) => {
21631
+ const encodedColumn = await this.readColumnChunkEncodedPages(
21632
+ schema,
21633
+ columnChunk,
21634
+ preserveCompression,
21635
+ signal,
21636
+ rowGroupOrdinal,
21637
+ columnOrdinal,
21638
+ metadata
21639
+ );
21640
+ return encodedColumn;
21641
+ })
21642
+ )
21643
+ );
21644
+ }
21645
+ return Object.freeze(columns);
21646
+ }
21407
21647
  /** Reads independently materializable non-repeated columns for one row range using page indexes. */
21408
21648
  async readRowGroupRange(schema, rowGroup, columnList, rowRange, pageLocations, signal, rowGroupOrdinal = 0) {
21409
21649
  await this.resolveColumnMetadata(rowGroup, rowGroupOrdinal, columnList);
@@ -21596,6 +21836,99 @@ var init_parquet_reader = __esm({
21596
21836
  ) : chunkBuffer.subarray(pagesRelativeOffset, pagesRelativeOffset + pagesSize);
21597
21837
  return await decodeDataPages(pagesBuf, { ...context, dictionary });
21598
21838
  }
21839
+ /** Reads one column chunk as encoded dictionaries, levels, and values for deferred decoding. */
21840
+ async readColumnChunkEncodedPages(schema, columnChunk, preserveCompression, signal, rowGroupOrdinal = 0, columnOrdinal = 0, resolvedMetadata) {
21841
+ if (columnChunk.file_path !== void 0 && columnChunk.file_path !== null) {
21842
+ throw new Error("external references are not supported");
21843
+ }
21844
+ const metadata = resolvedMetadata || columnChunk.meta_data;
21845
+ if (!metadata) throw new Error("Parquet column metadata is missing");
21846
+ const field = schema.findField(metadata.path_in_schema);
21847
+ const physicalType = getThriftEnum(Type, metadata.type);
21848
+ if (physicalType !== field.primitiveType) {
21849
+ throw new Error(`chunk type not matching schema: ${physicalType}`);
21850
+ }
21851
+ const compression = getThriftEnum(CompressionCodec, metadata.codec);
21852
+ const dataPageOffset = Number(metadata.data_page_offset);
21853
+ const dictionaryPageOffset = metadata.dictionary_page_offset;
21854
+ const validDictionaryPageOffset = dictionaryPageOffset !== void 0 && Number(dictionaryPageOffset) > 0 ? Number(dictionaryPageOffset) : void 0;
21855
+ const chunkOffset = Math.min(dataPageOffset, validDictionaryPageOffset ?? dataPageOffset);
21856
+ const chunkEnd = chunkOffset + Number(metadata.total_compressed_size);
21857
+ const chunkByteLength = Math.min(
21858
+ this.file.size - chunkOffset,
21859
+ Math.max(0, chunkEnd - chunkOffset)
21860
+ );
21861
+ const chunkBuffer = await this.readBytes(chunkOffset, chunkByteLength, signal);
21862
+ const dataPageRelativeOffset = dataPageOffset - chunkOffset;
21863
+ const dataPageByteLength = Math.min(
21864
+ chunkBuffer.byteLength - dataPageRelativeOffset,
21865
+ Math.max(0, chunkEnd - dataPageOffset)
21866
+ );
21867
+ const context = {
21868
+ column: field,
21869
+ compression,
21870
+ decompressPage: this.getPageDecompressor(compression),
21871
+ preserveCompression,
21872
+ verifyPageChecksums: this.props.verifyPageChecksums
21873
+ };
21874
+ let dictionary;
21875
+ if (validDictionaryPageOffset !== void 0) {
21876
+ const dictionaryRelativeOffset = validDictionaryPageOffset - chunkOffset;
21877
+ const dictionaryByteLength = Math.min(
21878
+ Math.max(0, dataPageOffset - validDictionaryPageOffset),
21879
+ chunkBuffer.byteLength - dictionaryRelativeOffset,
21880
+ this.props.defaultDictionarySize
21881
+ );
21882
+ const dictionaryBuffer = chunkBuffer.subarray(
21883
+ dictionaryRelativeOffset,
21884
+ dictionaryRelativeOffset + dictionaryByteLength
21885
+ );
21886
+ const plaintextDictionaryBuffer = columnChunk.crypto_metadata ? await this.decryptColumnPages(
21887
+ dictionaryBuffer,
21888
+ {
21889
+ type: physicalType,
21890
+ rLevelMax: field.rLevelMax,
21891
+ dLevelMax: field.dLevelMax,
21892
+ compression,
21893
+ column: field
21894
+ },
21895
+ rowGroupOrdinal,
21896
+ columnOrdinal,
21897
+ this.getColumnKeyMetadata(columnChunk),
21898
+ true
21899
+ ) : dictionaryBuffer;
21900
+ dictionary = (await scanParquetEncodedPages(plaintextDictionaryBuffer, context))[0];
21901
+ }
21902
+ const dataPageBuffer = chunkBuffer.subarray(
21903
+ dataPageRelativeOffset,
21904
+ dataPageRelativeOffset + dataPageByteLength
21905
+ );
21906
+ const plaintextDataPageBuffer = columnChunk.crypto_metadata ? await this.decryptColumnPages(
21907
+ dataPageBuffer,
21908
+ {
21909
+ type: physicalType,
21910
+ rLevelMax: field.rLevelMax,
21911
+ dLevelMax: field.dLevelMax,
21912
+ compression,
21913
+ column: field
21914
+ },
21915
+ rowGroupOrdinal,
21916
+ columnOrdinal,
21917
+ this.getColumnKeyMetadata(columnChunk)
21918
+ ) : dataPageBuffer;
21919
+ const pages = await scanParquetEncodedPages(plaintextDataPageBuffer, context);
21920
+ return Object.freeze({
21921
+ path: Object.freeze([...metadata.path_in_schema]),
21922
+ physicalType,
21923
+ typeLength: field.typeLength,
21924
+ maxRepetitionLevel: field.rLevelMax,
21925
+ maxDefinitionLevel: field.dLevelMax,
21926
+ compression,
21927
+ valueCount: Number(metadata.num_values),
21928
+ dictionary,
21929
+ pages: Object.freeze(pages)
21930
+ });
21931
+ }
21599
21932
  /** Decrypts the length-prefixed page header and data modules in one column chunk. */
21600
21933
  async decryptColumnPages(encryptedPages, context, rowGroupOrdinal, columnOrdinal, keyMetadata, includesDictionary = false, dataPageOrdinalStart = 0) {
21601
21934
  if (!this.encryptionContext || !this.props.keyRetriever) {
@@ -22419,6 +22752,7 @@ var init_parquet_source_loader = __esm({
22419
22752
  init_src5();
22420
22753
  init_src2();
22421
22754
  init_src3();
22755
+ init_src4();
22422
22756
  init_get_parquet_schema();
22423
22757
  init_geoparquet_covering();
22424
22758
  init_parquet_bloom_filter();
@@ -22572,6 +22906,73 @@ var init_parquet_source_loader = __esm({
22572
22906
  scan(options = {}) {
22573
22907
  return this.read(options);
22574
22908
  }
22909
+ /**
22910
+ * Reads candidate encoded pages without decoding values or constructing Arrow arrays.
22911
+ *
22912
+ * Footer statistics and Bloom filters prune complete row groups. Any exact predicate or spatial
22913
+ * filter is returned as residual work, and its hidden columns are included in the page batch.
22914
+ */
22915
+ async *readPages(options = {}) {
22916
+ const readOptions = this.getReadOptions({
22917
+ rowGroups: options.rowGroups,
22918
+ columns: options.columns,
22919
+ rowGroupFilter: options.rowGroupFilter,
22920
+ predicate: options.predicate,
22921
+ bbox: options.bbox,
22922
+ geometryColumn: options.geometryColumn,
22923
+ signal: options.signal
22924
+ });
22925
+ const readContext = createReadAbortContext(readOptions.signal);
22926
+ this.activeReadControllers.add(readContext.abortController);
22927
+ try {
22928
+ const initialization = await this.getInitialization(readContext.abortController.signal);
22929
+ await this.getCompressionInitialization();
22930
+ throwIfAborted2(readContext.abortController.signal);
22931
+ const physicalPlan = await this.planRowGroups(
22932
+ initialization,
22933
+ readOptions,
22934
+ readContext.abortController.signal
22935
+ );
22936
+ const projectedColumns = normalizeColumns(readOptions.columns, initialization.schema);
22937
+ const predicateColumns = physicalPlan.predicate ? getParquetPredicateColumns(physicalPlan.predicate) : [];
22938
+ const geoMetadata = readOptions.bbox ? getGeoMetadata(initialization.schema.metadata) : void 0;
22939
+ const geometryColumn = readOptions.bbox ? readOptions.geometryColumn || (geoMetadata == null ? void 0 : geoMetadata.primary_column) : void 0;
22940
+ const filterColumns = [
22941
+ .../* @__PURE__ */ new Set([...predicateColumns, ...geometryColumn ? [geometryColumn] : []])
22942
+ ];
22943
+ const decodedColumns = projectedColumns.length === 0 ? [] : [.../* @__PURE__ */ new Set([...projectedColumns, ...filterColumns])];
22944
+ const columnList = decodedColumns.map((column) => [column]);
22945
+ const preserveCompression = new Set(options.preserveCompression ?? []);
22946
+ const residualFilter = readOptions.predicate || readOptions.bbox ? Object.freeze({
22947
+ predicate: readOptions.predicate,
22948
+ bbox: readOptions.bbox,
22949
+ geometryColumn
22950
+ }) : void 0;
22951
+ for (const rowGroupIndex of physicalPlan.rowGroupIndices) {
22952
+ throwIfAborted2(readContext.abortController.signal);
22953
+ const columns = await initialization.reader.readRowGroupEncodedPages(
22954
+ initialization.parquetSchema,
22955
+ initialization.fileMetadata.row_groups[rowGroupIndex],
22956
+ columnList,
22957
+ preserveCompression,
22958
+ readContext.abortController.signal,
22959
+ rowGroupIndex
22960
+ );
22961
+ yield Object.freeze({
22962
+ shape: "parquet-encoded-pages",
22963
+ rowGroup: initialization.metadata.rowGroups[rowGroupIndex],
22964
+ projectedColumns: Object.freeze([...projectedColumns]),
22965
+ filterColumns: Object.freeze(filterColumns),
22966
+ columns,
22967
+ residualFilter
22968
+ });
22969
+ }
22970
+ } finally {
22971
+ readContext.abortController.abort();
22972
+ readContext.removeSignalListener();
22973
+ this.activeReadControllers.delete(readContext.abortController);
22974
+ }
22975
+ }
22575
22976
  /** Returns a copy of cumulative transport, decode, conversion, and pruning telemetry. */
22576
22977
  getTelemetry() {
22577
22978
  return Object.freeze({ ...this.telemetry });