@loaders.gl/parquet 5.0.0-alpha.2 → 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/LICENSE +1 -1
- package/dist/bundled.d.ts +1 -1
- package/dist/bundled.d.ts.map +1 -1
- package/dist/bundled.js.map +1 -1
- package/dist/delta-format.d.ts +9 -0
- package/dist/delta-format.d.ts.map +1 -0
- package/dist/delta-format.js +12 -0
- package/dist/delta-format.js.map +1 -0
- package/dist/delta-source-loader-types.d.ts +27 -0
- package/dist/delta-source-loader-types.d.ts.map +1 -0
- package/dist/delta-source-loader-types.js +28 -0
- package/dist/delta-source-loader-types.js.map +1 -0
- package/dist/delta-source.d.ts +48 -0
- package/dist/delta-source.d.ts.map +1 -0
- package/dist/delta-source.js +222 -0
- package/dist/delta-source.js.map +1 -0
- package/dist/delta-types.d.ts +38 -0
- package/dist/delta-types.d.ts.map +1 -0
- package/dist/delta-types.js +5 -0
- package/dist/delta-types.js.map +1 -0
- package/dist/geoparquet-loader.js +1 -1
- package/dist/iceberg-rest-catalog.d.ts +49 -0
- package/dist/iceberg-rest-catalog.d.ts.map +1 -0
- package/dist/iceberg-rest-catalog.js +78 -0
- package/dist/iceberg-rest-catalog.js.map +1 -0
- package/dist/iceberg-table-source.d.ts +63 -0
- package/dist/iceberg-table-source.d.ts.map +1 -0
- package/dist/iceberg-table-source.js +797 -0
- package/dist/iceberg-table-source.js.map +1 -0
- package/dist/iceberg-types.d.ts +150 -0
- package/dist/iceberg-types.d.ts.map +1 -0
- package/dist/iceberg-types.js +5 -0
- package/dist/iceberg-types.js.map +1 -0
- package/dist/index.cjs +15841 -3306
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/arrow/convert-schema-from-parquet.d.ts +3 -1
- package/dist/lib/arrow/convert-schema-from-parquet.d.ts.map +1 -1
- package/dist/lib/arrow/convert-schema-from-parquet.js +94 -19
- package/dist/lib/arrow/convert-schema-from-parquet.js.map +1 -1
- package/dist/lib/arrow/create-nested-arrow-vector.js +89 -0
- package/dist/lib/arrow/create-nested-arrow-vector.js.map +1 -1
- package/dist/lib/constants.js +1 -1
- package/dist/lib/encoders/encode-table-to-parquet-js.d.ts.map +1 -1
- package/dist/lib/encoders/encode-table-to-parquet-js.js +202 -7
- package/dist/lib/encoders/encode-table-to-parquet-js.js.map +1 -1
- package/dist/lib/geo/geoparquet-covering.d.ts +19 -0
- package/dist/lib/geo/geoparquet-covering.d.ts.map +1 -0
- package/dist/lib/geo/geoparquet-covering.js +137 -0
- package/dist/lib/geo/geoparquet-covering.js.map +1 -0
- package/dist/lib/geo/geospatial-metadata.d.ts.map +1 -1
- package/dist/lib/geo/geospatial-metadata.js +39 -13
- package/dist/lib/geo/geospatial-metadata.js.map +1 -1
- package/dist/lib/parquet-bloom-filter-planner.d.ts +16 -0
- package/dist/lib/parquet-bloom-filter-planner.d.ts.map +1 -0
- package/dist/lib/parquet-bloom-filter-planner.js +57 -0
- package/dist/lib/parquet-bloom-filter-planner.js.map +1 -0
- package/dist/lib/parquet-bloom-filter.d.ts +30 -0
- package/dist/lib/parquet-bloom-filter.d.ts.map +1 -0
- package/dist/lib/parquet-bloom-filter.js +289 -0
- package/dist/lib/parquet-bloom-filter.js.map +1 -0
- package/dist/lib/parquet-encryption.d.ts +80 -0
- package/dist/lib/parquet-encryption.d.ts.map +1 -0
- package/dist/lib/parquet-encryption.js +190 -0
- package/dist/lib/parquet-encryption.js.map +1 -0
- package/dist/lib/parquet-page-index.d.ts +34 -5
- package/dist/lib/parquet-page-index.d.ts.map +1 -1
- package/dist/lib/parquet-page-index.js +114 -28
- package/dist/lib/parquet-page-index.js.map +1 -1
- package/dist/lib/parquet-predicate.d.ts +5 -1
- package/dist/lib/parquet-predicate.d.ts.map +1 -1
- package/dist/lib/parquet-predicate.js +42 -14
- package/dist/lib/parquet-predicate.js.map +1 -1
- package/dist/lib/parquet-source-worker-decoder.d.ts +4 -1
- package/dist/lib/parquet-source-worker-decoder.d.ts.map +1 -1
- package/dist/lib/parquet-source-worker-decoder.js +83 -17
- package/dist/lib/parquet-source-worker-decoder.js.map +1 -1
- package/dist/lib/parquet-source-worker-types.d.ts +24 -0
- package/dist/lib/parquet-source-worker-types.d.ts.map +1 -1
- package/dist/lib/parsers/get-parquet-schema.d.ts.map +1 -1
- package/dist/lib/parsers/get-parquet-schema.js +3 -1
- package/dist/lib/parsers/get-parquet-schema.js.map +1 -1
- package/dist/lib/parsers/parse-parquet-to-arrow-js.d.ts.map +1 -1
- package/dist/lib/parsers/parse-parquet-to-arrow-js.js +327 -18
- package/dist/lib/parsers/parse-parquet-to-arrow-js.js.map +1 -1
- package/dist/lib/parsers/parse-parquet-to-arrow.d.ts.map +1 -1
- package/dist/lib/parsers/parse-parquet-to-arrow.js +5 -5
- package/dist/lib/parsers/parse-parquet-to-arrow.js.map +1 -1
- package/dist/lib/parsers/parse-parquet-to-columns.d.ts.map +1 -1
- package/dist/lib/parsers/parse-parquet-to-columns.js +3 -1
- package/dist/lib/parsers/parse-parquet-to-columns.js.map +1 -1
- package/dist/lib/parsers/parse-parquet-to-json.d.ts.map +1 -1
- package/dist/lib/parsers/parse-parquet-to-json.js +18 -2
- package/dist/lib/parsers/parse-parquet-to-json.js.map +1 -1
- package/dist/lib/slice-parquet-batch.d.ts +9 -0
- package/dist/lib/slice-parquet-batch.d.ts.map +1 -0
- package/dist/lib/slice-parquet-batch.js +28 -0
- package/dist/lib/slice-parquet-batch.js.map +1 -0
- package/dist/lib/sources/parquet-range-file.d.ts +1 -1
- package/dist/lib/sources/parquet-range-file.d.ts.map +1 -1
- package/dist/lib/sources/parquet-range-file.js +28 -22
- package/dist/lib/sources/parquet-range-file.js.map +1 -1
- package/dist/lib/utils/normalize-parquet-options.d.ts.map +1 -1
- package/dist/lib/utils/normalize-parquet-options.js +15 -1
- package/dist/lib/utils/normalize-parquet-options.js.map +1 -1
- package/dist/parquet-dataset-source.cjs +16528 -4928
- package/dist/parquet-dataset-source.cjs.map +3 -3
- package/dist/parquet-dataset-source.d.ts +31 -4
- package/dist/parquet-dataset-source.d.ts.map +1 -1
- package/dist/parquet-dataset-source.js +296 -74
- package/dist/parquet-dataset-source.js.map +1 -1
- package/dist/parquet-encoded-page-types.d.ts +108 -0
- package/dist/parquet-encoded-page-types.d.ts.map +1 -0
- package/dist/parquet-encoded-page-types.js +5 -0
- package/dist/parquet-encoded-page-types.js.map +1 -0
- package/dist/parquet-js-loader-types.d.ts +2 -0
- package/dist/parquet-js-loader-types.d.ts.map +1 -1
- package/dist/parquet-js-loader-types.js +2 -1
- package/dist/parquet-js-loader-types.js.map +1 -1
- package/dist/parquet-js-loader.d.ts +1 -0
- package/dist/parquet-js-loader.d.ts.map +1 -1
- package/dist/parquet-js-loader.js +2 -1
- package/dist/parquet-js-loader.js.map +1 -1
- package/dist/parquet-js-writer.d.ts +23 -2
- package/dist/parquet-js-writer.d.ts.map +1 -1
- package/dist/parquet-js-writer.js +1 -1
- package/dist/parquet-js-writer.js.map +1 -1
- package/dist/parquet-loader-base.js +1 -1
- package/dist/parquet-loader-options.d.ts +11 -0
- package/dist/parquet-loader-options.d.ts.map +1 -1
- package/dist/parquet-loader-options.js.map +1 -1
- package/dist/parquet-source-capabilities.d.ts +8 -1
- package/dist/parquet-source-capabilities.d.ts.map +1 -1
- package/dist/parquet-source-capabilities.js +10 -0
- package/dist/parquet-source-capabilities.js.map +1 -1
- package/dist/parquet-source-loader-types.d.ts +10 -0
- package/dist/parquet-source-loader-types.d.ts.map +1 -1
- package/dist/parquet-source-loader-types.js +11 -1
- package/dist/parquet-source-loader-types.js.map +1 -1
- package/dist/parquet-source-loader.cjs +14121 -2796
- package/dist/parquet-source-loader.cjs.map +3 -3
- package/dist/parquet-source-loader.d.ts +51 -4
- package/dist/parquet-source-loader.d.ts.map +1 -1
- package/dist/parquet-source-loader.js +651 -38
- package/dist/parquet-source-loader.js.map +1 -1
- package/dist/parquet-source-types.d.ts +180 -30
- package/dist/parquet-source-types.d.ts.map +1 -1
- package/dist/parquet-source-worker.js +7378 -6989
- package/dist/parquet-source-worker.js.map +4 -4
- package/dist/parquet-worker-node.cjs +8501 -1045
- package/dist/parquet-worker-node.cjs.map +4 -4
- package/dist/parquet-worker-url.js +5 -1
- package/dist/parquet-worker-url.js.map +1 -1
- package/dist/parquet-worker.js +289 -218
- package/dist/parquet-worker.js.map +4 -4
- package/dist/parquetjs/codecs/declare.d.ts +15 -0
- package/dist/parquetjs/codecs/declare.d.ts.map +1 -1
- package/dist/parquetjs/codecs/declare.js +10 -0
- package/dist/parquetjs/codecs/declare.js.map +1 -1
- package/dist/parquetjs/codecs/delta.d.ts.map +1 -1
- package/dist/parquetjs/codecs/delta.js +356 -7
- package/dist/parquetjs/codecs/delta.js.map +1 -1
- package/dist/parquetjs/codecs/index.d.ts.map +1 -1
- package/dist/parquetjs/codecs/index.js +4 -0
- package/dist/parquetjs/codecs/index.js.map +1 -1
- package/dist/parquetjs/codecs/plain.d.ts +1 -1
- package/dist/parquetjs/codecs/plain.d.ts.map +1 -1
- package/dist/parquetjs/codecs/plain.js +153 -12
- package/dist/parquetjs/codecs/plain.js.map +1 -1
- package/dist/parquetjs/codecs/rle.d.ts +4 -0
- package/dist/parquetjs/codecs/rle.d.ts.map +1 -1
- package/dist/parquetjs/codecs/rle.js +406 -3
- package/dist/parquetjs/codecs/rle.js.map +1 -1
- package/dist/parquetjs/compression.d.ts +9 -0
- package/dist/parquetjs/compression.d.ts.map +1 -1
- package/dist/parquetjs/compression.js +86 -67
- package/dist/parquetjs/compression.js.map +1 -1
- package/dist/parquetjs/encoder/dictionary-planner.d.ts.map +1 -1
- package/dist/parquetjs/encoder/dictionary-planner.js +5 -2
- package/dist/parquetjs/encoder/dictionary-planner.js.map +1 -1
- package/dist/parquetjs/encoder/parquet-encoder.d.ts +43 -1
- package/dist/parquetjs/encoder/parquet-encoder.d.ts.map +1 -1
- package/dist/parquetjs/encoder/parquet-encoder.js +691 -34
- package/dist/parquetjs/encoder/parquet-encoder.js.map +1 -1
- package/dist/parquetjs/parquet-thrift/BoundingBox.d.ts +29 -0
- package/dist/parquetjs/parquet-thrift/BoundingBox.d.ts.map +1 -0
- package/dist/parquetjs/parquet-thrift/BoundingBox.js +85 -0
- package/dist/parquetjs/parquet-thrift/BoundingBox.js.map +1 -0
- package/dist/parquetjs/parquet-thrift/ColumnChunk.d.ts +5 -0
- package/dist/parquetjs/parquet-thrift/ColumnChunk.d.ts.map +1 -1
- package/dist/parquetjs/parquet-thrift/ColumnChunk.js +35 -0
- package/dist/parquetjs/parquet-thrift/ColumnChunk.js.map +1 -1
- package/dist/parquetjs/parquet-thrift/ColumnCryptoMetaData.d.ts +35 -0
- package/dist/parquetjs/parquet-thrift/ColumnCryptoMetaData.d.ts.map +1 -0
- package/dist/parquetjs/parquet-thrift/ColumnCryptoMetaData.js +135 -0
- package/dist/parquetjs/parquet-thrift/ColumnCryptoMetaData.js.map +1 -0
- package/dist/parquetjs/parquet-thrift/ColumnMetaData.d.ts +10 -0
- package/dist/parquetjs/parquet-thrift/ColumnMetaData.d.ts.map +1 -1
- package/dist/parquetjs/parquet-thrift/ColumnMetaData.js +73 -0
- package/dist/parquetjs/parquet-thrift/ColumnMetaData.js.map +1 -1
- package/dist/parquetjs/parquet-thrift/EncryptionAlgorithm.d.ts +22 -0
- package/dist/parquetjs/parquet-thrift/EncryptionAlgorithm.d.ts.map +1 -0
- package/dist/parquetjs/parquet-thrift/EncryptionAlgorithm.js +111 -0
- package/dist/parquetjs/parquet-thrift/EncryptionAlgorithm.js.map +1 -0
- package/dist/parquetjs/parquet-thrift/FileCryptoMetaData.d.ts +16 -0
- package/dist/parquetjs/parquet-thrift/FileCryptoMetaData.d.ts.map +1 -0
- package/dist/parquetjs/parquet-thrift/FileCryptoMetaData.js +57 -0
- package/dist/parquetjs/parquet-thrift/FileCryptoMetaData.js.map +1 -0
- package/dist/parquetjs/parquet-thrift/FileMetaData.d.ts +5 -0
- package/dist/parquetjs/parquet-thrift/FileMetaData.d.ts.map +1 -1
- package/dist/parquetjs/parquet-thrift/FileMetaData.js +35 -0
- package/dist/parquetjs/parquet-thrift/FileMetaData.js.map +1 -1
- package/dist/parquetjs/parquet-thrift/GeospatialStatistics.d.ts +18 -0
- package/dist/parquetjs/parquet-thrift/GeospatialStatistics.d.ts.map +1 -0
- package/dist/parquetjs/parquet-thrift/GeospatialStatistics.js +62 -0
- package/dist/parquetjs/parquet-thrift/GeospatialStatistics.js.map +1 -0
- package/dist/parquetjs/parquet-thrift/SizeStatistics.d.ts +26 -0
- package/dist/parquetjs/parquet-thrift/SizeStatistics.d.ts.map +1 -0
- package/dist/parquetjs/parquet-thrift/SizeStatistics.js +85 -0
- package/dist/parquetjs/parquet-thrift/SizeStatistics.js.map +1 -0
- package/dist/parquetjs/parquet-thrift/index.d.ts +6 -0
- package/dist/parquetjs/parquet-thrift/index.d.ts.map +1 -1
- package/dist/parquetjs/parquet-thrift/index.js +6 -0
- package/dist/parquetjs/parquet-thrift/index.js.map +1 -1
- package/dist/parquetjs/parser/decoders.d.ts +10 -1
- package/dist/parquetjs/parser/decoders.d.ts.map +1 -1
- package/dist/parquetjs/parser/decoders.js +318 -14
- package/dist/parquetjs/parser/decoders.js.map +1 -1
- package/dist/parquetjs/parser/encoded-pages.d.ts +24 -0
- package/dist/parquetjs/parser/encoded-pages.d.ts.map +1 -0
- package/dist/parquetjs/parser/encoded-pages.js +206 -0
- package/dist/parquetjs/parser/encoded-pages.js.map +1 -0
- package/dist/parquetjs/parser/parquet-reader.d.ts +88 -12
- package/dist/parquetjs/parser/parquet-reader.d.ts.map +1 -1
- package/dist/parquetjs/parser/parquet-reader.js +713 -66
- package/dist/parquetjs/parser/parquet-reader.js.map +1 -1
- package/dist/parquetjs/schema/declare.d.ts +22 -3
- package/dist/parquetjs/schema/declare.d.ts.map +1 -1
- package/dist/parquetjs/schema/declare.js.map +1 -1
- package/dist/parquetjs/schema/schema.d.ts.map +1 -1
- package/dist/parquetjs/schema/schema.js +10 -0
- package/dist/parquetjs/schema/schema.js.map +1 -1
- package/dist/parquetjs/schema/shred.d.ts.map +1 -1
- package/dist/parquetjs/schema/shred.js +219 -11
- package/dist/parquetjs/schema/shred.js.map +1 -1
- package/dist/parquetjs/schema/types.js +19 -21
- package/dist/parquetjs/schema/types.js.map +1 -1
- package/dist/parquetjs/schema/variant.d.ts +3 -0
- package/dist/parquetjs/schema/variant.d.ts.map +1 -0
- package/dist/parquetjs/schema/variant.js +248 -0
- package/dist/parquetjs/schema/variant.js.map +1 -0
- package/dist/parquetjs/utils/crc32.d.ts +3 -0
- package/dist/parquetjs/utils/crc32.d.ts.map +1 -0
- package/dist/parquetjs/utils/crc32.js +15 -0
- package/dist/parquetjs/utils/crc32.js.map +1 -0
- package/dist/parquetjs/utils/read-utils.d.ts +11 -1
- package/dist/parquetjs/utils/read-utils.d.ts.map +1 -1
- package/dist/parquetjs/utils/read-utils.js +21 -1
- package/dist/parquetjs/utils/read-utils.js.map +1 -1
- package/dist/unbundled.d.ts +2 -1
- package/dist/unbundled.d.ts.map +1 -1
- package/dist/unbundled.js +1 -0
- package/dist/unbundled.js.map +1 -1
- package/package.json +36 -13
- package/src/bundled.ts +10 -0
- package/src/delta-format.ts +12 -0
- package/src/delta-source-loader-types.ts +40 -0
- package/src/delta-source.ts +305 -0
- package/src/delta-types.ts +40 -0
- package/src/iceberg-rest-catalog.ts +122 -0
- package/src/iceberg-table-source.ts +1037 -0
- package/src/iceberg-types.ts +165 -0
- package/src/index.ts +59 -1
- package/src/lib/arrow/convert-schema-from-parquet.ts +113 -19
- package/src/lib/arrow/create-nested-arrow-vector.ts +116 -0
- package/src/lib/encoders/encode-table-to-parquet-js.ts +268 -7
- package/src/lib/geo/geoparquet-covering.ts +201 -0
- package/src/lib/geo/geospatial-metadata.ts +42 -15
- package/src/lib/parquet-bloom-filter-planner.ts +86 -0
- package/src/lib/parquet-bloom-filter.ts +345 -0
- package/src/lib/parquet-encryption.ts +344 -0
- package/src/lib/parquet-page-index.ts +180 -34
- package/src/lib/parquet-predicate.ts +52 -16
- package/src/lib/parquet-source-worker-decoder.ts +116 -20
- package/src/lib/parquet-source-worker-types.ts +25 -0
- package/src/lib/parsers/get-parquet-schema.ts +3 -1
- package/src/lib/parsers/parse-parquet-to-arrow-js.ts +397 -17
- package/src/lib/parsers/parse-parquet-to-arrow.ts +4 -5
- package/src/lib/parsers/parse-parquet-to-columns.ts +3 -1
- package/src/lib/parsers/parse-parquet-to-json.ts +18 -2
- package/src/lib/slice-parquet-batch.ts +32 -0
- package/src/lib/sources/parquet-range-file.ts +35 -29
- package/src/lib/utils/normalize-parquet-options.ts +17 -1
- package/src/parquet-dataset-source.ts +397 -85
- package/src/parquet-encoded-page-types.ts +122 -0
- package/src/parquet-js-loader-types.ts +1 -0
- package/src/parquet-js-loader.ts +2 -1
- package/src/parquet-js-writer.ts +30 -0
- package/src/parquet-loader-options.ts +11 -0
- package/src/parquet-source-capabilities.ts +18 -1
- package/src/parquet-source-loader-types.ts +10 -0
- package/src/parquet-source-loader.ts +1024 -52
- package/src/parquet-source-types.ts +197 -28
- package/src/parquet-worker-url.ts +6 -1
- package/src/parquetjs/codecs/declare.ts +27 -0
- package/src/parquetjs/codecs/delta.ts +466 -6
- package/src/parquetjs/codecs/index.ts +4 -0
- package/src/parquetjs/codecs/plain.ts +177 -11
- package/src/parquetjs/codecs/rle.ts +573 -3
- package/src/parquetjs/compression.ts +102 -76
- package/src/parquetjs/encoder/dictionary-planner.ts +7 -2
- package/src/parquetjs/encoder/parquet-encoder.ts +1016 -38
- package/src/parquetjs/parquet-thrift/BoundingBox.ts +108 -0
- package/src/parquetjs/parquet-thrift/ColumnChunk.ts +35 -0
- package/src/parquetjs/parquet-thrift/ColumnCryptoMetaData.ts +139 -0
- package/src/parquetjs/parquet-thrift/ColumnMetaData.ts +73 -0
- package/src/parquetjs/parquet-thrift/EncryptionAlgorithm.ts +127 -0
- package/src/parquetjs/parquet-thrift/FileCryptoMetaData.ts +60 -0
- package/src/parquetjs/parquet-thrift/FileMetaData.ts +35 -0
- package/src/parquetjs/parquet-thrift/GeospatialStatistics.ts +68 -0
- package/src/parquetjs/parquet-thrift/SizeStatistics.ts +98 -0
- package/src/parquetjs/parquet-thrift/index.ts +6 -0
- package/src/parquetjs/parser/decoders.ts +451 -26
- package/src/parquetjs/parser/encoded-pages.ts +278 -0
- package/src/parquetjs/parser/parquet-reader.ts +1112 -87
- package/src/parquetjs/schema/declare.ts +22 -2
- package/src/parquetjs/schema/schema.ts +10 -0
- package/src/parquetjs/schema/shred.ts +238 -11
- package/src/parquetjs/schema/types.ts +16 -25
- package/src/parquetjs/schema/variant.ts +313 -0
- package/src/parquetjs/utils/crc32.ts +15 -0
- package/src/parquetjs/utils/read-utils.ts +26 -1
- package/src/unbundled.ts +14 -0
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
// loaders.gl
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
|
-
import { hydrateArrowTable } from '@loaders.gl/arrow';
|
|
5
|
-
import { BlobFile, DataSource, isBrowser } from '@loaders.gl/loader-utils';
|
|
4
|
+
import { dehydrateArrowTable, hydrateArrowTable, IndexedArrowTable } from '@loaders.gl/arrow';
|
|
5
|
+
import { BlobFile, DataSource, createScanQueryMetadata, emitScanExecutionTelemetry, explainTableQuery, isBrowser, makeTableScanBatch, validateTableQueryLimit } from '@loaders.gl/loader-utils';
|
|
6
6
|
import { convertTable } from '@loaders.gl/schema-utils';
|
|
7
|
+
import { getGeoMetadata } from '@loaders.gl/gis';
|
|
7
8
|
import { getSchemaFromParquetReader } from "./lib/parsers/get-parquet-schema.js";
|
|
9
|
+
import { canGeoParquetRowGroupMatch, combineParquetPredicates, createGeoParquetBoundingBoxPredicate } from "./lib/geo/geoparquet-covering.js";
|
|
10
|
+
import { decodeParquetSplitBlockBloomFilter, encodeParquetBloomFilterValue, hashParquetBloomFilterValue, checkParquetSplitBlockBloomFilter } from "./lib/parquet-bloom-filter.js";
|
|
11
|
+
import { getParquetBloomFilterProbes } from "./lib/parquet-bloom-filter-planner.js";
|
|
8
12
|
import { canParquetRowGroupMatch, copyParquetPredicate, filterParquetRowIndices, gatherParquetColumns, getParquetPredicateColumns, validateParquetPredicate } from "./lib/parquet-predicate.js";
|
|
9
13
|
import { createParquetPagePruningPlan, getParquetPageReadRanges } from "./lib/parquet-page-index.js";
|
|
10
14
|
import { canDecodeParquetSourceOnWorker, decodeParquetSourceRowGroupOnWorker } from "./lib/parquet-source-worker-client.js";
|
|
15
|
+
import { sliceParquetBatch } from "./lib/slice-parquet-batch.js";
|
|
11
16
|
import { ParquetRangeFile } from "./lib/sources/parquet-range-file.js";
|
|
12
|
-
import {
|
|
17
|
+
import { toPrimitive } from "./parquetjs/schema/types.js";
|
|
18
|
+
import { PARQUET_TABLE_QUERY_CAPABILITIES, PARQUET_SOURCE_CAPABILITIES } from "./parquet-source-capabilities.js";
|
|
13
19
|
import { ParquetSourceLoader as ParquetSourceLoaderMetadata } from "./parquet-source-loader-types.js";
|
|
14
|
-
export { PARQUET_SOURCE_CAPABILITIES } from "./parquet-source-capabilities.js";
|
|
20
|
+
export { PARQUET_TABLE_QUERY_CAPABILITIES, PARQUET_SOURCE_CAPABILITIES } from "./parquet-source-capabilities.js";
|
|
15
21
|
import { preloadCompressions } from "./parquetjs/compression.js";
|
|
16
22
|
import { CompressionCodec, Encoding } from "./parquetjs/parquet-thrift/index.js";
|
|
17
23
|
import { ParquetReader } from "./parquetjs/parser/parquet-reader.js";
|
|
@@ -28,6 +34,8 @@ export const ParquetSourceLoaderWithParser = {
|
|
|
28
34
|
export { ParquetSourceLoaderWithParser as ParquetSourceLoader };
|
|
29
35
|
/** Reusable Parquet source that caches footer/schema state and selectively reads byte ranges. */
|
|
30
36
|
export class ParquetSource extends DataSource {
|
|
37
|
+
/** Common projection, predicate, limit, streaming, and cancellation capabilities. */
|
|
38
|
+
tableQueryCapabilities = PARQUET_TABLE_QUERY_CAPABILITIES;
|
|
31
39
|
/** Immutable feature support for the current range-backed source implementation. */
|
|
32
40
|
capabilities = PARQUET_SOURCE_CAPABILITIES;
|
|
33
41
|
/** Shared initialization for this source instance. */
|
|
@@ -66,39 +74,183 @@ export class ParquetSource extends DataSource {
|
|
|
66
74
|
formatSpecificMetadata: initialization.fileMetadata
|
|
67
75
|
};
|
|
68
76
|
}
|
|
77
|
+
/** Discovers schema, capabilities, and footer statistics without decoding Parquet data pages. */
|
|
78
|
+
async getQueryMetadata(options = {}) {
|
|
79
|
+
const [schema, metadata] = await Promise.all([
|
|
80
|
+
this.getSchema({ signal: options.signal }),
|
|
81
|
+
this.getMetadata({ signal: options.signal })
|
|
82
|
+
]);
|
|
83
|
+
return createScanQueryMetadata({
|
|
84
|
+
sourceType: 'parquet',
|
|
85
|
+
queryType: 'table',
|
|
86
|
+
execution: { status: 'supported', method: 'read' },
|
|
87
|
+
name: metadata.name,
|
|
88
|
+
schema,
|
|
89
|
+
capabilities: { table: this.tableQueryCapabilities },
|
|
90
|
+
columnRoles: getParquetColumnRoles(schema.fields.map(field => field.name)),
|
|
91
|
+
statistics: { rowCount: metadata.rowCount, byteLength: metadata.fileByteLength }
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Plans a portable query using Parquet metadata without decoding data pages. */
|
|
95
|
+
async getScanPlan(options = {}) {
|
|
96
|
+
const readOptions = this.getReadOptions(options);
|
|
97
|
+
const initialization = await this.getInitialization(readOptions.signal);
|
|
98
|
+
const sourceColumnNames = initialization.schema.fields.map(field => field.name);
|
|
99
|
+
const signal = readOptions.signal ?? new AbortController().signal;
|
|
100
|
+
const physicalPlan = await this.planRowGroups(initialization, readOptions, signal);
|
|
101
|
+
const pagePlans = await this.planPages(initialization, readOptions, physicalPlan, signal);
|
|
102
|
+
const explanation = explainTableQuery(sourceColumnNames, {
|
|
103
|
+
columns: readOptions.columns,
|
|
104
|
+
predicate: physicalPlan.predicate,
|
|
105
|
+
limit: readOptions.limit
|
|
106
|
+
}, PARQUET_TABLE_QUERY_CAPABILITIES);
|
|
107
|
+
return Object.freeze({
|
|
108
|
+
...explanation,
|
|
109
|
+
source: 'parquet',
|
|
110
|
+
rowGroups: Object.freeze({
|
|
111
|
+
indices: Object.freeze([...physicalPlan.rowGroupIndices]),
|
|
112
|
+
requested: physicalPlan.requested,
|
|
113
|
+
selected: physicalPlan.rowGroupIndices.length,
|
|
114
|
+
prunedByCallback: physicalPlan.prunedByCallback,
|
|
115
|
+
prunedBySpatial: physicalPlan.prunedBySpatial,
|
|
116
|
+
prunedByStatistics: physicalPlan.prunedByStatistics,
|
|
117
|
+
prunedByBloomFilter: physicalPlan.prunedByBloomFilter
|
|
118
|
+
}),
|
|
119
|
+
bloomFilters: Object.freeze({
|
|
120
|
+
read: physicalPlan.bloomFiltersRead,
|
|
121
|
+
bytesRead: physicalPlan.bloomFilterBytesRead
|
|
122
|
+
}),
|
|
123
|
+
pages: Object.freeze({
|
|
124
|
+
rowGroupsPlanned: new Set(pagePlans.map(plan => plan.rowGroupIndex)).size,
|
|
125
|
+
indexesRead: pagePlans.reduce((sum, plan) => sum + plan.indexesRead, 0),
|
|
126
|
+
total: pagePlans.reduce((sum, plan) => sum + plan.totalPages, 0),
|
|
127
|
+
selected: pagePlans.reduce((sum, plan) => sum + plan.selectedPages, 0),
|
|
128
|
+
rowsPruned: pagePlans.reduce((sum, plan) => sum + plan.rowsPruned, 0),
|
|
129
|
+
plans: Object.freeze(pagePlans)
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/** Explains the common logical query and Parquet-specific physical scan plan. */
|
|
134
|
+
async explain(options = {}) {
|
|
135
|
+
return await this.getScanPlan(options);
|
|
136
|
+
}
|
|
137
|
+
/** Executes a previously computed physical plan while preserving its row-group decisions. */
|
|
138
|
+
async *executeScanPlan(plan, options = {}) {
|
|
139
|
+
if (plan.source !== 'parquet') {
|
|
140
|
+
throw new Error('ParquetSource can only execute Parquet scan plans');
|
|
141
|
+
}
|
|
142
|
+
const predicateStep = plan.plan.find(step => step.kind === 'filter');
|
|
143
|
+
const limitStep = plan.plan.find(step => step.kind === 'limit');
|
|
144
|
+
yield* this.read({
|
|
145
|
+
...options,
|
|
146
|
+
columns: options.columns ?? plan.outputColumns,
|
|
147
|
+
predicate: options.predicate ??
|
|
148
|
+
(predicateStep?.kind === 'filter' ? predicateStep.predicate : undefined),
|
|
149
|
+
limit: options.limit ?? (limitStep?.kind === 'limit' ? limitStep.limit : undefined),
|
|
150
|
+
rowGroups: plan.rowGroups.indices
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** Common scan-architecture alias for selective Parquet reads. */
|
|
154
|
+
scan(options = {}) {
|
|
155
|
+
return this.read(options);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Reads candidate encoded pages without decoding values or constructing Arrow arrays.
|
|
159
|
+
*
|
|
160
|
+
* Footer statistics and Bloom filters prune complete row groups. Any exact predicate or spatial
|
|
161
|
+
* filter is returned as residual work, and its hidden columns are included in the page batch.
|
|
162
|
+
*/
|
|
163
|
+
async *readPages(options = {}) {
|
|
164
|
+
const readOptions = this.getReadOptions({
|
|
165
|
+
rowGroups: options.rowGroups,
|
|
166
|
+
columns: options.columns,
|
|
167
|
+
rowGroupFilter: options.rowGroupFilter,
|
|
168
|
+
predicate: options.predicate,
|
|
169
|
+
bbox: options.bbox,
|
|
170
|
+
geometryColumn: options.geometryColumn,
|
|
171
|
+
signal: options.signal
|
|
172
|
+
});
|
|
173
|
+
const readContext = createReadAbortContext(readOptions.signal);
|
|
174
|
+
this.activeReadControllers.add(readContext.abortController);
|
|
175
|
+
try {
|
|
176
|
+
const initialization = await this.getInitialization(readContext.abortController.signal);
|
|
177
|
+
await this.getCompressionInitialization();
|
|
178
|
+
throwIfAborted(readContext.abortController.signal);
|
|
179
|
+
const physicalPlan = await this.planRowGroups(initialization, readOptions, readContext.abortController.signal);
|
|
180
|
+
const projectedColumns = normalizeColumns(readOptions.columns, initialization.schema);
|
|
181
|
+
const predicateColumns = physicalPlan.predicate
|
|
182
|
+
? getParquetPredicateColumns(physicalPlan.predicate)
|
|
183
|
+
: [];
|
|
184
|
+
const geoMetadata = readOptions.bbox
|
|
185
|
+
? getGeoMetadata(initialization.schema.metadata)
|
|
186
|
+
: undefined;
|
|
187
|
+
const geometryColumn = readOptions.bbox
|
|
188
|
+
? readOptions.geometryColumn || geoMetadata?.primary_column
|
|
189
|
+
: undefined;
|
|
190
|
+
const filterColumns = [
|
|
191
|
+
...new Set([...predicateColumns, ...(geometryColumn ? [geometryColumn] : [])])
|
|
192
|
+
];
|
|
193
|
+
const decodedColumns = projectedColumns.length === 0 ? [] : [...new Set([...projectedColumns, ...filterColumns])];
|
|
194
|
+
const columnList = decodedColumns.map(column => [column]);
|
|
195
|
+
const preserveCompression = new Set(options.preserveCompression ?? []);
|
|
196
|
+
const residualFilter = readOptions.predicate || readOptions.bbox
|
|
197
|
+
? Object.freeze({
|
|
198
|
+
predicate: readOptions.predicate,
|
|
199
|
+
bbox: readOptions.bbox,
|
|
200
|
+
geometryColumn
|
|
201
|
+
})
|
|
202
|
+
: undefined;
|
|
203
|
+
for (const rowGroupIndex of physicalPlan.rowGroupIndices) {
|
|
204
|
+
throwIfAborted(readContext.abortController.signal);
|
|
205
|
+
const columns = await initialization.reader.readRowGroupEncodedPages(initialization.parquetSchema, initialization.fileMetadata.row_groups[rowGroupIndex], columnList, preserveCompression, readContext.abortController.signal, rowGroupIndex);
|
|
206
|
+
yield Object.freeze({
|
|
207
|
+
shape: 'parquet-encoded-pages',
|
|
208
|
+
rowGroup: initialization.metadata.rowGroups[rowGroupIndex],
|
|
209
|
+
projectedColumns: Object.freeze([...projectedColumns]),
|
|
210
|
+
filterColumns: Object.freeze(filterColumns),
|
|
211
|
+
columns,
|
|
212
|
+
residualFilter
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
readContext.abortController.abort();
|
|
218
|
+
readContext.removeSignalListener();
|
|
219
|
+
this.activeReadControllers.delete(readContext.abortController);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
69
222
|
/** Returns a copy of cumulative transport, decode, conversion, and pruning telemetry. */
|
|
70
223
|
getTelemetry() {
|
|
71
224
|
return Object.freeze({ ...this.telemetry });
|
|
72
225
|
}
|
|
73
226
|
/** Selectively fetches row groups and columns as ordered Arrow batches with source provenance. */
|
|
74
227
|
async *read(options = {}) {
|
|
228
|
+
const startedAt = Date.now();
|
|
229
|
+
const telemetryBefore = this.getTelemetry();
|
|
75
230
|
const readOptions = this.getReadOptions(options);
|
|
231
|
+
if (readOptions.limit === 0) {
|
|
232
|
+
emitScanExecutionTelemetry(options.onTelemetry, createParquetScanExecutionTelemetry(telemetryBefore, this.getTelemetry(), startedAt, 'early-terminated', 'limit'));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
76
235
|
const readContext = createReadAbortContext(readOptions.signal);
|
|
77
236
|
const inFlightReads = new Set();
|
|
78
237
|
let completed = false;
|
|
238
|
+
let limitReached = false;
|
|
79
239
|
let readError;
|
|
80
240
|
this.activeReadControllers.add(readContext.abortController);
|
|
81
241
|
try {
|
|
82
242
|
const initialization = await this.getInitialization(readContext.abortController.signal);
|
|
83
243
|
await this.getCompressionInitialization();
|
|
84
244
|
throwIfAborted(readContext.abortController.signal);
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
? candidateRowGroupIndices.filter(rowGroupIndex => readOptions.rowGroupFilter(initialization.metadata.rowGroups[rowGroupIndex]))
|
|
88
|
-
: candidateRowGroupIndices;
|
|
89
|
-
const predicate = readOptions.predicate;
|
|
90
|
-
const availableColumns = new Set(initialization.schema.fields.map(field => field.name));
|
|
91
|
-
if (predicate) {
|
|
92
|
-
validateParquetPredicate(predicate, availableColumns);
|
|
93
|
-
}
|
|
94
|
-
const rowGroupIndices = predicate
|
|
95
|
-
? callbackFilteredRowGroupIndices.filter(rowGroupIndex => canParquetRowGroupMatch(predicate, initialization.metadata.rowGroups[rowGroupIndex]))
|
|
96
|
-
: callbackFilteredRowGroupIndices;
|
|
97
|
-
const rowGroupsPrunedByStatistics = callbackFilteredRowGroupIndices.length - rowGroupIndices.length;
|
|
245
|
+
const physicalPlan = await this.planRowGroups(initialization, readOptions, readContext.abortController.signal);
|
|
246
|
+
const { predicate, rowGroupIndices } = physicalPlan;
|
|
98
247
|
this.recordTelemetry('row-group-prune', {
|
|
99
|
-
rowGroupsRequested:
|
|
100
|
-
rowGroupsPruned:
|
|
101
|
-
rowGroupsPrunedByStatistics
|
|
248
|
+
rowGroupsRequested: physicalPlan.requested,
|
|
249
|
+
rowGroupsPruned: physicalPlan.requested - rowGroupIndices.length,
|
|
250
|
+
rowGroupsPrunedByStatistics: physicalPlan.prunedByStatistics,
|
|
251
|
+
rowGroupsPrunedByBloomFilter: physicalPlan.prunedByBloomFilter,
|
|
252
|
+
bloomFiltersRead: physicalPlan.bloomFiltersRead,
|
|
253
|
+
bloomFilterBytesRead: physicalPlan.bloomFilterBytesRead
|
|
102
254
|
}, {});
|
|
103
255
|
const columns = normalizeColumns(readOptions.columns, initialization.schema);
|
|
104
256
|
const predicateColumns = predicate ? getParquetPredicateColumns(predicate) : [];
|
|
@@ -109,9 +261,11 @@ export class ParquetSource extends DataSource {
|
|
|
109
261
|
const projectedSchema = projectSchema(initialization.schema, columns);
|
|
110
262
|
const projectedColumnNames = columns.length ? new Set(columns) : undefined;
|
|
111
263
|
const workerOptions = this.getWorkerOptions(concurrency, readContext.abortController.signal);
|
|
112
|
-
const decodeOnWorker = canDecodeParquetSourceOnWorker(workerOptions)
|
|
264
|
+
const decodeOnWorker = canDecodeParquetSourceOnWorker(workerOptions) &&
|
|
265
|
+
(!initialization.reader.encrypted || Boolean(this.options.parquet?.keyRetriever));
|
|
113
266
|
const scheduledReads = new Map();
|
|
114
267
|
let nextPositionToSchedule = 0;
|
|
268
|
+
let remainingRows = readOptions.limit ?? Number.POSITIVE_INFINITY;
|
|
115
269
|
const scheduleReads = () => {
|
|
116
270
|
while (nextPositionToSchedule < rowGroupIndices.length &&
|
|
117
271
|
scheduledReads.size < concurrency) {
|
|
@@ -139,9 +293,15 @@ export class ParquetSource extends DataSource {
|
|
|
139
293
|
const arrowTable = hydrateArrowTable(workerBatch.arrowTable);
|
|
140
294
|
const deserializationDurationMs = getCurrentTime() - deserializationStartTime;
|
|
141
295
|
this.recordTelemetry('arrow-conversion', { arrowConversionDurationMs: deserializationDurationMs }, { rowGroupIndex, durationMs: deserializationDurationMs });
|
|
142
|
-
const batch = createParquetBatchFromArrow(initialization.metadata, projectedSchema, rowGroupIndex, workerBatch.rowGroupRowOffset, arrowTable, workerBatch.rowCount, workerBatch.rowGroupRowIndices);
|
|
143
|
-
this.recordTelemetry('batch', { batchesEmitted: 1, rowsEmitted:
|
|
296
|
+
const batch = sliceParquetBatch(createParquetBatchFromArrow(initialization.metadata, projectedSchema, rowGroupIndex, workerBatch.rowGroupRowOffset, arrowTable, workerBatch.rowCount, workerBatch.rowGroupRowIndices), Math.min(workerBatch.rowCount, remainingRows));
|
|
297
|
+
this.recordTelemetry('batch', { batchesEmitted: 1, rowsEmitted: batch.length }, { rowGroupIndex, rowCount: batch.length });
|
|
144
298
|
yield batch;
|
|
299
|
+
remainingRows -= batch.length;
|
|
300
|
+
if (remainingRows === 0) {
|
|
301
|
+
limitReached = true;
|
|
302
|
+
completed = true;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
145
305
|
}
|
|
146
306
|
continue;
|
|
147
307
|
}
|
|
@@ -149,7 +309,7 @@ export class ParquetSource extends DataSource {
|
|
|
149
309
|
const outputBatchSize = batchSize || Math.max(outputRowCount, 1);
|
|
150
310
|
for (let outputRowOffset = 0; outputRowOffset < outputRowCount; outputRowOffset += outputBatchSize) {
|
|
151
311
|
throwIfAborted(readContext.abortController.signal);
|
|
152
|
-
const batchRowCount = Math.min(outputBatchSize, outputRowCount - outputRowOffset);
|
|
312
|
+
const batchRowCount = Math.min(outputBatchSize, outputRowCount - outputRowOffset, remainingRows);
|
|
153
313
|
const batchRowIndices = rowIndices
|
|
154
314
|
? rowIndices.slice(outputRowOffset, outputRowOffset + batchRowCount)
|
|
155
315
|
: undefined;
|
|
@@ -161,6 +321,12 @@ export class ParquetSource extends DataSource {
|
|
|
161
321
|
this.recordTelemetry('arrow-conversion', { arrowConversionDurationMs: conversionDurationMs }, { rowGroupIndex, durationMs: conversionDurationMs });
|
|
162
322
|
this.recordTelemetry('batch', { batchesEmitted: 1, rowsEmitted: batchRowCount }, { rowGroupIndex, rowCount: batchRowCount });
|
|
163
323
|
yield batch;
|
|
324
|
+
remainingRows -= batch.length;
|
|
325
|
+
if (remainingRows === 0) {
|
|
326
|
+
limitReached = true;
|
|
327
|
+
completed = true;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
164
330
|
}
|
|
165
331
|
}
|
|
166
332
|
completed = true;
|
|
@@ -179,11 +345,124 @@ export class ParquetSource extends DataSource {
|
|
|
179
345
|
if (!completed && readError === undefined) {
|
|
180
346
|
this.recordTelemetry('cancel', { cancellationCount: 1 }, {});
|
|
181
347
|
}
|
|
348
|
+
const cancelled = readContext.abortController.signal.aborted || readOptions.signal?.aborted;
|
|
182
349
|
readContext.abortController.abort();
|
|
183
350
|
readContext.removeSignalListener();
|
|
184
351
|
this.activeReadControllers.delete(readContext.abortController);
|
|
185
352
|
await Promise.allSettled([...inFlightReads]);
|
|
353
|
+
const status = readError
|
|
354
|
+
? cancelled
|
|
355
|
+
? 'cancelled'
|
|
356
|
+
: 'failed'
|
|
357
|
+
: limitReached || !completed
|
|
358
|
+
? 'early-terminated'
|
|
359
|
+
: 'completed';
|
|
360
|
+
emitScanExecutionTelemetry(options.onTelemetry, createParquetScanExecutionTelemetry(telemetryBefore, this.getTelemetry(), startedAt, status, limitReached
|
|
361
|
+
? 'limit'
|
|
362
|
+
: !completed && readError === undefined
|
|
363
|
+
? 'consumer-return'
|
|
364
|
+
: undefined, readError));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/** Produces the physical row-group plan shared by explain and execution. */
|
|
368
|
+
async planRowGroups(initialization, readOptions, signal) {
|
|
369
|
+
throwIfAborted(signal);
|
|
370
|
+
const candidateRowGroupIndices = normalizeRowGroupIndices(readOptions.rowGroups, initialization.fileMetadata.row_groups.length);
|
|
371
|
+
const callbackFilteredRowGroupIndices = readOptions.rowGroupFilter
|
|
372
|
+
? candidateRowGroupIndices.filter(rowGroupIndex => readOptions.rowGroupFilter(initialization.metadata.rowGroups[rowGroupIndex]))
|
|
373
|
+
: candidateRowGroupIndices;
|
|
374
|
+
const spatiallyFilteredRowGroupIndices = readOptions.bbox
|
|
375
|
+
? callbackFilteredRowGroupIndices.filter(rowGroupIndex => canGeoParquetRowGroupMatch(initialization.metadata, initialization.metadata.rowGroups[rowGroupIndex], readOptions.bbox, readOptions.geometryColumn))
|
|
376
|
+
: callbackFilteredRowGroupIndices;
|
|
377
|
+
const spatialPredicate = readOptions.bbox
|
|
378
|
+
? createGeoParquetBoundingBoxPredicate(initialization.metadata, readOptions.bbox, readOptions.geometryColumn)
|
|
379
|
+
: undefined;
|
|
380
|
+
const predicate = combineParquetPredicates(readOptions.predicate, spatialPredicate);
|
|
381
|
+
if (predicate) {
|
|
382
|
+
validateParquetPredicate(predicate, new Set(initialization.schema.fields.map(field => field.name)));
|
|
383
|
+
}
|
|
384
|
+
const statisticsRowGroupIndices = predicate
|
|
385
|
+
? spatiallyFilteredRowGroupIndices.filter(rowGroupIndex => canParquetRowGroupMatch(predicate, initialization.metadata.rowGroups[rowGroupIndex]))
|
|
386
|
+
: spatiallyFilteredRowGroupIndices;
|
|
387
|
+
const bloomFilterResult = predicate
|
|
388
|
+
? await filterParquetRowGroupsWithBloomFilters(initialization, statisticsRowGroupIndices, predicate, signal)
|
|
389
|
+
: { rowGroupIndices: statisticsRowGroupIndices, filtersRead: 0, bytesRead: 0 };
|
|
390
|
+
return {
|
|
391
|
+
predicate,
|
|
392
|
+
rowGroupIndices: bloomFilterResult.rowGroupIndices,
|
|
393
|
+
requested: candidateRowGroupIndices.length,
|
|
394
|
+
prunedByCallback: candidateRowGroupIndices.length - callbackFilteredRowGroupIndices.length,
|
|
395
|
+
prunedBySpatial: callbackFilteredRowGroupIndices.length - spatiallyFilteredRowGroupIndices.length,
|
|
396
|
+
prunedByStatistics: spatiallyFilteredRowGroupIndices.length - statisticsRowGroupIndices.length,
|
|
397
|
+
prunedByBloomFilter: statisticsRowGroupIndices.length - bloomFilterResult.rowGroupIndices.length,
|
|
398
|
+
bloomFiltersRead: bloomFilterResult.filtersRead,
|
|
399
|
+
bloomFilterBytesRead: bloomFilterResult.bytesRead
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
/** Produces explainable page-index and byte-range plans for retained row groups. */
|
|
403
|
+
async planPages(initialization, readOptions, rowGroupPlan, signal) {
|
|
404
|
+
if (!rowGroupPlan.predicate)
|
|
405
|
+
return [];
|
|
406
|
+
const columns = normalizeColumns(readOptions.columns, initialization.schema);
|
|
407
|
+
const predicateColumns = getParquetPredicateColumns(rowGroupPlan.predicate);
|
|
408
|
+
const decodedColumns = columns.length === 0 ? [] : [...new Set([...columns, ...predicateColumns])];
|
|
409
|
+
const hasHiddenPredicateColumns = columns.length > 0 && predicateColumns.some(column => !columns.includes(column));
|
|
410
|
+
const phases = hasHiddenPredicateColumns
|
|
411
|
+
? [
|
|
412
|
+
{
|
|
413
|
+
phase: 'predicate',
|
|
414
|
+
columns: predicateColumns.map(column => [column]),
|
|
415
|
+
predicate: rowGroupPlan.predicate
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
phase: 'projection',
|
|
419
|
+
columns: columns.map(column => [column]),
|
|
420
|
+
predicate: undefined
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
: [
|
|
424
|
+
{
|
|
425
|
+
phase: 'combined',
|
|
426
|
+
columns: decodedColumns.map(column => [column]),
|
|
427
|
+
predicate: rowGroupPlan.predicate
|
|
428
|
+
}
|
|
429
|
+
];
|
|
430
|
+
const plans = [];
|
|
431
|
+
for (const rowGroupIndex of rowGroupPlan.rowGroupIndices) {
|
|
432
|
+
const rowGroup = initialization.fileMetadata.row_groups[rowGroupIndex];
|
|
433
|
+
let predicateProvedEmpty = false;
|
|
434
|
+
for (const phase of phases) {
|
|
435
|
+
throwIfAborted(signal);
|
|
436
|
+
if (phase.phase === 'projection' && predicateProvedEmpty) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
await initialization.reader.resolveColumnMetadata(rowGroup, rowGroupIndex, phase.columns);
|
|
440
|
+
const pagePlan = phase.predicate
|
|
441
|
+
? await createParquetPagePruningPlan(initialization.file, rowGroup, initialization.parquetSchema, phase.columns, phase.predicate, signal, {
|
|
442
|
+
rowGroupOrdinal: rowGroupIndex,
|
|
443
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
444
|
+
decryptModule: (bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk) => initialization.reader.decryptIndexModule(bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk)
|
|
445
|
+
})
|
|
446
|
+
: undefined;
|
|
447
|
+
plans.push(pagePlan
|
|
448
|
+
? Object.freeze({
|
|
449
|
+
rowGroupIndex,
|
|
450
|
+
phase: phase.phase,
|
|
451
|
+
columns: Object.freeze(phase.columns.map(column => Object.freeze([...column]))),
|
|
452
|
+
rowRanges: Object.freeze(pagePlan.rowRanges.map(rowRange => Object.freeze({ ...rowRange }))),
|
|
453
|
+
indexesRead: pagePlan.indexCount,
|
|
454
|
+
totalPages: pagePlan.totalPageCount,
|
|
455
|
+
selectedPages: pagePlan.selectedPageCount,
|
|
456
|
+
rowsPruned: pagePlan.prunedRowCount,
|
|
457
|
+
ranges: Object.freeze(getParquetPageReadRanges(rowGroup, phase.columns, pagePlan, initialization.parquetSchema).map(range => Object.freeze({ ...range })))
|
|
458
|
+
})
|
|
459
|
+
: createFullColumnScanPlan(rowGroup, rowGroupIndex, phase.phase, phase.columns));
|
|
460
|
+
if (phase.phase === 'predicate' && pagePlan?.rowRanges.length === 0) {
|
|
461
|
+
predicateProvedEmpty = true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
186
464
|
}
|
|
465
|
+
return plans;
|
|
187
466
|
}
|
|
188
467
|
/** Closes the underlying readable file and aborts active remote requests. */
|
|
189
468
|
async close() {
|
|
@@ -196,6 +475,7 @@ export class ParquetSource extends DataSource {
|
|
|
196
475
|
}
|
|
197
476
|
await this.readableFile?.close();
|
|
198
477
|
const initialization = await this.initializationPromise?.catch(() => null);
|
|
478
|
+
initialization?.reader.close();
|
|
199
479
|
if (initialization?.file !== this.readableFile) {
|
|
200
480
|
await initialization?.file.close();
|
|
201
481
|
}
|
|
@@ -230,11 +510,17 @@ export class ParquetSource extends DataSource {
|
|
|
230
510
|
const rowGroups = options.rowGroups ?? this.options.parquet?.rowGroups;
|
|
231
511
|
const columns = options.columns ?? this.options.parquet?.columns;
|
|
232
512
|
const predicate = options.predicate ?? this.options.parquet?.predicate;
|
|
513
|
+
const bbox = options.bbox ?? this.options.parquet?.bbox;
|
|
514
|
+
const limit = options.limit ?? this.options.parquet?.limit;
|
|
515
|
+
validateTableQueryLimit(limit);
|
|
233
516
|
return {
|
|
234
517
|
rowGroups: rowGroups && [...rowGroups],
|
|
235
518
|
columns: columns && [...columns],
|
|
236
519
|
rowGroupFilter: options.rowGroupFilter ?? this.options.parquet?.rowGroupFilter,
|
|
237
520
|
predicate: predicate ? copyParquetPredicate(predicate) : undefined,
|
|
521
|
+
limit,
|
|
522
|
+
bbox: bbox ? [...bbox] : undefined,
|
|
523
|
+
geometryColumn: options.geometryColumn ?? this.options.parquet?.geometryColumn,
|
|
238
524
|
batchSize: options.batchSize ?? this.options.parquet?.batchSize,
|
|
239
525
|
concurrency: options.concurrency ?? this.options.parquet?.concurrency,
|
|
240
526
|
signal: options.signal
|
|
@@ -260,9 +546,19 @@ export class ParquetSource extends DataSource {
|
|
|
260
546
|
}
|
|
261
547
|
/** Fetches and decodes one selected row group using the cached footer and schema. */
|
|
262
548
|
async readRowGroup(initialization, rowGroupIndex, columnList, projectedSchema, batchSize, predicate, projectedColumnNames, workerOptions, signal) {
|
|
549
|
+
if (predicate &&
|
|
550
|
+
projectedColumnNames &&
|
|
551
|
+
getParquetPredicateColumns(predicate).some(column => !projectedColumnNames.has(column))) {
|
|
552
|
+
return await this.readRowGroupWithLateMaterialization(initialization, rowGroupIndex, columnList, projectedSchema, batchSize, predicate, projectedColumnNames, workerOptions, signal);
|
|
553
|
+
}
|
|
263
554
|
const rowGroup = initialization.fileMetadata.row_groups[rowGroupIndex];
|
|
555
|
+
await initialization.reader.resolveColumnMetadata(rowGroup, rowGroupIndex, columnList);
|
|
264
556
|
const pagePlan = predicate
|
|
265
|
-
? await createParquetPagePruningPlan(initialization.file, rowGroup, initialization.parquetSchema, columnList, predicate, signal
|
|
557
|
+
? await createParquetPagePruningPlan(initialization.file, rowGroup, initialization.parquetSchema, columnList, predicate, signal, {
|
|
558
|
+
rowGroupOrdinal: rowGroupIndex,
|
|
559
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
560
|
+
decryptModule: (bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk) => initialization.reader.decryptIndexModule(bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk)
|
|
561
|
+
})
|
|
266
562
|
: undefined;
|
|
267
563
|
if (pagePlan) {
|
|
268
564
|
this.recordPagePruningTelemetry(rowGroupIndex, pagePlan);
|
|
@@ -275,9 +571,9 @@ export class ParquetSource extends DataSource {
|
|
|
275
571
|
}
|
|
276
572
|
const decodeStartTime = getCurrentTime();
|
|
277
573
|
const decodedRowGroups = pagePlan
|
|
278
|
-
? await Promise.all(pagePlan.rowRanges.map(rowRange => initialization.reader.readRowGroupRange(initialization.parquetSchema, rowGroup, columnList, rowRange, pagePlan.pageLocations, signal)))
|
|
574
|
+
? await Promise.all(pagePlan.rowRanges.map(rowRange => initialization.reader.readRowGroupRange(initialization.parquetSchema, rowGroup, columnList, rowRange, pagePlan.pageLocations, signal, rowGroupIndex)))
|
|
279
575
|
: [
|
|
280
|
-
await initialization.reader.readRowGroup(initialization.parquetSchema, rowGroup, columnList, signal)
|
|
576
|
+
await initialization.reader.readRowGroup(initialization.parquetSchema, rowGroup, columnList, signal, rowGroupIndex)
|
|
281
577
|
];
|
|
282
578
|
throwIfAborted(signal);
|
|
283
579
|
const columns = concatenateMaterializedColumns(decodedRowGroups.map(decodedRowGroup => initialization.parquetSchema.materializeColumns(decodedRowGroup)));
|
|
@@ -315,12 +611,25 @@ export class ParquetSource extends DataSource {
|
|
|
315
611
|
return Boolean(path && (columnList.length === 0 || fieldIndexOf(columnList, path) >= 0));
|
|
316
612
|
});
|
|
317
613
|
const rangeDescriptors = pagePlan
|
|
318
|
-
? getParquetPageReadRanges(rowGroup, columnList, pagePlan)
|
|
614
|
+
? getParquetPageReadRanges(rowGroup, columnList, pagePlan, initialization.parquetSchema)
|
|
319
615
|
: selectedColumnChunks.map(getColumnChunkRange);
|
|
320
616
|
const ranges = await Promise.all(rangeDescriptors.map(async ({ offset, length }) => {
|
|
321
617
|
return { offset, data: await initialization.file.read(offset, length, signal) };
|
|
322
618
|
}));
|
|
323
619
|
throwIfAborted(signal);
|
|
620
|
+
const workerColumnChunks = await Promise.all(selectedColumnChunks.map(async (columnChunk) => {
|
|
621
|
+
const descriptor = createParquetSourceWorkerColumnChunk(columnChunk);
|
|
622
|
+
descriptor.columnOrdinal = rowGroup.columns.indexOf(columnChunk);
|
|
623
|
+
if (columnChunk.crypto_metadata) {
|
|
624
|
+
const columnOrdinal = rowGroup.columns.indexOf(columnChunk);
|
|
625
|
+
const key = await initialization.reader.getColumnKeyForWorker(columnChunk, rowGroupIndex, columnOrdinal);
|
|
626
|
+
descriptor.encrypted = true;
|
|
627
|
+
descriptor.keyMetadata = key.keyMetadata?.slice().buffer;
|
|
628
|
+
descriptor.keyMaterial = key.keyMaterial.slice().buffer;
|
|
629
|
+
}
|
|
630
|
+
return descriptor;
|
|
631
|
+
}));
|
|
632
|
+
const encryptionContext = initialization.reader.getEncryptionContextForWorker();
|
|
324
633
|
let workerResult;
|
|
325
634
|
try {
|
|
326
635
|
workerResult = await decodeParquetSourceRowGroupOnWorker({
|
|
@@ -329,12 +638,21 @@ export class ParquetSource extends DataSource {
|
|
|
329
638
|
uncompressedByteLength: Number(rowGroup.total_byte_size),
|
|
330
639
|
schemaDefinition: initialization.parquetSchema.schema,
|
|
331
640
|
projectedSchema,
|
|
332
|
-
columnChunks:
|
|
641
|
+
columnChunks: workerColumnChunks,
|
|
333
642
|
ranges,
|
|
334
643
|
batchSize: batchSize || Math.max(Number(rowGroup.num_rows), 1),
|
|
335
644
|
predicate: predicate ? copyParquetPredicate(predicate) : undefined,
|
|
336
645
|
pagePlan,
|
|
337
|
-
|
|
646
|
+
encryption: encryptionContext
|
|
647
|
+
? {
|
|
648
|
+
algorithm: encryptionContext.algorithm,
|
|
649
|
+
aadPrefix: encryptionContext.aadPrefix?.slice().buffer,
|
|
650
|
+
fileUnique: encryptionContext.fileUnique.slice().buffer
|
|
651
|
+
}
|
|
652
|
+
: undefined,
|
|
653
|
+
preserveBinary: Boolean(this.options.parquet?.preserveBinary),
|
|
654
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
655
|
+
verifyPageChecksums: Boolean(this.options.parquet?.verifyPageChecksums)
|
|
338
656
|
}, workerOptions);
|
|
339
657
|
}
|
|
340
658
|
catch (error) {
|
|
@@ -352,6 +670,35 @@ export class ParquetSource extends DataSource {
|
|
|
352
670
|
}
|
|
353
671
|
return { rowGroupIndex, rowCount: workerResult.rowCount, workerResult };
|
|
354
672
|
}
|
|
673
|
+
/** Decodes predicate columns first, avoiding projected-column decoding for empty matches. */
|
|
674
|
+
async readRowGroupWithLateMaterialization(initialization, rowGroupIndex, columnList, projectedSchema, batchSize, predicate, projectedColumnNames, workerOptions, signal) {
|
|
675
|
+
const predicateColumns = getParquetPredicateColumns(predicate);
|
|
676
|
+
const predicateColumnList = predicateColumns.map(column => [column]);
|
|
677
|
+
const filterResult = await this.readRowGroup(initialization, rowGroupIndex, predicateColumnList, projectSchema(initialization.schema, predicateColumns), batchSize, predicate, new Set(predicateColumns), workerOptions, signal);
|
|
678
|
+
const rowIndices = filterResult.rowIndices;
|
|
679
|
+
const selectedRowIndices = rowIndices || getWorkerRowIndices(filterResult.workerResult);
|
|
680
|
+
if (!selectedRowIndices || selectedRowIndices.length === 0) {
|
|
681
|
+
return { rowGroupIndex, rowCount: 0, columns: {}, rowIndices: [] };
|
|
682
|
+
}
|
|
683
|
+
const projectedColumnList = columnList.filter(columnPath => projectedColumnNames.has(columnPath[0]));
|
|
684
|
+
const projectedResult = await this.readRowGroup(initialization, rowGroupIndex, projectedColumnList, projectedSchema, batchSize, undefined, projectedColumnNames, workerOptions, signal);
|
|
685
|
+
if (workerOptions && projectedResult.workerResult) {
|
|
686
|
+
return {
|
|
687
|
+
rowGroupIndex,
|
|
688
|
+
rowCount: selectedRowIndices.length,
|
|
689
|
+
rowIndices: selectedRowIndices,
|
|
690
|
+
workerResult: gatherWorkerProjectedRows(projectedResult.workerResult, selectedRowIndices, batchSize)
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
rowGroupIndex,
|
|
695
|
+
rowCount: selectedRowIndices.length,
|
|
696
|
+
rowIndices: selectedRowIndices,
|
|
697
|
+
columns: projectedResult.columns
|
|
698
|
+
? gatherParquetColumns(projectedResult.columns, selectedRowIndices, projectedColumnNames)
|
|
699
|
+
: {}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
355
702
|
/** Records the rows and pages avoided by one conservative page-index plan. */
|
|
356
703
|
recordPagePruningTelemetry(rowGroupIndex, plan) {
|
|
357
704
|
this.recordTelemetry('page-index-prune', {
|
|
@@ -369,6 +716,11 @@ export class ParquetSource extends DataSource {
|
|
|
369
716
|
try {
|
|
370
717
|
const reader = new ParquetReader(file, {
|
|
371
718
|
preserveBinary: this.options.parquet?.preserveBinary,
|
|
719
|
+
int96AsTimestamp: this.options.parquet?.int96AsTimestamp,
|
|
720
|
+
verifyPageChecksums: this.options.parquet?.verifyPageChecksums,
|
|
721
|
+
verifyFooterSignature: this.options.parquet?.verifyFooterSignature,
|
|
722
|
+
keyRetriever: this.options.parquet?.keyRetriever,
|
|
723
|
+
aadPrefix: this.options.parquet?.aadPrefix,
|
|
372
724
|
signal
|
|
373
725
|
});
|
|
374
726
|
const fileMetadata = await reader.getFileMetadata();
|
|
@@ -421,6 +773,26 @@ export class ParquetSource extends DataSource {
|
|
|
421
773
|
}
|
|
422
774
|
}
|
|
423
775
|
}
|
|
776
|
+
function getParquetColumnRoles(columnNames) {
|
|
777
|
+
const roles = {};
|
|
778
|
+
for (const columnName of columnNames) {
|
|
779
|
+
const normalizedName = columnName.toLowerCase();
|
|
780
|
+
if (normalizedName === 'geometry' || normalizedName === 'geom' || normalizedName === 'shape') {
|
|
781
|
+
roles[columnName] = 'geometry';
|
|
782
|
+
}
|
|
783
|
+
else if (normalizedName === 'longitude' ||
|
|
784
|
+
normalizedName === 'lon' ||
|
|
785
|
+
normalizedName === 'x') {
|
|
786
|
+
roles[columnName] = 'longitude';
|
|
787
|
+
}
|
|
788
|
+
else if (normalizedName === 'latitude' ||
|
|
789
|
+
normalizedName === 'lat' ||
|
|
790
|
+
normalizedName === 'y') {
|
|
791
|
+
roles[columnName] = 'latitude';
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return roles;
|
|
795
|
+
}
|
|
424
796
|
/** Normalizes a decoded Parquet footer into source metadata. */
|
|
425
797
|
function createParquetSourceMetadata(data, url, file, fileMetadata, schema, parquetSchema, objectVersion) {
|
|
426
798
|
let rowOffset = 0;
|
|
@@ -428,7 +800,7 @@ function createParquetSourceMetadata(data, url, file, fileMetadata, schema, parq
|
|
|
428
800
|
const columns = rowGroup.columns
|
|
429
801
|
.filter(columnChunk => Boolean(columnChunk.meta_data))
|
|
430
802
|
.map(columnChunk => createColumnChunkMetadata(columnChunk, parquetSchema));
|
|
431
|
-
const normalizedRowGroup = createRowGroupMetadata(index, rowOffset, Number(rowGroup.num_rows), Number(rowGroup.total_byte_size), columns);
|
|
803
|
+
const normalizedRowGroup = createRowGroupMetadata(index, rowOffset, Number(rowGroup.num_rows), Number(rowGroup.total_byte_size), columns, rowGroup.sorting_columns);
|
|
432
804
|
rowOffset += normalizedRowGroup.rowCount;
|
|
433
805
|
return normalizedRowGroup;
|
|
434
806
|
});
|
|
@@ -455,6 +827,8 @@ function createColumnChunkMetadata(columnChunk, parquetSchema) {
|
|
|
455
827
|
? undefined
|
|
456
828
|
: Number(columnMetadata.dictionary_page_offset);
|
|
457
829
|
const statistics = createColumnChunkStatistics(columnMetadata.statistics, parquetSchema.findField(columnMetadata.path_in_schema));
|
|
830
|
+
const geospatialStatistics = createGeospatialStatistics(columnMetadata.geospatial_statistics);
|
|
831
|
+
const sizeStatistics = createSizeStatistics(columnMetadata.size_statistics);
|
|
458
832
|
const compressedByteLength = Number(columnMetadata.total_compressed_size);
|
|
459
833
|
const uncompressedByteLength = Number(columnMetadata.total_uncompressed_size);
|
|
460
834
|
return Object.freeze({
|
|
@@ -478,7 +852,75 @@ function createColumnChunkMetadata(columnChunk, parquetSchema) {
|
|
|
478
852
|
? undefined
|
|
479
853
|
: Number(columnChunk.offset_index_offset),
|
|
480
854
|
offsetIndexByteLength: columnChunk.offset_index_length,
|
|
481
|
-
|
|
855
|
+
bloomFilterOffset: columnMetadata.bloom_filter_offset === undefined
|
|
856
|
+
? undefined
|
|
857
|
+
: Number(columnMetadata.bloom_filter_offset),
|
|
858
|
+
bloomFilterByteLength: columnMetadata.bloom_filter_length,
|
|
859
|
+
statistics,
|
|
860
|
+
sizeStatistics,
|
|
861
|
+
geospatialStatistics
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
/** Normalizes optional Parquet size statistics without inventing missing counts. */
|
|
865
|
+
function createSizeStatistics(statistics) {
|
|
866
|
+
if (!statistics)
|
|
867
|
+
return undefined;
|
|
868
|
+
const result = {
|
|
869
|
+
unencodedByteArrayDataBytes: statistics.unencoded_byte_array_data_bytes === undefined
|
|
870
|
+
? undefined
|
|
871
|
+
: toSafeNumber(statistics.unencoded_byte_array_data_bytes),
|
|
872
|
+
repetitionLevelHistogram: normalizeSizeStatisticsList(statistics.repetition_level_histogram),
|
|
873
|
+
definitionLevelHistogram: normalizeSizeStatisticsList(statistics.definition_level_histogram)
|
|
874
|
+
};
|
|
875
|
+
if (result.unencodedByteArrayDataBytes === undefined &&
|
|
876
|
+
result.repetitionLevelHistogram === undefined &&
|
|
877
|
+
result.definitionLevelHistogram === undefined) {
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
return Object.freeze({
|
|
881
|
+
...result,
|
|
882
|
+
repetitionLevelHistogram: result.repetitionLevelHistogram
|
|
883
|
+
? Object.freeze(result.repetitionLevelHistogram)
|
|
884
|
+
: undefined,
|
|
885
|
+
definitionLevelHistogram: result.definitionLevelHistogram
|
|
886
|
+
? Object.freeze(result.definitionLevelHistogram)
|
|
887
|
+
: undefined
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
function toSafeNumber(value) {
|
|
891
|
+
const numberValue = typeof value === 'number' ? value : value.toNumber?.();
|
|
892
|
+
return numberValue !== undefined && Number.isSafeInteger(numberValue) && numberValue >= 0
|
|
893
|
+
? numberValue
|
|
894
|
+
: undefined;
|
|
895
|
+
}
|
|
896
|
+
/** Converts a Thrift int64 histogram only when every entry is a safe non-negative integer. */
|
|
897
|
+
function normalizeSizeStatisticsList(values) {
|
|
898
|
+
if (!values)
|
|
899
|
+
return undefined;
|
|
900
|
+
const normalized = values.map(toSafeNumber);
|
|
901
|
+
return normalized.every((value) => value !== undefined) ? normalized : undefined;
|
|
902
|
+
}
|
|
903
|
+
/** Normalizes native Parquet geospatial statistics from the decoded footer. */
|
|
904
|
+
function createGeospatialStatistics(statistics) {
|
|
905
|
+
if (!statistics)
|
|
906
|
+
return undefined;
|
|
907
|
+
const bbox = statistics.bbox
|
|
908
|
+
? Object.freeze({
|
|
909
|
+
xmin: statistics.bbox.xmin,
|
|
910
|
+
xmax: statistics.bbox.xmax,
|
|
911
|
+
ymin: statistics.bbox.ymin,
|
|
912
|
+
ymax: statistics.bbox.ymax,
|
|
913
|
+
zmin: statistics.bbox.zmin,
|
|
914
|
+
zmax: statistics.bbox.zmax,
|
|
915
|
+
mmin: statistics.bbox.mmin,
|
|
916
|
+
mmax: statistics.bbox.mmax
|
|
917
|
+
})
|
|
918
|
+
: undefined;
|
|
919
|
+
return Object.freeze({
|
|
920
|
+
bbox,
|
|
921
|
+
geometryTypes: statistics.geospatial_types
|
|
922
|
+
? Object.freeze([...statistics.geospatial_types])
|
|
923
|
+
: undefined
|
|
482
924
|
});
|
|
483
925
|
}
|
|
484
926
|
/** Decodes optional footer statistics into the column's logical value representation. */
|
|
@@ -547,7 +989,7 @@ function decodeStatisticsValueSafely(bytes, field) {
|
|
|
547
989
|
}
|
|
548
990
|
}
|
|
549
991
|
/** Normalizes one decoded row group and derives its compressed byte length. */
|
|
550
|
-
function createRowGroupMetadata(index, rowOffset, rowCount, uncompressedByteLength, columns) {
|
|
992
|
+
function createRowGroupMetadata(index, rowOffset, rowCount, uncompressedByteLength, columns, sortingColumns) {
|
|
551
993
|
const compressedByteLength = columns.reduce((sum, column) => sum + column.compressedByteLength, 0);
|
|
552
994
|
return Object.freeze({
|
|
553
995
|
index,
|
|
@@ -557,8 +999,36 @@ function createRowGroupMetadata(index, rowOffset, rowCount, uncompressedByteLeng
|
|
|
557
999
|
uncompressedSize: uncompressedByteLength,
|
|
558
1000
|
compressedByteLength,
|
|
559
1001
|
compressedSize: compressedByteLength,
|
|
560
|
-
columns: Object.freeze(columns)
|
|
1002
|
+
columns: Object.freeze(columns),
|
|
1003
|
+
sortingColumns: Object.freeze((sortingColumns || []).map(sortingColumn => Object.freeze({
|
|
1004
|
+
columnIndex: sortingColumn.column_idx,
|
|
1005
|
+
descending: sortingColumn.descending,
|
|
1006
|
+
nullsFirst: sortingColumn.nulls_first
|
|
1007
|
+
})))
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
/** Describes a phase that reads complete column chunks because no page index is available. */
|
|
1011
|
+
function createFullColumnScanPlan(rowGroup, rowGroupIndex, phase, columns) {
|
|
1012
|
+
const selectedColumnChunks = rowGroup.columns.filter(columnChunk => {
|
|
1013
|
+
const path = columnChunk.meta_data?.path_in_schema;
|
|
1014
|
+
return Boolean(path && (columns.length === 0 || columns.some(column => pathMatchesSelection(column, path))));
|
|
561
1015
|
});
|
|
1016
|
+
const rowCount = Number(rowGroup.num_rows);
|
|
1017
|
+
return Object.freeze({
|
|
1018
|
+
rowGroupIndex,
|
|
1019
|
+
phase,
|
|
1020
|
+
columns: Object.freeze(columns.map(column => Object.freeze([...column]))),
|
|
1021
|
+
rowRanges: Object.freeze([Object.freeze({ start: 0, end: rowCount })]),
|
|
1022
|
+
indexesRead: 0,
|
|
1023
|
+
totalPages: 0,
|
|
1024
|
+
selectedPages: 0,
|
|
1025
|
+
rowsPruned: 0,
|
|
1026
|
+
ranges: Object.freeze(selectedColumnChunks.map(columnChunk => Object.freeze(getColumnChunkRange(columnChunk))))
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
/** Returns whether a requested path selects a footer leaf or one of its nested children. */
|
|
1030
|
+
function pathMatchesSelection(selection, path) {
|
|
1031
|
+
return selection.length <= path.length && selection.every((part, index) => part === path[index]);
|
|
562
1032
|
}
|
|
563
1033
|
/** Returns the complete contiguous byte range occupied by one Parquet column chunk. */
|
|
564
1034
|
function getColumnChunkRange(columnChunk) {
|
|
@@ -623,11 +1093,8 @@ function createParquetBatchFromArrow(metadata, schema, rowGroupIndex, rowGroupRo
|
|
|
623
1093
|
: undefined
|
|
624
1094
|
});
|
|
625
1095
|
return {
|
|
626
|
-
|
|
627
|
-
shape: 'arrow-table',
|
|
1096
|
+
...makeTableScanBatch({ shape: 'arrow-table', schema, data }),
|
|
628
1097
|
schemaType: 'explicit',
|
|
629
|
-
schema,
|
|
630
|
-
data,
|
|
631
1098
|
length: rowCount,
|
|
632
1099
|
metadata: provenance,
|
|
633
1100
|
...provenance
|
|
@@ -731,6 +1198,9 @@ function createParquetTelemetry() {
|
|
|
731
1198
|
rowGroupsRequested: 0,
|
|
732
1199
|
rowGroupsPruned: 0,
|
|
733
1200
|
rowGroupsPrunedByStatistics: 0,
|
|
1201
|
+
rowGroupsPrunedByBloomFilter: 0,
|
|
1202
|
+
bloomFiltersRead: 0,
|
|
1203
|
+
bloomFilterBytesRead: 0,
|
|
734
1204
|
rowGroupsPrunedByPageIndex: 0,
|
|
735
1205
|
pageIndexesRead: 0,
|
|
736
1206
|
pagesRead: 0,
|
|
@@ -745,6 +1215,34 @@ function createParquetTelemetry() {
|
|
|
745
1215
|
failedReadCount: 0
|
|
746
1216
|
};
|
|
747
1217
|
}
|
|
1218
|
+
/** Converts cumulative Parquet counters into one portable read-scoped execution snapshot. */
|
|
1219
|
+
function createParquetScanExecutionTelemetry(before, after, startedAt, status, earlyTerminationReason, error) {
|
|
1220
|
+
const delta = Object.fromEntries(Object.keys(after).map(key => {
|
|
1221
|
+
const telemetryKey = key;
|
|
1222
|
+
return [key, after[telemetryKey] - before[telemetryKey]];
|
|
1223
|
+
}));
|
|
1224
|
+
const rowsRead = Math.max(delta.predicateRowsTested, delta.rowsEmitted);
|
|
1225
|
+
return Object.freeze({
|
|
1226
|
+
status,
|
|
1227
|
+
sourcesPlanned: 1,
|
|
1228
|
+
sourcesRead: status === 'early-terminated' && earlyTerminationReason === 'limit' && rowsRead === 0 ? 0 : 1,
|
|
1229
|
+
batchesRead: delta.batchesEmitted,
|
|
1230
|
+
batchesDecoded: delta.rowGroupsDecoded,
|
|
1231
|
+
rowsRead,
|
|
1232
|
+
rowsTested: delta.predicateRowsTested || undefined,
|
|
1233
|
+
rowsRetained: delta.predicateRowsMatched || undefined,
|
|
1234
|
+
rowsReturned: delta.rowsEmitted,
|
|
1235
|
+
bytesRead: delta.downloadedBytes,
|
|
1236
|
+
bytesFetched: delta.downloadedBytes,
|
|
1237
|
+
filesOpened: rowsRead || delta.downloadedBytes ? 1 : 0,
|
|
1238
|
+
tasksOpened: delta.rowGroupsDecoded,
|
|
1239
|
+
rowsPruned: delta.rowsPrunedByPageIndex,
|
|
1240
|
+
durationMilliseconds: Date.now() - startedAt,
|
|
1241
|
+
earlyTerminationReason,
|
|
1242
|
+
details: Object.freeze({ ...delta }),
|
|
1243
|
+
...(error === undefined ? {} : { error })
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
748
1246
|
/** Returns a monotonic timestamp when available and falls back to wall-clock time. */
|
|
749
1247
|
function getCurrentTime() {
|
|
750
1248
|
return globalThis.performance?.now() ?? Date.now();
|
|
@@ -805,6 +1303,121 @@ function getSourceName(data, url) {
|
|
|
805
1303
|
: 'parquet';
|
|
806
1304
|
return sourceName.replace(/\.parquet$/i, '') || 'parquet';
|
|
807
1305
|
}
|
|
1306
|
+
/** Reconstructs exact row-group indexes from worker predicate batches. */
|
|
1307
|
+
function getWorkerRowIndices(workerResult) {
|
|
1308
|
+
if (!workerResult)
|
|
1309
|
+
return [];
|
|
1310
|
+
return workerResult.batches.flatMap(batch => batch.rowGroupRowIndices
|
|
1311
|
+
? [...batch.rowGroupRowIndices]
|
|
1312
|
+
: Array.from({ length: batch.rowCount }, (_, index) => batch.rowGroupRowOffset + index));
|
|
1313
|
+
}
|
|
1314
|
+
/** Gathers projected worker Arrow batches through the shared indexed Arrow table utility. */
|
|
1315
|
+
function gatherWorkerProjectedRows(workerResult, rowIndices, batchSize) {
|
|
1316
|
+
const tables = workerResult.batches.map(batch => hydrateArrowTable(batch.arrowTable));
|
|
1317
|
+
if (tables.length === 0) {
|
|
1318
|
+
return { ...workerResult, rowCount: 0, batches: [] };
|
|
1319
|
+
}
|
|
1320
|
+
const table = tables.length === 1 ? tables[0] : tables[0].concat(...tables.slice(1));
|
|
1321
|
+
const indexedTable = new IndexedArrowTable(table, rowIndices);
|
|
1322
|
+
const outputBatchSize = batchSize || Math.max(rowIndices.length, 1);
|
|
1323
|
+
const batches = [];
|
|
1324
|
+
for (let offset = 0; offset < rowIndices.length; offset += outputBatchSize) {
|
|
1325
|
+
const end = Math.min(offset + outputBatchSize, rowIndices.length);
|
|
1326
|
+
const batchIndexes = rowIndices.slice(offset, end);
|
|
1327
|
+
const batchTable = indexedTable.slice(offset, end).materializeArrowTable();
|
|
1328
|
+
batches.push({
|
|
1329
|
+
rowGroupRowOffset: batchIndexes[0],
|
|
1330
|
+
rowCount: batchIndexes.length,
|
|
1331
|
+
rowGroupRowIndices: [...batchIndexes],
|
|
1332
|
+
arrowTable: dehydrateArrowTable(batchTable)
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
sourceRowCount: workerResult.sourceRowCount,
|
|
1337
|
+
rowCount: rowIndices.length,
|
|
1338
|
+
batches,
|
|
1339
|
+
decodeDurationMs: workerResult.decodeDurationMs,
|
|
1340
|
+
arrowConversionDurationMs: workerResult.arrowConversionDurationMs
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
/** Applies only proven-negative Bloom-filter checks to candidate row groups. */
|
|
1344
|
+
async function filterParquetRowGroupsWithBloomFilters(initialization, rowGroupIndices, predicate, signal) {
|
|
1345
|
+
const rowGroups = [];
|
|
1346
|
+
let filtersRead = 0;
|
|
1347
|
+
let bytesRead = 0;
|
|
1348
|
+
for (const rowGroupIndex of rowGroupIndices) {
|
|
1349
|
+
const rowGroup = initialization.metadata.rowGroups[rowGroupIndex];
|
|
1350
|
+
const probes = getParquetBloomFilterProbes(predicate, rowGroup);
|
|
1351
|
+
let keepRowGroup = true;
|
|
1352
|
+
for (const probe of probes) {
|
|
1353
|
+
if (probe.column.bloomFilterOffset === undefined ||
|
|
1354
|
+
probe.column.bloomFilterByteLength === undefined) {
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
let field;
|
|
1358
|
+
try {
|
|
1359
|
+
field = initialization.parquetSchema.findField([...probe.column.path]);
|
|
1360
|
+
}
|
|
1361
|
+
catch {
|
|
1362
|
+
throwIfAborted(signal);
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
if (!field.primitiveType)
|
|
1366
|
+
continue;
|
|
1367
|
+
let filter;
|
|
1368
|
+
try {
|
|
1369
|
+
const rawColumnOrdinal = initialization.fileMetadata.row_groups[rowGroupIndex].columns.findIndex(columnChunk => JSON.stringify(columnChunk.meta_data?.path_in_schema) ===
|
|
1370
|
+
JSON.stringify(probe.column.path));
|
|
1371
|
+
const rawColumnChunk = rawColumnOrdinal >= 0
|
|
1372
|
+
? initialization.fileMetadata.row_groups[rowGroupIndex].columns[rawColumnOrdinal]
|
|
1373
|
+
: undefined;
|
|
1374
|
+
const data = await initialization.file.read(probe.column.bloomFilterOffset, probe.column.bloomFilterByteLength, signal);
|
|
1375
|
+
const decodedBloomFilter = rawColumnChunk
|
|
1376
|
+
? await initialization.reader.decryptBloomFilter(toUint8Array(data), rowGroupIndex, rawColumnOrdinal, rawColumnChunk)
|
|
1377
|
+
: toUint8Array(data);
|
|
1378
|
+
filter = decodeParquetSplitBlockBloomFilter(decodedBloomFilter);
|
|
1379
|
+
filtersRead++;
|
|
1380
|
+
bytesRead += data.byteLength;
|
|
1381
|
+
}
|
|
1382
|
+
catch {
|
|
1383
|
+
throwIfAborted(signal);
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (filter.algorithm !== 'BLOCK' ||
|
|
1387
|
+
filter.hash !== 'XXHASH' ||
|
|
1388
|
+
filter.compression !== 'UNCOMPRESSED') {
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
const mayContainValue = probe.values.some(value => {
|
|
1392
|
+
try {
|
|
1393
|
+
if (value instanceof Date)
|
|
1394
|
+
return true;
|
|
1395
|
+
const physicalValue = getParquetBloomFilterPhysicalValue(value, field);
|
|
1396
|
+
const encoded = encodeParquetBloomFilterValue(physicalValue, field.primitiveType, field.typeLength);
|
|
1397
|
+
return checkParquetSplitBlockBloomFilter(filter.bitset, hashParquetBloomFilterValue(encoded));
|
|
1398
|
+
}
|
|
1399
|
+
catch {
|
|
1400
|
+
throwIfAborted(signal);
|
|
1401
|
+
return true;
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
if (!mayContainValue) {
|
|
1405
|
+
keepRowGroup = false;
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (keepRowGroup)
|
|
1410
|
+
rowGroups.push(rowGroupIndex);
|
|
1411
|
+
}
|
|
1412
|
+
return { rowGroupIndices: rowGroups, filtersRead, bytesRead };
|
|
1413
|
+
}
|
|
1414
|
+
/** Converts logical predicate values into the physical values stored in Bloom filters. */
|
|
1415
|
+
function getParquetBloomFilterPhysicalValue(value, field) {
|
|
1416
|
+
if (field.originalType?.startsWith('DECIMAL_')) {
|
|
1417
|
+
return toPrimitive(field.originalType, value, field);
|
|
1418
|
+
}
|
|
1419
|
+
return value;
|
|
1420
|
+
}
|
|
808
1421
|
/** Returns true when at least one HTTP object validator was captured. */
|
|
809
1422
|
function hasObjectVersion(version) {
|
|
810
1423
|
return Boolean(version?.etag || version?.lastModified);
|