@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
|
@@ -2,13 +2,42 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {hydrateArrowTable} from '@loaders.gl/arrow';
|
|
5
|
+
import {dehydrateArrowTable, hydrateArrowTable, IndexedArrowTable} from '@loaders.gl/arrow';
|
|
6
6
|
import type {CoreAPI, ReadableFile, SourceLoader} from '@loaders.gl/loader-utils';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
BlobFile,
|
|
9
|
+
DataSource,
|
|
10
|
+
createScanQueryMetadata,
|
|
11
|
+
emitScanExecutionTelemetry,
|
|
12
|
+
explainTableQuery,
|
|
13
|
+
isBrowser,
|
|
14
|
+
makeTableScanBatch,
|
|
15
|
+
validateTableQueryLimit
|
|
16
|
+
} from '@loaders.gl/loader-utils';
|
|
17
|
+
import type {
|
|
18
|
+
ScanColumnRole,
|
|
19
|
+
ScanExecutionTelemetry,
|
|
20
|
+
ScanQueryMetadata,
|
|
21
|
+
ScanQueryMetadataOptions,
|
|
22
|
+
TableScanSource
|
|
23
|
+
} from '@loaders.gl/loader-utils';
|
|
8
24
|
import type {ArrayType, ArrowTable, Schema} from '@loaders.gl/schema';
|
|
9
25
|
import {convertTable} from '@loaders.gl/schema-utils';
|
|
26
|
+
import {getGeoMetadata} from '@loaders.gl/gis';
|
|
10
27
|
|
|
11
28
|
import {getSchemaFromParquetReader} from './lib/parsers/get-parquet-schema';
|
|
29
|
+
import {
|
|
30
|
+
canGeoParquetRowGroupMatch,
|
|
31
|
+
combineParquetPredicates,
|
|
32
|
+
createGeoParquetBoundingBoxPredicate
|
|
33
|
+
} from './lib/geo/geoparquet-covering';
|
|
34
|
+
import {
|
|
35
|
+
decodeParquetSplitBlockBloomFilter,
|
|
36
|
+
encodeParquetBloomFilterValue,
|
|
37
|
+
hashParquetBloomFilterValue,
|
|
38
|
+
checkParquetSplitBlockBloomFilter
|
|
39
|
+
} from './lib/parquet-bloom-filter';
|
|
40
|
+
import {getParquetBloomFilterProbes} from './lib/parquet-bloom-filter-planner';
|
|
12
41
|
import {
|
|
13
42
|
canParquetRowGroupMatch,
|
|
14
43
|
copyParquetPredicate,
|
|
@@ -31,28 +60,43 @@ import {
|
|
|
31
60
|
type ParquetSourceWorkerOptions,
|
|
32
61
|
type ParquetSourceWorkerResult
|
|
33
62
|
} from './lib/parquet-source-worker-types';
|
|
63
|
+
import {sliceParquetBatch} from './lib/slice-parquet-batch';
|
|
34
64
|
import {ParquetRangeFile} from './lib/sources/parquet-range-file';
|
|
65
|
+
import {toPrimitive} from './parquetjs/schema/types';
|
|
66
|
+
import type {ParquetType} from './parquetjs/schema/declare';
|
|
35
67
|
import {
|
|
68
|
+
PARQUET_TABLE_QUERY_CAPABILITIES,
|
|
36
69
|
PARQUET_SOURCE_CAPABILITIES,
|
|
37
70
|
type ParquetSourceCapabilities
|
|
38
71
|
} from './parquet-source-capabilities';
|
|
39
72
|
import {ParquetSourceLoader as ParquetSourceLoaderMetadata} from './parquet-source-loader-types';
|
|
73
|
+
import type {
|
|
74
|
+
ParquetEncodedPageBatch,
|
|
75
|
+
ParquetEncodedPageReadOptions,
|
|
76
|
+
ParquetDeferredPageFilter
|
|
77
|
+
} from './parquet-encoded-page-types';
|
|
40
78
|
import type {
|
|
41
79
|
ParquetBatch,
|
|
42
80
|
ParquetColumnChunkMetadata,
|
|
43
81
|
ParquetColumnChunkStatistics,
|
|
82
|
+
ParquetColumnChunkSizeStatistics,
|
|
83
|
+
ParquetGeospatialStatistics,
|
|
44
84
|
ParquetMetadataRequestOptions,
|
|
45
85
|
ParquetObjectVersion,
|
|
86
|
+
ParquetPageScanPlan,
|
|
46
87
|
ParquetPredicate,
|
|
47
88
|
ParquetRowGroupMetadata,
|
|
89
|
+
ParquetSortingColumn,
|
|
48
90
|
ParquetSourceBatch,
|
|
49
91
|
ParquetSourceLoaderOptions,
|
|
50
92
|
ParquetSourceMetadata,
|
|
51
93
|
ParquetSourceReadOptions,
|
|
94
|
+
ParquetSourceExplain,
|
|
52
95
|
ParquetTelemetry,
|
|
53
96
|
ParquetTelemetryEvent
|
|
54
97
|
} from './parquet-source-types';
|
|
55
98
|
export {
|
|
99
|
+
PARQUET_TABLE_QUERY_CAPABILITIES,
|
|
56
100
|
PARQUET_SOURCE_CAPABILITIES,
|
|
57
101
|
type ParquetSourceCapabilities
|
|
58
102
|
} from './parquet-source-capabilities';
|
|
@@ -62,6 +106,8 @@ import {
|
|
|
62
106
|
Encoding,
|
|
63
107
|
type ColumnChunk,
|
|
64
108
|
type FileMetaData,
|
|
109
|
+
type GeospatialStatistics as ParquetThriftGeospatialStatistics,
|
|
110
|
+
type RowGroup,
|
|
65
111
|
type Statistics as ParquetThriftStatistics
|
|
66
112
|
} from './parquetjs/parquet-thrift/index';
|
|
67
113
|
import {ParquetReader} from './parquetjs/parser/parquet-reader';
|
|
@@ -80,14 +126,28 @@ import {
|
|
|
80
126
|
} from './parquetjs/utils/binary-utils';
|
|
81
127
|
import {fieldIndexOf} from './parquetjs/utils/read-utils';
|
|
82
128
|
|
|
129
|
+
export type {
|
|
130
|
+
ParquetEncodedColumnChunk,
|
|
131
|
+
ParquetEncodedPage,
|
|
132
|
+
ParquetEncodedPageBatch,
|
|
133
|
+
ParquetEncodedPageReadOptions,
|
|
134
|
+
ParquetEncodedPageSection,
|
|
135
|
+
ParquetDeferredPageFilter,
|
|
136
|
+
ParquetPageCompressionState
|
|
137
|
+
} from './parquet-encoded-page-types';
|
|
83
138
|
export type {
|
|
84
139
|
ParquetBatch,
|
|
85
140
|
ParquetBatchMetadata,
|
|
86
141
|
ParquetBatchProvenance,
|
|
142
|
+
ParquetBoundingBox,
|
|
87
143
|
ParquetColumnChunkMetadata,
|
|
88
144
|
ParquetColumnChunkStatistics,
|
|
145
|
+
ParquetColumnChunkSizeStatistics,
|
|
146
|
+
ParquetGeospatialBoundingBox,
|
|
147
|
+
ParquetGeospatialStatistics,
|
|
89
148
|
ParquetMetadataRequestOptions,
|
|
90
149
|
ParquetObjectVersion,
|
|
150
|
+
ParquetPageScanPlan,
|
|
91
151
|
ParquetComparisonPredicate,
|
|
92
152
|
ParquetInPredicate,
|
|
93
153
|
ParquetLogicalPredicate,
|
|
@@ -99,10 +159,12 @@ export type {
|
|
|
99
159
|
ParquetRangeRequestOptions,
|
|
100
160
|
ParquetReadOptions,
|
|
101
161
|
ParquetRowGroupMetadata,
|
|
162
|
+
ParquetSortingColumn,
|
|
102
163
|
ParquetSourceBatch,
|
|
103
164
|
ParquetSourceLoaderOptions,
|
|
104
165
|
ParquetSourceMetadata,
|
|
105
166
|
ParquetSourceReadOptions,
|
|
167
|
+
ParquetSourceExplain,
|
|
106
168
|
ParquetTelemetry,
|
|
107
169
|
ParquetTelemetryEvent
|
|
108
170
|
} from './parquet-source-types';
|
|
@@ -149,6 +211,27 @@ type SettledParquetRowGroupRead =
|
|
|
149
211
|
result?: never;
|
|
150
212
|
};
|
|
151
213
|
|
|
214
|
+
type ParquetPhysicalRowGroupPlan = {
|
|
215
|
+
/** Predicate including any GeoParquet covering predicate derived from a bounding box. */
|
|
216
|
+
predicate?: ParquetPredicate;
|
|
217
|
+
/** Row groups retained by all physical pruning stages. */
|
|
218
|
+
rowGroupIndices: number[];
|
|
219
|
+
/** Candidate row groups explicitly requested by the caller. */
|
|
220
|
+
requested: number;
|
|
221
|
+
/** Row groups rejected by the caller metadata callback. */
|
|
222
|
+
prunedByCallback: number;
|
|
223
|
+
/** Row groups rejected by native geospatial statistics. */
|
|
224
|
+
prunedBySpatial: number;
|
|
225
|
+
/** Row groups rejected by column or covering statistics. */
|
|
226
|
+
prunedByStatistics: number;
|
|
227
|
+
/** Row groups rejected by split-block Bloom filters. */
|
|
228
|
+
prunedByBloomFilter: number;
|
|
229
|
+
/** Bloom-filter payloads read while planning. */
|
|
230
|
+
bloomFiltersRead: number;
|
|
231
|
+
/** Bloom-filter bytes read while planning. */
|
|
232
|
+
bloomFilterBytesRead: number;
|
|
233
|
+
};
|
|
234
|
+
|
|
152
235
|
const {
|
|
153
236
|
preload: _preloadParquetSourceLoader,
|
|
154
237
|
createDataSource: _createMetadataDataSource,
|
|
@@ -169,7 +252,12 @@ export const ParquetSourceLoaderWithParser = {
|
|
|
169
252
|
export {ParquetSourceLoaderWithParser as ParquetSourceLoader};
|
|
170
253
|
|
|
171
254
|
/** Reusable Parquet source that caches footer/schema state and selectively reads byte ranges. */
|
|
172
|
-
export class ParquetSource
|
|
255
|
+
export class ParquetSource
|
|
256
|
+
extends DataSource<string | Blob, ParquetSourceLoaderOptions>
|
|
257
|
+
implements TableScanSource<ParquetSourceBatch, ParquetPredicate>
|
|
258
|
+
{
|
|
259
|
+
/** Common projection, predicate, limit, streaming, and cancellation capabilities. */
|
|
260
|
+
readonly tableQueryCapabilities = PARQUET_TABLE_QUERY_CAPABILITIES;
|
|
173
261
|
/** Immutable feature support for the current range-backed source implementation. */
|
|
174
262
|
readonly capabilities: ParquetSourceCapabilities = PARQUET_SOURCE_CAPABILITIES;
|
|
175
263
|
/** Shared initialization for this source instance. */
|
|
@@ -212,6 +300,179 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
212
300
|
};
|
|
213
301
|
}
|
|
214
302
|
|
|
303
|
+
/** Discovers schema, capabilities, and footer statistics without decoding Parquet data pages. */
|
|
304
|
+
async getQueryMetadata(options: ScanQueryMetadataOptions = {}): Promise<ScanQueryMetadata> {
|
|
305
|
+
const [schema, metadata] = await Promise.all([
|
|
306
|
+
this.getSchema({signal: options.signal}),
|
|
307
|
+
this.getMetadata({signal: options.signal})
|
|
308
|
+
]);
|
|
309
|
+
return createScanQueryMetadata({
|
|
310
|
+
sourceType: 'parquet',
|
|
311
|
+
queryType: 'table',
|
|
312
|
+
execution: {status: 'supported', method: 'read'},
|
|
313
|
+
name: metadata.name,
|
|
314
|
+
schema,
|
|
315
|
+
capabilities: {table: this.tableQueryCapabilities},
|
|
316
|
+
columnRoles: getParquetColumnRoles(schema.fields.map(field => field.name)),
|
|
317
|
+
statistics: {rowCount: metadata.rowCount, byteLength: metadata.fileByteLength}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Plans a portable query using Parquet metadata without decoding data pages. */
|
|
322
|
+
async getScanPlan(options: ParquetSourceReadOptions = {}): Promise<ParquetSourceExplain> {
|
|
323
|
+
const readOptions = this.getReadOptions(options);
|
|
324
|
+
const initialization = await this.getInitialization(readOptions.signal);
|
|
325
|
+
const sourceColumnNames = initialization.schema.fields.map(field => field.name);
|
|
326
|
+
const signal = readOptions.signal ?? new AbortController().signal;
|
|
327
|
+
const physicalPlan = await this.planRowGroups(initialization, readOptions, signal);
|
|
328
|
+
const pagePlans = await this.planPages(initialization, readOptions, physicalPlan, signal);
|
|
329
|
+
const explanation = explainTableQuery(
|
|
330
|
+
sourceColumnNames,
|
|
331
|
+
{
|
|
332
|
+
columns: readOptions.columns,
|
|
333
|
+
predicate: physicalPlan.predicate,
|
|
334
|
+
limit: readOptions.limit
|
|
335
|
+
},
|
|
336
|
+
PARQUET_TABLE_QUERY_CAPABILITIES
|
|
337
|
+
);
|
|
338
|
+
return Object.freeze({
|
|
339
|
+
...explanation,
|
|
340
|
+
source: 'parquet' as const,
|
|
341
|
+
rowGroups: Object.freeze({
|
|
342
|
+
indices: Object.freeze([...physicalPlan.rowGroupIndices]),
|
|
343
|
+
requested: physicalPlan.requested,
|
|
344
|
+
selected: physicalPlan.rowGroupIndices.length,
|
|
345
|
+
prunedByCallback: physicalPlan.prunedByCallback,
|
|
346
|
+
prunedBySpatial: physicalPlan.prunedBySpatial,
|
|
347
|
+
prunedByStatistics: physicalPlan.prunedByStatistics,
|
|
348
|
+
prunedByBloomFilter: physicalPlan.prunedByBloomFilter
|
|
349
|
+
}),
|
|
350
|
+
bloomFilters: Object.freeze({
|
|
351
|
+
read: physicalPlan.bloomFiltersRead,
|
|
352
|
+
bytesRead: physicalPlan.bloomFilterBytesRead
|
|
353
|
+
}),
|
|
354
|
+
pages: Object.freeze({
|
|
355
|
+
rowGroupsPlanned: new Set(pagePlans.map(plan => plan.rowGroupIndex)).size,
|
|
356
|
+
indexesRead: pagePlans.reduce((sum, plan) => sum + plan.indexesRead, 0),
|
|
357
|
+
total: pagePlans.reduce((sum, plan) => sum + plan.totalPages, 0),
|
|
358
|
+
selected: pagePlans.reduce((sum, plan) => sum + plan.selectedPages, 0),
|
|
359
|
+
rowsPruned: pagePlans.reduce((sum, plan) => sum + plan.rowsPruned, 0),
|
|
360
|
+
plans: Object.freeze(pagePlans)
|
|
361
|
+
})
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Explains the common logical query and Parquet-specific physical scan plan. */
|
|
366
|
+
async explain(options: ParquetSourceReadOptions = {}): Promise<ParquetSourceExplain> {
|
|
367
|
+
return await this.getScanPlan(options);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Executes a previously computed physical plan while preserving its row-group decisions. */
|
|
371
|
+
async *executeScanPlan(
|
|
372
|
+
plan: ParquetSourceExplain,
|
|
373
|
+
options: ParquetSourceReadOptions = {}
|
|
374
|
+
): AsyncIterable<ParquetSourceBatch> {
|
|
375
|
+
if (plan.source !== 'parquet') {
|
|
376
|
+
throw new Error('ParquetSource can only execute Parquet scan plans');
|
|
377
|
+
}
|
|
378
|
+
const predicateStep = plan.plan.find(step => step.kind === 'filter');
|
|
379
|
+
const limitStep = plan.plan.find(step => step.kind === 'limit');
|
|
380
|
+
yield* this.read({
|
|
381
|
+
...options,
|
|
382
|
+
columns: options.columns ?? plan.outputColumns,
|
|
383
|
+
predicate:
|
|
384
|
+
options.predicate ??
|
|
385
|
+
(predicateStep?.kind === 'filter' ? predicateStep.predicate : undefined),
|
|
386
|
+
limit: options.limit ?? (limitStep?.kind === 'limit' ? limitStep.limit : undefined),
|
|
387
|
+
rowGroups: plan.rowGroups.indices
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Common scan-architecture alias for selective Parquet reads. */
|
|
392
|
+
scan(options: ParquetSourceReadOptions = {}): AsyncIterable<ParquetSourceBatch> {
|
|
393
|
+
return this.read(options);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Reads candidate encoded pages without decoding values or constructing Arrow arrays.
|
|
398
|
+
*
|
|
399
|
+
* Footer statistics and Bloom filters prune complete row groups. Any exact predicate or spatial
|
|
400
|
+
* filter is returned as residual work, and its hidden columns are included in the page batch.
|
|
401
|
+
*/
|
|
402
|
+
async *readPages(
|
|
403
|
+
options: ParquetEncodedPageReadOptions = {}
|
|
404
|
+
): AsyncIterable<ParquetEncodedPageBatch> {
|
|
405
|
+
const readOptions = this.getReadOptions({
|
|
406
|
+
rowGroups: options.rowGroups,
|
|
407
|
+
columns: options.columns,
|
|
408
|
+
rowGroupFilter: options.rowGroupFilter,
|
|
409
|
+
predicate: options.predicate,
|
|
410
|
+
bbox: options.bbox,
|
|
411
|
+
geometryColumn: options.geometryColumn,
|
|
412
|
+
signal: options.signal
|
|
413
|
+
});
|
|
414
|
+
const readContext = createReadAbortContext(readOptions.signal);
|
|
415
|
+
this.activeReadControllers.add(readContext.abortController);
|
|
416
|
+
try {
|
|
417
|
+
const initialization = await this.getInitialization(readContext.abortController.signal);
|
|
418
|
+
await this.getCompressionInitialization();
|
|
419
|
+
throwIfAborted(readContext.abortController.signal);
|
|
420
|
+
const physicalPlan = await this.planRowGroups(
|
|
421
|
+
initialization,
|
|
422
|
+
readOptions,
|
|
423
|
+
readContext.abortController.signal
|
|
424
|
+
);
|
|
425
|
+
const projectedColumns = normalizeColumns(readOptions.columns, initialization.schema);
|
|
426
|
+
const predicateColumns = physicalPlan.predicate
|
|
427
|
+
? getParquetPredicateColumns(physicalPlan.predicate)
|
|
428
|
+
: [];
|
|
429
|
+
const geoMetadata = readOptions.bbox
|
|
430
|
+
? getGeoMetadata(initialization.schema.metadata)
|
|
431
|
+
: undefined;
|
|
432
|
+
const geometryColumn = readOptions.bbox
|
|
433
|
+
? readOptions.geometryColumn || geoMetadata?.primary_column
|
|
434
|
+
: undefined;
|
|
435
|
+
const filterColumns = [
|
|
436
|
+
...new Set([...predicateColumns, ...(geometryColumn ? [geometryColumn] : [])])
|
|
437
|
+
];
|
|
438
|
+
const decodedColumns =
|
|
439
|
+
projectedColumns.length === 0 ? [] : [...new Set([...projectedColumns, ...filterColumns])];
|
|
440
|
+
const columnList = decodedColumns.map(column => [column]);
|
|
441
|
+
const preserveCompression = new Set(options.preserveCompression ?? []);
|
|
442
|
+
const residualFilter: ParquetDeferredPageFilter | undefined =
|
|
443
|
+
readOptions.predicate || readOptions.bbox
|
|
444
|
+
? Object.freeze({
|
|
445
|
+
predicate: readOptions.predicate,
|
|
446
|
+
bbox: readOptions.bbox,
|
|
447
|
+
geometryColumn
|
|
448
|
+
})
|
|
449
|
+
: undefined;
|
|
450
|
+
for (const rowGroupIndex of physicalPlan.rowGroupIndices) {
|
|
451
|
+
throwIfAborted(readContext.abortController.signal);
|
|
452
|
+
const columns = await initialization.reader.readRowGroupEncodedPages(
|
|
453
|
+
initialization.parquetSchema,
|
|
454
|
+
initialization.fileMetadata.row_groups[rowGroupIndex],
|
|
455
|
+
columnList,
|
|
456
|
+
preserveCompression,
|
|
457
|
+
readContext.abortController.signal,
|
|
458
|
+
rowGroupIndex
|
|
459
|
+
);
|
|
460
|
+
yield Object.freeze({
|
|
461
|
+
shape: 'parquet-encoded-pages' as const,
|
|
462
|
+
rowGroup: initialization.metadata.rowGroups[rowGroupIndex],
|
|
463
|
+
projectedColumns: Object.freeze([...projectedColumns]),
|
|
464
|
+
filterColumns: Object.freeze(filterColumns),
|
|
465
|
+
columns,
|
|
466
|
+
residualFilter
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
} finally {
|
|
470
|
+
readContext.abortController.abort();
|
|
471
|
+
readContext.removeSignalListener();
|
|
472
|
+
this.activeReadControllers.delete(readContext.abortController);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
215
476
|
/** Returns a copy of cumulative transport, decode, conversion, and pruning telemetry. */
|
|
216
477
|
getTelemetry(): ParquetTelemetry {
|
|
217
478
|
return Object.freeze({...this.telemetry});
|
|
@@ -219,10 +480,26 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
219
480
|
|
|
220
481
|
/** Selectively fetches row groups and columns as ordered Arrow batches with source provenance. */
|
|
221
482
|
async *read(options: ParquetSourceReadOptions = {}): AsyncIterable<ParquetSourceBatch> {
|
|
483
|
+
const startedAt = Date.now();
|
|
484
|
+
const telemetryBefore = this.getTelemetry();
|
|
222
485
|
const readOptions = this.getReadOptions(options);
|
|
486
|
+
if (readOptions.limit === 0) {
|
|
487
|
+
emitScanExecutionTelemetry(
|
|
488
|
+
options.onTelemetry,
|
|
489
|
+
createParquetScanExecutionTelemetry(
|
|
490
|
+
telemetryBefore,
|
|
491
|
+
this.getTelemetry(),
|
|
492
|
+
startedAt,
|
|
493
|
+
'early-terminated',
|
|
494
|
+
'limit'
|
|
495
|
+
)
|
|
496
|
+
);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
223
499
|
const readContext = createReadAbortContext(readOptions.signal);
|
|
224
500
|
const inFlightReads = new Set<Promise<SettledParquetRowGroupRead>>();
|
|
225
501
|
let completed = false;
|
|
502
|
+
let limitReached = false;
|
|
226
503
|
let readError: unknown;
|
|
227
504
|
this.activeReadControllers.add(readContext.abortController);
|
|
228
505
|
|
|
@@ -231,33 +508,21 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
231
508
|
await this.getCompressionInitialization();
|
|
232
509
|
throwIfAborted(readContext.abortController.signal);
|
|
233
510
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
511
|
+
const physicalPlan = await this.planRowGroups(
|
|
512
|
+
initialization,
|
|
513
|
+
readOptions,
|
|
514
|
+
readContext.abortController.signal
|
|
237
515
|
);
|
|
238
|
-
const
|
|
239
|
-
? candidateRowGroupIndices.filter(rowGroupIndex =>
|
|
240
|
-
readOptions.rowGroupFilter!(initialization.metadata.rowGroups[rowGroupIndex])
|
|
241
|
-
)
|
|
242
|
-
: candidateRowGroupIndices;
|
|
243
|
-
const predicate = readOptions.predicate;
|
|
244
|
-
const availableColumns = new Set(initialization.schema.fields.map(field => field.name));
|
|
245
|
-
if (predicate) {
|
|
246
|
-
validateParquetPredicate(predicate, availableColumns);
|
|
247
|
-
}
|
|
248
|
-
const rowGroupIndices = predicate
|
|
249
|
-
? callbackFilteredRowGroupIndices.filter(rowGroupIndex =>
|
|
250
|
-
canParquetRowGroupMatch(predicate, initialization.metadata.rowGroups[rowGroupIndex])
|
|
251
|
-
)
|
|
252
|
-
: callbackFilteredRowGroupIndices;
|
|
253
|
-
const rowGroupsPrunedByStatistics =
|
|
254
|
-
callbackFilteredRowGroupIndices.length - rowGroupIndices.length;
|
|
516
|
+
const {predicate, rowGroupIndices} = physicalPlan;
|
|
255
517
|
this.recordTelemetry(
|
|
256
518
|
'row-group-prune',
|
|
257
519
|
{
|
|
258
|
-
rowGroupsRequested:
|
|
259
|
-
rowGroupsPruned:
|
|
260
|
-
rowGroupsPrunedByStatistics
|
|
520
|
+
rowGroupsRequested: physicalPlan.requested,
|
|
521
|
+
rowGroupsPruned: physicalPlan.requested - rowGroupIndices.length,
|
|
522
|
+
rowGroupsPrunedByStatistics: physicalPlan.prunedByStatistics,
|
|
523
|
+
rowGroupsPrunedByBloomFilter: physicalPlan.prunedByBloomFilter,
|
|
524
|
+
bloomFiltersRead: physicalPlan.bloomFiltersRead,
|
|
525
|
+
bloomFilterBytesRead: physicalPlan.bloomFilterBytesRead
|
|
261
526
|
},
|
|
262
527
|
{}
|
|
263
528
|
);
|
|
@@ -271,9 +536,12 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
271
536
|
const projectedSchema = projectSchema(initialization.schema, columns);
|
|
272
537
|
const projectedColumnNames = columns.length ? new Set(columns) : undefined;
|
|
273
538
|
const workerOptions = this.getWorkerOptions(concurrency, readContext.abortController.signal);
|
|
274
|
-
const decodeOnWorker =
|
|
539
|
+
const decodeOnWorker =
|
|
540
|
+
canDecodeParquetSourceOnWorker(workerOptions) &&
|
|
541
|
+
(!initialization.reader.encrypted || Boolean(this.options.parquet?.keyRetriever));
|
|
275
542
|
const scheduledReads = new Map<number, Promise<SettledParquetRowGroupRead>>();
|
|
276
543
|
let nextPositionToSchedule = 0;
|
|
544
|
+
let remainingRows = readOptions.limit ?? Number.POSITIVE_INFINITY;
|
|
277
545
|
|
|
278
546
|
const scheduleReads = (): void => {
|
|
279
547
|
while (
|
|
@@ -329,21 +597,30 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
329
597
|
{arrowConversionDurationMs: deserializationDurationMs},
|
|
330
598
|
{rowGroupIndex, durationMs: deserializationDurationMs}
|
|
331
599
|
);
|
|
332
|
-
const batch =
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
600
|
+
const batch = sliceParquetBatch(
|
|
601
|
+
createParquetBatchFromArrow(
|
|
602
|
+
initialization.metadata,
|
|
603
|
+
projectedSchema,
|
|
604
|
+
rowGroupIndex,
|
|
605
|
+
workerBatch.rowGroupRowOffset,
|
|
606
|
+
arrowTable,
|
|
607
|
+
workerBatch.rowCount,
|
|
608
|
+
workerBatch.rowGroupRowIndices
|
|
609
|
+
),
|
|
610
|
+
Math.min(workerBatch.rowCount, remainingRows)
|
|
340
611
|
);
|
|
341
612
|
this.recordTelemetry(
|
|
342
613
|
'batch',
|
|
343
|
-
{batchesEmitted: 1, rowsEmitted:
|
|
344
|
-
{rowGroupIndex, rowCount:
|
|
614
|
+
{batchesEmitted: 1, rowsEmitted: batch.length},
|
|
615
|
+
{rowGroupIndex, rowCount: batch.length}
|
|
345
616
|
);
|
|
346
617
|
yield batch;
|
|
618
|
+
remainingRows -= batch.length;
|
|
619
|
+
if (remainingRows === 0) {
|
|
620
|
+
limitReached = true;
|
|
621
|
+
completed = true;
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
347
624
|
}
|
|
348
625
|
continue;
|
|
349
626
|
}
|
|
@@ -356,7 +633,11 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
356
633
|
outputRowOffset += outputBatchSize
|
|
357
634
|
) {
|
|
358
635
|
throwIfAborted(readContext.abortController.signal);
|
|
359
|
-
const batchRowCount = Math.min(
|
|
636
|
+
const batchRowCount = Math.min(
|
|
637
|
+
outputBatchSize,
|
|
638
|
+
outputRowCount - outputRowOffset,
|
|
639
|
+
remainingRows
|
|
640
|
+
);
|
|
360
641
|
const batchRowIndices = rowIndices
|
|
361
642
|
? rowIndices!.slice(outputRowOffset, outputRowOffset + batchRowCount)
|
|
362
643
|
: undefined;
|
|
@@ -388,6 +669,12 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
388
669
|
{rowGroupIndex, rowCount: batchRowCount}
|
|
389
670
|
);
|
|
390
671
|
yield batch;
|
|
672
|
+
remainingRows -= batch.length;
|
|
673
|
+
if (remainingRows === 0) {
|
|
674
|
+
limitReached = true;
|
|
675
|
+
completed = true;
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
391
678
|
}
|
|
392
679
|
}
|
|
393
680
|
completed = true;
|
|
@@ -403,11 +690,201 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
403
690
|
if (!completed && readError === undefined) {
|
|
404
691
|
this.recordTelemetry('cancel', {cancellationCount: 1}, {});
|
|
405
692
|
}
|
|
693
|
+
const cancelled = readContext.abortController.signal.aborted || readOptions.signal?.aborted;
|
|
406
694
|
readContext.abortController.abort();
|
|
407
695
|
readContext.removeSignalListener();
|
|
408
696
|
this.activeReadControllers.delete(readContext.abortController);
|
|
409
697
|
await Promise.allSettled([...inFlightReads]);
|
|
698
|
+
const status: ScanExecutionTelemetry['status'] = readError
|
|
699
|
+
? cancelled
|
|
700
|
+
? 'cancelled'
|
|
701
|
+
: 'failed'
|
|
702
|
+
: limitReached || !completed
|
|
703
|
+
? 'early-terminated'
|
|
704
|
+
: 'completed';
|
|
705
|
+
emitScanExecutionTelemetry(
|
|
706
|
+
options.onTelemetry,
|
|
707
|
+
createParquetScanExecutionTelemetry(
|
|
708
|
+
telemetryBefore,
|
|
709
|
+
this.getTelemetry(),
|
|
710
|
+
startedAt,
|
|
711
|
+
status,
|
|
712
|
+
limitReached
|
|
713
|
+
? 'limit'
|
|
714
|
+
: !completed && readError === undefined
|
|
715
|
+
? 'consumer-return'
|
|
716
|
+
: undefined,
|
|
717
|
+
readError
|
|
718
|
+
)
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** Produces the physical row-group plan shared by explain and execution. */
|
|
724
|
+
private async planRowGroups(
|
|
725
|
+
initialization: ParquetSourceInitialization,
|
|
726
|
+
readOptions: ParquetSourceReadOptions,
|
|
727
|
+
signal: AbortSignal
|
|
728
|
+
): Promise<ParquetPhysicalRowGroupPlan> {
|
|
729
|
+
throwIfAborted(signal);
|
|
730
|
+
const candidateRowGroupIndices = normalizeRowGroupIndices(
|
|
731
|
+
readOptions.rowGroups,
|
|
732
|
+
initialization.fileMetadata.row_groups.length
|
|
733
|
+
);
|
|
734
|
+
const callbackFilteredRowGroupIndices = readOptions.rowGroupFilter
|
|
735
|
+
? candidateRowGroupIndices.filter(rowGroupIndex =>
|
|
736
|
+
readOptions.rowGroupFilter!(initialization.metadata.rowGroups[rowGroupIndex])
|
|
737
|
+
)
|
|
738
|
+
: candidateRowGroupIndices;
|
|
739
|
+
const spatiallyFilteredRowGroupIndices = readOptions.bbox
|
|
740
|
+
? callbackFilteredRowGroupIndices.filter(rowGroupIndex =>
|
|
741
|
+
canGeoParquetRowGroupMatch(
|
|
742
|
+
initialization.metadata,
|
|
743
|
+
initialization.metadata.rowGroups[rowGroupIndex],
|
|
744
|
+
readOptions.bbox!,
|
|
745
|
+
readOptions.geometryColumn
|
|
746
|
+
)
|
|
747
|
+
)
|
|
748
|
+
: callbackFilteredRowGroupIndices;
|
|
749
|
+
const spatialPredicate = readOptions.bbox
|
|
750
|
+
? createGeoParquetBoundingBoxPredicate(
|
|
751
|
+
initialization.metadata,
|
|
752
|
+
readOptions.bbox,
|
|
753
|
+
readOptions.geometryColumn
|
|
754
|
+
)
|
|
755
|
+
: undefined;
|
|
756
|
+
const predicate = combineParquetPredicates(readOptions.predicate, spatialPredicate);
|
|
757
|
+
if (predicate) {
|
|
758
|
+
validateParquetPredicate(
|
|
759
|
+
predicate,
|
|
760
|
+
new Set(initialization.schema.fields.map(field => field.name))
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
const statisticsRowGroupIndices = predicate
|
|
764
|
+
? spatiallyFilteredRowGroupIndices.filter(rowGroupIndex =>
|
|
765
|
+
canParquetRowGroupMatch(predicate, initialization.metadata.rowGroups[rowGroupIndex])
|
|
766
|
+
)
|
|
767
|
+
: spatiallyFilteredRowGroupIndices;
|
|
768
|
+
const bloomFilterResult = predicate
|
|
769
|
+
? await filterParquetRowGroupsWithBloomFilters(
|
|
770
|
+
initialization,
|
|
771
|
+
statisticsRowGroupIndices,
|
|
772
|
+
predicate,
|
|
773
|
+
signal
|
|
774
|
+
)
|
|
775
|
+
: {rowGroupIndices: statisticsRowGroupIndices, filtersRead: 0, bytesRead: 0};
|
|
776
|
+
return {
|
|
777
|
+
predicate,
|
|
778
|
+
rowGroupIndices: bloomFilterResult.rowGroupIndices,
|
|
779
|
+
requested: candidateRowGroupIndices.length,
|
|
780
|
+
prunedByCallback: candidateRowGroupIndices.length - callbackFilteredRowGroupIndices.length,
|
|
781
|
+
prunedBySpatial:
|
|
782
|
+
callbackFilteredRowGroupIndices.length - spatiallyFilteredRowGroupIndices.length,
|
|
783
|
+
prunedByStatistics:
|
|
784
|
+
spatiallyFilteredRowGroupIndices.length - statisticsRowGroupIndices.length,
|
|
785
|
+
prunedByBloomFilter:
|
|
786
|
+
statisticsRowGroupIndices.length - bloomFilterResult.rowGroupIndices.length,
|
|
787
|
+
bloomFiltersRead: bloomFilterResult.filtersRead,
|
|
788
|
+
bloomFilterBytesRead: bloomFilterResult.bytesRead
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/** Produces explainable page-index and byte-range plans for retained row groups. */
|
|
793
|
+
private async planPages(
|
|
794
|
+
initialization: ParquetSourceInitialization,
|
|
795
|
+
readOptions: ParquetSourceReadOptions,
|
|
796
|
+
rowGroupPlan: ParquetPhysicalRowGroupPlan,
|
|
797
|
+
signal: AbortSignal
|
|
798
|
+
): Promise<ParquetPageScanPlan[]> {
|
|
799
|
+
if (!rowGroupPlan.predicate) return [];
|
|
800
|
+
const columns = normalizeColumns(readOptions.columns, initialization.schema);
|
|
801
|
+
const predicateColumns = getParquetPredicateColumns(rowGroupPlan.predicate);
|
|
802
|
+
const decodedColumns =
|
|
803
|
+
columns.length === 0 ? [] : [...new Set([...columns, ...predicateColumns])];
|
|
804
|
+
const hasHiddenPredicateColumns =
|
|
805
|
+
columns.length > 0 && predicateColumns.some(column => !columns.includes(column));
|
|
806
|
+
const phases = hasHiddenPredicateColumns
|
|
807
|
+
? [
|
|
808
|
+
{
|
|
809
|
+
phase: 'predicate' as const,
|
|
810
|
+
columns: predicateColumns.map(column => [column]),
|
|
811
|
+
predicate: rowGroupPlan.predicate
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
phase: 'projection' as const,
|
|
815
|
+
columns: columns.map(column => [column]),
|
|
816
|
+
predicate: undefined
|
|
817
|
+
}
|
|
818
|
+
]
|
|
819
|
+
: [
|
|
820
|
+
{
|
|
821
|
+
phase: 'combined' as const,
|
|
822
|
+
columns: decodedColumns.map(column => [column]),
|
|
823
|
+
predicate: rowGroupPlan.predicate
|
|
824
|
+
}
|
|
825
|
+
];
|
|
826
|
+
const plans: ParquetPageScanPlan[] = [];
|
|
827
|
+
for (const rowGroupIndex of rowGroupPlan.rowGroupIndices) {
|
|
828
|
+
const rowGroup = initialization.fileMetadata.row_groups[rowGroupIndex];
|
|
829
|
+
let predicateProvedEmpty = false;
|
|
830
|
+
for (const phase of phases) {
|
|
831
|
+
throwIfAborted(signal);
|
|
832
|
+
if (phase.phase === 'projection' && predicateProvedEmpty) {
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
await initialization.reader.resolveColumnMetadata(rowGroup, rowGroupIndex, phase.columns);
|
|
836
|
+
const pagePlan = phase.predicate
|
|
837
|
+
? await createParquetPagePruningPlan(
|
|
838
|
+
initialization.file,
|
|
839
|
+
rowGroup,
|
|
840
|
+
initialization.parquetSchema,
|
|
841
|
+
phase.columns,
|
|
842
|
+
phase.predicate,
|
|
843
|
+
signal,
|
|
844
|
+
{
|
|
845
|
+
rowGroupOrdinal: rowGroupIndex,
|
|
846
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
847
|
+
decryptModule: (bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk) =>
|
|
848
|
+
initialization.reader.decryptIndexModule(
|
|
849
|
+
bytes,
|
|
850
|
+
module,
|
|
851
|
+
rowGroupOrdinal,
|
|
852
|
+
columnOrdinal,
|
|
853
|
+
columnChunk
|
|
854
|
+
)
|
|
855
|
+
}
|
|
856
|
+
)
|
|
857
|
+
: undefined;
|
|
858
|
+
plans.push(
|
|
859
|
+
pagePlan
|
|
860
|
+
? Object.freeze({
|
|
861
|
+
rowGroupIndex,
|
|
862
|
+
phase: phase.phase,
|
|
863
|
+
columns: Object.freeze(phase.columns.map(column => Object.freeze([...column]))),
|
|
864
|
+
rowRanges: Object.freeze(
|
|
865
|
+
pagePlan.rowRanges.map(rowRange => Object.freeze({...rowRange}))
|
|
866
|
+
),
|
|
867
|
+
indexesRead: pagePlan.indexCount,
|
|
868
|
+
totalPages: pagePlan.totalPageCount,
|
|
869
|
+
selectedPages: pagePlan.selectedPageCount,
|
|
870
|
+
rowsPruned: pagePlan.prunedRowCount,
|
|
871
|
+
ranges: Object.freeze(
|
|
872
|
+
getParquetPageReadRanges(
|
|
873
|
+
rowGroup,
|
|
874
|
+
phase.columns,
|
|
875
|
+
pagePlan,
|
|
876
|
+
initialization.parquetSchema
|
|
877
|
+
).map(range => Object.freeze({...range}))
|
|
878
|
+
)
|
|
879
|
+
})
|
|
880
|
+
: createFullColumnScanPlan(rowGroup, rowGroupIndex, phase.phase, phase.columns)
|
|
881
|
+
);
|
|
882
|
+
if (phase.phase === 'predicate' && pagePlan?.rowRanges.length === 0) {
|
|
883
|
+
predicateProvedEmpty = true;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
410
886
|
}
|
|
887
|
+
return plans;
|
|
411
888
|
}
|
|
412
889
|
|
|
413
890
|
/** Closes the underlying readable file and aborts active remote requests. */
|
|
@@ -421,6 +898,7 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
421
898
|
}
|
|
422
899
|
await this.readableFile?.close();
|
|
423
900
|
const initialization = await this.initializationPromise?.catch(() => null);
|
|
901
|
+
initialization?.reader.close();
|
|
424
902
|
if (initialization?.file !== this.readableFile) {
|
|
425
903
|
await initialization?.file.close();
|
|
426
904
|
}
|
|
@@ -458,11 +936,17 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
458
936
|
const rowGroups = options.rowGroups ?? this.options.parquet?.rowGroups;
|
|
459
937
|
const columns = options.columns ?? this.options.parquet?.columns;
|
|
460
938
|
const predicate = options.predicate ?? this.options.parquet?.predicate;
|
|
939
|
+
const bbox = options.bbox ?? this.options.parquet?.bbox;
|
|
940
|
+
const limit = options.limit ?? this.options.parquet?.limit;
|
|
941
|
+
validateTableQueryLimit(limit);
|
|
461
942
|
return {
|
|
462
943
|
rowGroups: rowGroups && [...rowGroups],
|
|
463
944
|
columns: columns && [...columns],
|
|
464
945
|
rowGroupFilter: options.rowGroupFilter ?? this.options.parquet?.rowGroupFilter,
|
|
465
946
|
predicate: predicate ? copyParquetPredicate(predicate) : undefined,
|
|
947
|
+
limit,
|
|
948
|
+
bbox: bbox ? ([...bbox] as ParquetSourceReadOptions['bbox']) : undefined,
|
|
949
|
+
geometryColumn: options.geometryColumn ?? this.options.parquet?.geometryColumn,
|
|
466
950
|
batchSize: options.batchSize ?? this.options.parquet?.batchSize,
|
|
467
951
|
concurrency: options.concurrency ?? this.options.parquet?.concurrency,
|
|
468
952
|
signal: options.signal
|
|
@@ -500,7 +984,25 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
500
984
|
workerOptions: ParquetSourceWorkerOptions | undefined,
|
|
501
985
|
signal: AbortSignal
|
|
502
986
|
): Promise<ParquetRowGroupReadResult> {
|
|
987
|
+
if (
|
|
988
|
+
predicate &&
|
|
989
|
+
projectedColumnNames &&
|
|
990
|
+
getParquetPredicateColumns(predicate).some(column => !projectedColumnNames.has(column))
|
|
991
|
+
) {
|
|
992
|
+
return await this.readRowGroupWithLateMaterialization(
|
|
993
|
+
initialization,
|
|
994
|
+
rowGroupIndex,
|
|
995
|
+
columnList,
|
|
996
|
+
projectedSchema,
|
|
997
|
+
batchSize,
|
|
998
|
+
predicate,
|
|
999
|
+
projectedColumnNames,
|
|
1000
|
+
workerOptions,
|
|
1001
|
+
signal
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
503
1004
|
const rowGroup = initialization.fileMetadata.row_groups[rowGroupIndex];
|
|
1005
|
+
await initialization.reader.resolveColumnMetadata(rowGroup, rowGroupIndex, columnList);
|
|
504
1006
|
const pagePlan = predicate
|
|
505
1007
|
? await createParquetPagePruningPlan(
|
|
506
1008
|
initialization.file,
|
|
@@ -508,7 +1010,19 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
508
1010
|
initialization.parquetSchema,
|
|
509
1011
|
columnList,
|
|
510
1012
|
predicate,
|
|
511
|
-
signal
|
|
1013
|
+
signal,
|
|
1014
|
+
{
|
|
1015
|
+
rowGroupOrdinal: rowGroupIndex,
|
|
1016
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
1017
|
+
decryptModule: (bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk) =>
|
|
1018
|
+
initialization.reader.decryptIndexModule(
|
|
1019
|
+
bytes,
|
|
1020
|
+
module,
|
|
1021
|
+
rowGroupOrdinal,
|
|
1022
|
+
columnOrdinal,
|
|
1023
|
+
columnChunk
|
|
1024
|
+
)
|
|
1025
|
+
}
|
|
512
1026
|
)
|
|
513
1027
|
: undefined;
|
|
514
1028
|
if (pagePlan) {
|
|
@@ -542,7 +1056,8 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
542
1056
|
columnList,
|
|
543
1057
|
rowRange,
|
|
544
1058
|
pagePlan.pageLocations,
|
|
545
|
-
signal
|
|
1059
|
+
signal,
|
|
1060
|
+
rowGroupIndex
|
|
546
1061
|
)
|
|
547
1062
|
)
|
|
548
1063
|
)
|
|
@@ -551,7 +1066,8 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
551
1066
|
initialization.parquetSchema,
|
|
552
1067
|
rowGroup,
|
|
553
1068
|
columnList,
|
|
554
|
-
signal
|
|
1069
|
+
signal,
|
|
1070
|
+
rowGroupIndex
|
|
555
1071
|
)
|
|
556
1072
|
];
|
|
557
1073
|
throwIfAborted(signal);
|
|
@@ -615,7 +1131,7 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
615
1131
|
return Boolean(path && (columnList.length === 0 || fieldIndexOf(columnList, path) >= 0));
|
|
616
1132
|
});
|
|
617
1133
|
const rangeDescriptors = pagePlan
|
|
618
|
-
? getParquetPageReadRanges(rowGroup, columnList, pagePlan)
|
|
1134
|
+
? getParquetPageReadRanges(rowGroup, columnList, pagePlan, initialization.parquetSchema)
|
|
619
1135
|
: selectedColumnChunks.map(getColumnChunkRange);
|
|
620
1136
|
const ranges = await Promise.all(
|
|
621
1137
|
rangeDescriptors.map(async ({offset, length}) => {
|
|
@@ -624,6 +1140,26 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
624
1140
|
);
|
|
625
1141
|
throwIfAborted(signal);
|
|
626
1142
|
|
|
1143
|
+
const workerColumnChunks = await Promise.all(
|
|
1144
|
+
selectedColumnChunks.map(async columnChunk => {
|
|
1145
|
+
const descriptor = createParquetSourceWorkerColumnChunk(columnChunk);
|
|
1146
|
+
descriptor.columnOrdinal = rowGroup.columns.indexOf(columnChunk);
|
|
1147
|
+
if (columnChunk.crypto_metadata) {
|
|
1148
|
+
const columnOrdinal = rowGroup.columns.indexOf(columnChunk);
|
|
1149
|
+
const key = await initialization.reader.getColumnKeyForWorker(
|
|
1150
|
+
columnChunk,
|
|
1151
|
+
rowGroupIndex,
|
|
1152
|
+
columnOrdinal
|
|
1153
|
+
);
|
|
1154
|
+
descriptor.encrypted = true;
|
|
1155
|
+
descriptor.keyMetadata = key.keyMetadata?.slice().buffer;
|
|
1156
|
+
descriptor.keyMaterial = key.keyMaterial.slice().buffer;
|
|
1157
|
+
}
|
|
1158
|
+
return descriptor;
|
|
1159
|
+
})
|
|
1160
|
+
);
|
|
1161
|
+
const encryptionContext = initialization.reader.getEncryptionContextForWorker();
|
|
1162
|
+
|
|
627
1163
|
let workerResult: ParquetSourceWorkerResult;
|
|
628
1164
|
try {
|
|
629
1165
|
workerResult = await decodeParquetSourceRowGroupOnWorker(
|
|
@@ -633,12 +1169,21 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
633
1169
|
uncompressedByteLength: Number(rowGroup.total_byte_size),
|
|
634
1170
|
schemaDefinition: initialization.parquetSchema.schema,
|
|
635
1171
|
projectedSchema,
|
|
636
|
-
columnChunks:
|
|
1172
|
+
columnChunks: workerColumnChunks,
|
|
637
1173
|
ranges,
|
|
638
1174
|
batchSize: batchSize || Math.max(Number(rowGroup.num_rows), 1),
|
|
639
1175
|
predicate: predicate ? copyParquetPredicate(predicate) : undefined,
|
|
640
1176
|
pagePlan,
|
|
641
|
-
|
|
1177
|
+
encryption: encryptionContext
|
|
1178
|
+
? {
|
|
1179
|
+
algorithm: encryptionContext.algorithm,
|
|
1180
|
+
aadPrefix: encryptionContext.aadPrefix?.slice().buffer,
|
|
1181
|
+
fileUnique: encryptionContext.fileUnique.slice().buffer
|
|
1182
|
+
}
|
|
1183
|
+
: undefined,
|
|
1184
|
+
preserveBinary: Boolean(this.options.parquet?.preserveBinary),
|
|
1185
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
1186
|
+
verifyPageChecksums: Boolean(this.options.parquet?.verifyPageChecksums)
|
|
642
1187
|
},
|
|
643
1188
|
workerOptions
|
|
644
1189
|
);
|
|
@@ -670,6 +1215,72 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
670
1215
|
return {rowGroupIndex, rowCount: workerResult.rowCount, workerResult};
|
|
671
1216
|
}
|
|
672
1217
|
|
|
1218
|
+
/** Decodes predicate columns first, avoiding projected-column decoding for empty matches. */
|
|
1219
|
+
private async readRowGroupWithLateMaterialization(
|
|
1220
|
+
initialization: ParquetSourceInitialization,
|
|
1221
|
+
rowGroupIndex: number,
|
|
1222
|
+
columnList: string[][],
|
|
1223
|
+
projectedSchema: Schema,
|
|
1224
|
+
batchSize: number | undefined,
|
|
1225
|
+
predicate: ParquetPredicate,
|
|
1226
|
+
projectedColumnNames: ReadonlySet<string>,
|
|
1227
|
+
workerOptions: ParquetSourceWorkerOptions | undefined,
|
|
1228
|
+
signal: AbortSignal
|
|
1229
|
+
): Promise<ParquetRowGroupReadResult> {
|
|
1230
|
+
const predicateColumns = getParquetPredicateColumns(predicate);
|
|
1231
|
+
const predicateColumnList = predicateColumns.map(column => [column]);
|
|
1232
|
+
const filterResult = await this.readRowGroup(
|
|
1233
|
+
initialization,
|
|
1234
|
+
rowGroupIndex,
|
|
1235
|
+
predicateColumnList,
|
|
1236
|
+
projectSchema(initialization.schema, predicateColumns),
|
|
1237
|
+
batchSize,
|
|
1238
|
+
predicate,
|
|
1239
|
+
new Set(predicateColumns),
|
|
1240
|
+
workerOptions,
|
|
1241
|
+
signal
|
|
1242
|
+
);
|
|
1243
|
+
const rowIndices = filterResult.rowIndices;
|
|
1244
|
+
const selectedRowIndices = rowIndices || getWorkerRowIndices(filterResult.workerResult);
|
|
1245
|
+
if (!selectedRowIndices || selectedRowIndices.length === 0) {
|
|
1246
|
+
return {rowGroupIndex, rowCount: 0, columns: {}, rowIndices: []};
|
|
1247
|
+
}
|
|
1248
|
+
const projectedColumnList = columnList.filter(columnPath =>
|
|
1249
|
+
projectedColumnNames.has(columnPath[0])
|
|
1250
|
+
);
|
|
1251
|
+
const projectedResult = await this.readRowGroup(
|
|
1252
|
+
initialization,
|
|
1253
|
+
rowGroupIndex,
|
|
1254
|
+
projectedColumnList,
|
|
1255
|
+
projectedSchema,
|
|
1256
|
+
batchSize,
|
|
1257
|
+
undefined,
|
|
1258
|
+
projectedColumnNames,
|
|
1259
|
+
workerOptions,
|
|
1260
|
+
signal
|
|
1261
|
+
);
|
|
1262
|
+
if (workerOptions && projectedResult.workerResult) {
|
|
1263
|
+
return {
|
|
1264
|
+
rowGroupIndex,
|
|
1265
|
+
rowCount: selectedRowIndices.length,
|
|
1266
|
+
rowIndices: selectedRowIndices,
|
|
1267
|
+
workerResult: gatherWorkerProjectedRows(
|
|
1268
|
+
projectedResult.workerResult,
|
|
1269
|
+
selectedRowIndices,
|
|
1270
|
+
batchSize
|
|
1271
|
+
)
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
return {
|
|
1275
|
+
rowGroupIndex,
|
|
1276
|
+
rowCount: selectedRowIndices.length,
|
|
1277
|
+
rowIndices: selectedRowIndices,
|
|
1278
|
+
columns: projectedResult.columns
|
|
1279
|
+
? gatherParquetColumns(projectedResult.columns, selectedRowIndices, projectedColumnNames)
|
|
1280
|
+
: {}
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
|
|
673
1284
|
/** Records the rows and pages avoided by one conservative page-index plan. */
|
|
674
1285
|
private recordPagePruningTelemetry(rowGroupIndex: number, plan: ParquetPagePruningPlan): void {
|
|
675
1286
|
this.recordTelemetry(
|
|
@@ -692,6 +1303,11 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
692
1303
|
try {
|
|
693
1304
|
const reader = new ParquetReader(file, {
|
|
694
1305
|
preserveBinary: this.options.parquet?.preserveBinary,
|
|
1306
|
+
int96AsTimestamp: this.options.parquet?.int96AsTimestamp,
|
|
1307
|
+
verifyPageChecksums: this.options.parquet?.verifyPageChecksums,
|
|
1308
|
+
verifyFooterSignature: this.options.parquet?.verifyFooterSignature,
|
|
1309
|
+
keyRetriever: this.options.parquet?.keyRetriever,
|
|
1310
|
+
aadPrefix: this.options.parquet?.aadPrefix,
|
|
695
1311
|
signal
|
|
696
1312
|
});
|
|
697
1313
|
const fileMetadata = await reader.getFileMetadata();
|
|
@@ -764,6 +1380,29 @@ export class ParquetSource extends DataSource<string | Blob, ParquetSourceLoader
|
|
|
764
1380
|
}
|
|
765
1381
|
}
|
|
766
1382
|
|
|
1383
|
+
function getParquetColumnRoles(columnNames: readonly string[]): Record<string, ScanColumnRole> {
|
|
1384
|
+
const roles: Record<string, ScanColumnRole> = {};
|
|
1385
|
+
for (const columnName of columnNames) {
|
|
1386
|
+
const normalizedName = columnName.toLowerCase();
|
|
1387
|
+
if (normalizedName === 'geometry' || normalizedName === 'geom' || normalizedName === 'shape') {
|
|
1388
|
+
roles[columnName] = 'geometry';
|
|
1389
|
+
} else if (
|
|
1390
|
+
normalizedName === 'longitude' ||
|
|
1391
|
+
normalizedName === 'lon' ||
|
|
1392
|
+
normalizedName === 'x'
|
|
1393
|
+
) {
|
|
1394
|
+
roles[columnName] = 'longitude';
|
|
1395
|
+
} else if (
|
|
1396
|
+
normalizedName === 'latitude' ||
|
|
1397
|
+
normalizedName === 'lat' ||
|
|
1398
|
+
normalizedName === 'y'
|
|
1399
|
+
) {
|
|
1400
|
+
roles[columnName] = 'latitude';
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
return roles;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
767
1406
|
/** Normalizes a decoded Parquet footer into source metadata. */
|
|
768
1407
|
function createParquetSourceMetadata(
|
|
769
1408
|
data: string | Blob,
|
|
@@ -784,7 +1423,8 @@ function createParquetSourceMetadata(
|
|
|
784
1423
|
rowOffset,
|
|
785
1424
|
Number(rowGroup.num_rows),
|
|
786
1425
|
Number(rowGroup.total_byte_size),
|
|
787
|
-
columns
|
|
1426
|
+
columns,
|
|
1427
|
+
rowGroup.sorting_columns
|
|
788
1428
|
);
|
|
789
1429
|
rowOffset += normalizedRowGroup.rowCount;
|
|
790
1430
|
return normalizedRowGroup;
|
|
@@ -821,6 +1461,8 @@ function createColumnChunkMetadata(
|
|
|
821
1461
|
columnMetadata.statistics,
|
|
822
1462
|
parquetSchema.findField(columnMetadata.path_in_schema)
|
|
823
1463
|
);
|
|
1464
|
+
const geospatialStatistics = createGeospatialStatistics(columnMetadata.geospatial_statistics);
|
|
1465
|
+
const sizeStatistics = createSizeStatistics(columnMetadata.size_statistics);
|
|
824
1466
|
const compressedByteLength = Number(columnMetadata.total_compressed_size);
|
|
825
1467
|
const uncompressedByteLength = Number(columnMetadata.total_uncompressed_size);
|
|
826
1468
|
return Object.freeze({
|
|
@@ -851,7 +1493,92 @@ function createColumnChunkMetadata(
|
|
|
851
1493
|
? undefined
|
|
852
1494
|
: Number(columnChunk.offset_index_offset),
|
|
853
1495
|
offsetIndexByteLength: columnChunk.offset_index_length,
|
|
854
|
-
|
|
1496
|
+
bloomFilterOffset:
|
|
1497
|
+
columnMetadata.bloom_filter_offset === undefined
|
|
1498
|
+
? undefined
|
|
1499
|
+
: Number(columnMetadata.bloom_filter_offset),
|
|
1500
|
+
bloomFilterByteLength: columnMetadata.bloom_filter_length,
|
|
1501
|
+
statistics,
|
|
1502
|
+
sizeStatistics,
|
|
1503
|
+
geospatialStatistics
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/** Normalizes optional Parquet size statistics without inventing missing counts. */
|
|
1508
|
+
function createSizeStatistics(
|
|
1509
|
+
statistics:
|
|
1510
|
+
| {
|
|
1511
|
+
unencoded_byte_array_data_bytes?: {toNumber?: () => number} | number;
|
|
1512
|
+
repetition_level_histogram?: Array<{toNumber?: () => number} | number>;
|
|
1513
|
+
definition_level_histogram?: Array<{toNumber?: () => number} | number>;
|
|
1514
|
+
}
|
|
1515
|
+
| undefined
|
|
1516
|
+
): ParquetColumnChunkSizeStatistics | undefined {
|
|
1517
|
+
if (!statistics) return undefined;
|
|
1518
|
+
const result: ParquetColumnChunkSizeStatistics = {
|
|
1519
|
+
unencodedByteArrayDataBytes:
|
|
1520
|
+
statistics.unencoded_byte_array_data_bytes === undefined
|
|
1521
|
+
? undefined
|
|
1522
|
+
: toSafeNumber(statistics.unencoded_byte_array_data_bytes),
|
|
1523
|
+
repetitionLevelHistogram: normalizeSizeStatisticsList(statistics.repetition_level_histogram),
|
|
1524
|
+
definitionLevelHistogram: normalizeSizeStatisticsList(statistics.definition_level_histogram)
|
|
1525
|
+
};
|
|
1526
|
+
if (
|
|
1527
|
+
result.unencodedByteArrayDataBytes === undefined &&
|
|
1528
|
+
result.repetitionLevelHistogram === undefined &&
|
|
1529
|
+
result.definitionLevelHistogram === undefined
|
|
1530
|
+
) {
|
|
1531
|
+
return undefined;
|
|
1532
|
+
}
|
|
1533
|
+
return Object.freeze({
|
|
1534
|
+
...result,
|
|
1535
|
+
repetitionLevelHistogram: result.repetitionLevelHistogram
|
|
1536
|
+
? Object.freeze(result.repetitionLevelHistogram)
|
|
1537
|
+
: undefined,
|
|
1538
|
+
definitionLevelHistogram: result.definitionLevelHistogram
|
|
1539
|
+
? Object.freeze(result.definitionLevelHistogram)
|
|
1540
|
+
: undefined
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
function toSafeNumber(value: {toNumber?: () => number} | number): number | undefined {
|
|
1545
|
+
const numberValue = typeof value === 'number' ? value : value.toNumber?.();
|
|
1546
|
+
return numberValue !== undefined && Number.isSafeInteger(numberValue) && numberValue >= 0
|
|
1547
|
+
? numberValue
|
|
1548
|
+
: undefined;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/** Converts a Thrift int64 histogram only when every entry is a safe non-negative integer. */
|
|
1552
|
+
function normalizeSizeStatisticsList(
|
|
1553
|
+
values: Array<{toNumber?: () => number} | number> | undefined
|
|
1554
|
+
): readonly number[] | undefined {
|
|
1555
|
+
if (!values) return undefined;
|
|
1556
|
+
const normalized = values.map(toSafeNumber);
|
|
1557
|
+
return normalized.every((value): value is number => value !== undefined) ? normalized : undefined;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
/** Normalizes native Parquet geospatial statistics from the decoded footer. */
|
|
1561
|
+
function createGeospatialStatistics(
|
|
1562
|
+
statistics: ParquetThriftGeospatialStatistics | undefined
|
|
1563
|
+
): ParquetGeospatialStatistics | undefined {
|
|
1564
|
+
if (!statistics) return undefined;
|
|
1565
|
+
const bbox = statistics.bbox
|
|
1566
|
+
? Object.freeze({
|
|
1567
|
+
xmin: statistics.bbox.xmin,
|
|
1568
|
+
xmax: statistics.bbox.xmax,
|
|
1569
|
+
ymin: statistics.bbox.ymin,
|
|
1570
|
+
ymax: statistics.bbox.ymax,
|
|
1571
|
+
zmin: statistics.bbox.zmin,
|
|
1572
|
+
zmax: statistics.bbox.zmax,
|
|
1573
|
+
mmin: statistics.bbox.mmin,
|
|
1574
|
+
mmax: statistics.bbox.mmax
|
|
1575
|
+
})
|
|
1576
|
+
: undefined;
|
|
1577
|
+
return Object.freeze({
|
|
1578
|
+
bbox,
|
|
1579
|
+
geometryTypes: statistics.geospatial_types
|
|
1580
|
+
? Object.freeze([...statistics.geospatial_types])
|
|
1581
|
+
: undefined
|
|
855
1582
|
});
|
|
856
1583
|
}
|
|
857
1584
|
|
|
@@ -933,7 +1660,8 @@ function createRowGroupMetadata(
|
|
|
933
1660
|
rowOffset: number,
|
|
934
1661
|
rowCount: number,
|
|
935
1662
|
uncompressedByteLength: number,
|
|
936
|
-
columns: ParquetColumnChunkMetadata[]
|
|
1663
|
+
columns: ParquetColumnChunkMetadata[],
|
|
1664
|
+
sortingColumns: RowGroup['sorting_columns']
|
|
937
1665
|
): ParquetRowGroupMetadata {
|
|
938
1666
|
const compressedByteLength = columns.reduce(
|
|
939
1667
|
(sum, column) => sum + column.compressedByteLength,
|
|
@@ -947,10 +1675,54 @@ function createRowGroupMetadata(
|
|
|
947
1675
|
uncompressedSize: uncompressedByteLength,
|
|
948
1676
|
compressedByteLength,
|
|
949
1677
|
compressedSize: compressedByteLength,
|
|
950
|
-
columns: Object.freeze(columns)
|
|
1678
|
+
columns: Object.freeze(columns),
|
|
1679
|
+
sortingColumns: Object.freeze(
|
|
1680
|
+
(sortingColumns || []).map(
|
|
1681
|
+
sortingColumn =>
|
|
1682
|
+
Object.freeze({
|
|
1683
|
+
columnIndex: sortingColumn.column_idx,
|
|
1684
|
+
descending: sortingColumn.descending,
|
|
1685
|
+
nullsFirst: sortingColumn.nulls_first
|
|
1686
|
+
}) satisfies ParquetSortingColumn
|
|
1687
|
+
)
|
|
1688
|
+
)
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/** Describes a phase that reads complete column chunks because no page index is available. */
|
|
1693
|
+
function createFullColumnScanPlan(
|
|
1694
|
+
rowGroup: RowGroup,
|
|
1695
|
+
rowGroupIndex: number,
|
|
1696
|
+
phase: ParquetPageScanPlan['phase'],
|
|
1697
|
+
columns: readonly string[][]
|
|
1698
|
+
): ParquetPageScanPlan {
|
|
1699
|
+
const selectedColumnChunks = rowGroup.columns.filter(columnChunk => {
|
|
1700
|
+
const path = columnChunk.meta_data?.path_in_schema;
|
|
1701
|
+
return Boolean(
|
|
1702
|
+
path && (columns.length === 0 || columns.some(column => pathMatchesSelection(column, path)))
|
|
1703
|
+
);
|
|
1704
|
+
});
|
|
1705
|
+
const rowCount = Number(rowGroup.num_rows);
|
|
1706
|
+
return Object.freeze({
|
|
1707
|
+
rowGroupIndex,
|
|
1708
|
+
phase,
|
|
1709
|
+
columns: Object.freeze(columns.map(column => Object.freeze([...column]))),
|
|
1710
|
+
rowRanges: Object.freeze([Object.freeze({start: 0, end: rowCount})]),
|
|
1711
|
+
indexesRead: 0,
|
|
1712
|
+
totalPages: 0,
|
|
1713
|
+
selectedPages: 0,
|
|
1714
|
+
rowsPruned: 0,
|
|
1715
|
+
ranges: Object.freeze(
|
|
1716
|
+
selectedColumnChunks.map(columnChunk => Object.freeze(getColumnChunkRange(columnChunk)))
|
|
1717
|
+
)
|
|
951
1718
|
});
|
|
952
1719
|
}
|
|
953
1720
|
|
|
1721
|
+
/** Returns whether a requested path selects a footer leaf or one of its nested children. */
|
|
1722
|
+
function pathMatchesSelection(selection: readonly string[], path: readonly string[]): boolean {
|
|
1723
|
+
return selection.length <= path.length && selection.every((part, index) => part === path[index]);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
954
1726
|
/** Returns the complete contiguous byte range occupied by one Parquet column chunk. */
|
|
955
1727
|
function getColumnChunkRange(columnChunk: ColumnChunk): {offset: number; length: number} {
|
|
956
1728
|
if (columnChunk.file_path) {
|
|
@@ -1048,11 +1820,8 @@ function createParquetBatchFromArrow(
|
|
|
1048
1820
|
: undefined
|
|
1049
1821
|
});
|
|
1050
1822
|
return {
|
|
1051
|
-
|
|
1052
|
-
shape: 'arrow-table',
|
|
1823
|
+
...makeTableScanBatch({shape: 'arrow-table', schema, data}),
|
|
1053
1824
|
schemaType: 'explicit',
|
|
1054
|
-
schema,
|
|
1055
|
-
data,
|
|
1056
1825
|
length: rowCount,
|
|
1057
1826
|
metadata: provenance,
|
|
1058
1827
|
...provenance
|
|
@@ -1173,6 +1942,9 @@ function createParquetTelemetry(): ParquetTelemetry {
|
|
|
1173
1942
|
rowGroupsRequested: 0,
|
|
1174
1943
|
rowGroupsPruned: 0,
|
|
1175
1944
|
rowGroupsPrunedByStatistics: 0,
|
|
1945
|
+
rowGroupsPrunedByBloomFilter: 0,
|
|
1946
|
+
bloomFiltersRead: 0,
|
|
1947
|
+
bloomFilterBytesRead: 0,
|
|
1176
1948
|
rowGroupsPrunedByPageIndex: 0,
|
|
1177
1949
|
pageIndexesRead: 0,
|
|
1178
1950
|
pagesRead: 0,
|
|
@@ -1188,6 +1960,45 @@ function createParquetTelemetry(): ParquetTelemetry {
|
|
|
1188
1960
|
};
|
|
1189
1961
|
}
|
|
1190
1962
|
|
|
1963
|
+
/** Converts cumulative Parquet counters into one portable read-scoped execution snapshot. */
|
|
1964
|
+
function createParquetScanExecutionTelemetry(
|
|
1965
|
+
before: ParquetTelemetry,
|
|
1966
|
+
after: ParquetTelemetry,
|
|
1967
|
+
startedAt: number,
|
|
1968
|
+
status: ScanExecutionTelemetry['status'],
|
|
1969
|
+
earlyTerminationReason?: ScanExecutionTelemetry['earlyTerminationReason'],
|
|
1970
|
+
error?: unknown
|
|
1971
|
+
): ScanExecutionTelemetry {
|
|
1972
|
+
const delta = Object.fromEntries(
|
|
1973
|
+
Object.keys(after).map(key => {
|
|
1974
|
+
const telemetryKey = key as keyof ParquetTelemetry;
|
|
1975
|
+
return [key, after[telemetryKey] - before[telemetryKey]];
|
|
1976
|
+
})
|
|
1977
|
+
) as ParquetTelemetry;
|
|
1978
|
+
const rowsRead = Math.max(delta.predicateRowsTested, delta.rowsEmitted);
|
|
1979
|
+
return Object.freeze({
|
|
1980
|
+
status,
|
|
1981
|
+
sourcesPlanned: 1,
|
|
1982
|
+
sourcesRead:
|
|
1983
|
+
status === 'early-terminated' && earlyTerminationReason === 'limit' && rowsRead === 0 ? 0 : 1,
|
|
1984
|
+
batchesRead: delta.batchesEmitted,
|
|
1985
|
+
batchesDecoded: delta.rowGroupsDecoded,
|
|
1986
|
+
rowsRead,
|
|
1987
|
+
rowsTested: delta.predicateRowsTested || undefined,
|
|
1988
|
+
rowsRetained: delta.predicateRowsMatched || undefined,
|
|
1989
|
+
rowsReturned: delta.rowsEmitted,
|
|
1990
|
+
bytesRead: delta.downloadedBytes,
|
|
1991
|
+
bytesFetched: delta.downloadedBytes,
|
|
1992
|
+
filesOpened: rowsRead || delta.downloadedBytes ? 1 : 0,
|
|
1993
|
+
tasksOpened: delta.rowGroupsDecoded,
|
|
1994
|
+
rowsPruned: delta.rowsPrunedByPageIndex,
|
|
1995
|
+
durationMilliseconds: Date.now() - startedAt,
|
|
1996
|
+
earlyTerminationReason,
|
|
1997
|
+
details: Object.freeze({...delta}),
|
|
1998
|
+
...(error === undefined ? {} : {error})
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1191
2002
|
/** Returns a monotonic timestamp when available and falls back to wall-clock time. */
|
|
1192
2003
|
function getCurrentTime(): number {
|
|
1193
2004
|
return globalThis.performance?.now() ?? Date.now();
|
|
@@ -1259,6 +2070,167 @@ function getSourceName(data: string | Blob, url: string): string {
|
|
|
1259
2070
|
return sourceName.replace(/\.parquet$/i, '') || 'parquet';
|
|
1260
2071
|
}
|
|
1261
2072
|
|
|
2073
|
+
type ParquetBloomFilterReadResult = {
|
|
2074
|
+
rowGroupIndices: number[];
|
|
2075
|
+
filtersRead: number;
|
|
2076
|
+
bytesRead: number;
|
|
2077
|
+
};
|
|
2078
|
+
|
|
2079
|
+
/** Reconstructs exact row-group indexes from worker predicate batches. */
|
|
2080
|
+
function getWorkerRowIndices(workerResult?: ParquetSourceWorkerResult): number[] {
|
|
2081
|
+
if (!workerResult) return [];
|
|
2082
|
+
return workerResult.batches.flatMap(batch =>
|
|
2083
|
+
batch.rowGroupRowIndices
|
|
2084
|
+
? [...batch.rowGroupRowIndices]
|
|
2085
|
+
: Array.from({length: batch.rowCount}, (_, index) => batch.rowGroupRowOffset + index)
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
/** Gathers projected worker Arrow batches through the shared indexed Arrow table utility. */
|
|
2090
|
+
function gatherWorkerProjectedRows(
|
|
2091
|
+
workerResult: ParquetSourceWorkerResult,
|
|
2092
|
+
rowIndices: readonly number[],
|
|
2093
|
+
batchSize: number | undefined
|
|
2094
|
+
): ParquetSourceWorkerResult {
|
|
2095
|
+
const tables = workerResult.batches.map(batch => hydrateArrowTable(batch.arrowTable));
|
|
2096
|
+
if (tables.length === 0) {
|
|
2097
|
+
return {...workerResult, rowCount: 0, batches: []};
|
|
2098
|
+
}
|
|
2099
|
+
const table = tables.length === 1 ? tables[0] : tables[0].concat(...tables.slice(1));
|
|
2100
|
+
const indexedTable = new IndexedArrowTable(table, rowIndices);
|
|
2101
|
+
const outputBatchSize = batchSize || Math.max(rowIndices.length, 1);
|
|
2102
|
+
const batches: ParquetSourceWorkerResult['batches'] = [];
|
|
2103
|
+
for (let offset = 0; offset < rowIndices.length; offset += outputBatchSize) {
|
|
2104
|
+
const end = Math.min(offset + outputBatchSize, rowIndices.length);
|
|
2105
|
+
const batchIndexes = rowIndices.slice(offset, end);
|
|
2106
|
+
const batchTable = indexedTable.slice(offset, end).materializeArrowTable();
|
|
2107
|
+
batches.push({
|
|
2108
|
+
rowGroupRowOffset: batchIndexes[0],
|
|
2109
|
+
rowCount: batchIndexes.length,
|
|
2110
|
+
rowGroupRowIndices: [...batchIndexes],
|
|
2111
|
+
arrowTable: dehydrateArrowTable(batchTable)
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
return {
|
|
2115
|
+
sourceRowCount: workerResult.sourceRowCount,
|
|
2116
|
+
rowCount: rowIndices.length,
|
|
2117
|
+
batches,
|
|
2118
|
+
decodeDurationMs: workerResult.decodeDurationMs,
|
|
2119
|
+
arrowConversionDurationMs: workerResult.arrowConversionDurationMs
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
/** Applies only proven-negative Bloom-filter checks to candidate row groups. */
|
|
2124
|
+
async function filterParquetRowGroupsWithBloomFilters(
|
|
2125
|
+
initialization: ParquetSourceInitialization,
|
|
2126
|
+
rowGroupIndices: readonly number[],
|
|
2127
|
+
predicate: ParquetPredicate,
|
|
2128
|
+
signal: AbortSignal
|
|
2129
|
+
): Promise<ParquetBloomFilterReadResult> {
|
|
2130
|
+
const rowGroups: number[] = [];
|
|
2131
|
+
let filtersRead = 0;
|
|
2132
|
+
let bytesRead = 0;
|
|
2133
|
+
for (const rowGroupIndex of rowGroupIndices) {
|
|
2134
|
+
const rowGroup = initialization.metadata.rowGroups[rowGroupIndex];
|
|
2135
|
+
const probes = getParquetBloomFilterProbes(predicate, rowGroup);
|
|
2136
|
+
let keepRowGroup = true;
|
|
2137
|
+
for (const probe of probes) {
|
|
2138
|
+
if (
|
|
2139
|
+
probe.column.bloomFilterOffset === undefined ||
|
|
2140
|
+
probe.column.bloomFilterByteLength === undefined
|
|
2141
|
+
) {
|
|
2142
|
+
continue;
|
|
2143
|
+
}
|
|
2144
|
+
let field: ParquetField;
|
|
2145
|
+
try {
|
|
2146
|
+
field = initialization.parquetSchema.findField([...probe.column.path]);
|
|
2147
|
+
} catch {
|
|
2148
|
+
throwIfAborted(signal);
|
|
2149
|
+
continue;
|
|
2150
|
+
}
|
|
2151
|
+
if (!field.primitiveType) continue;
|
|
2152
|
+
let filter: ReturnType<typeof decodeParquetSplitBlockBloomFilter>;
|
|
2153
|
+
try {
|
|
2154
|
+
const rawColumnOrdinal = initialization.fileMetadata.row_groups[
|
|
2155
|
+
rowGroupIndex
|
|
2156
|
+
].columns.findIndex(
|
|
2157
|
+
columnChunk =>
|
|
2158
|
+
JSON.stringify(columnChunk.meta_data?.path_in_schema) ===
|
|
2159
|
+
JSON.stringify(probe.column.path)
|
|
2160
|
+
);
|
|
2161
|
+
const rawColumnChunk =
|
|
2162
|
+
rawColumnOrdinal >= 0
|
|
2163
|
+
? initialization.fileMetadata.row_groups[rowGroupIndex].columns[rawColumnOrdinal]
|
|
2164
|
+
: undefined;
|
|
2165
|
+
const data = await initialization.file.read(
|
|
2166
|
+
probe.column.bloomFilterOffset,
|
|
2167
|
+
probe.column.bloomFilterByteLength,
|
|
2168
|
+
signal
|
|
2169
|
+
);
|
|
2170
|
+
const decodedBloomFilter = rawColumnChunk
|
|
2171
|
+
? await initialization.reader.decryptBloomFilter(
|
|
2172
|
+
toUint8Array(data),
|
|
2173
|
+
rowGroupIndex,
|
|
2174
|
+
rawColumnOrdinal,
|
|
2175
|
+
rawColumnChunk
|
|
2176
|
+
)
|
|
2177
|
+
: toUint8Array(data);
|
|
2178
|
+
filter = decodeParquetSplitBlockBloomFilter(decodedBloomFilter);
|
|
2179
|
+
filtersRead++;
|
|
2180
|
+
bytesRead += data.byteLength;
|
|
2181
|
+
} catch {
|
|
2182
|
+
throwIfAborted(signal);
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
if (
|
|
2186
|
+
filter.algorithm !== 'BLOCK' ||
|
|
2187
|
+
filter.hash !== 'XXHASH' ||
|
|
2188
|
+
filter.compression !== 'UNCOMPRESSED'
|
|
2189
|
+
) {
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
const mayContainValue = probe.values.some(value => {
|
|
2193
|
+
try {
|
|
2194
|
+
if (value instanceof Date) return true;
|
|
2195
|
+
const physicalValue = getParquetBloomFilterPhysicalValue(value, field);
|
|
2196
|
+
const encoded = encodeParquetBloomFilterValue(
|
|
2197
|
+
physicalValue,
|
|
2198
|
+
field.primitiveType as Parameters<typeof encodeParquetBloomFilterValue>[1],
|
|
2199
|
+
field.typeLength
|
|
2200
|
+
);
|
|
2201
|
+
return checkParquetSplitBlockBloomFilter(
|
|
2202
|
+
filter.bitset,
|
|
2203
|
+
hashParquetBloomFilterValue(encoded)
|
|
2204
|
+
);
|
|
2205
|
+
} catch {
|
|
2206
|
+
throwIfAborted(signal);
|
|
2207
|
+
return true;
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
if (!mayContainValue) {
|
|
2211
|
+
keepRowGroup = false;
|
|
2212
|
+
break;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
if (keepRowGroup) rowGroups.push(rowGroupIndex);
|
|
2216
|
+
}
|
|
2217
|
+
return {rowGroupIndices: rowGroups, filtersRead, bytesRead};
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
/** Converts logical predicate values into the physical values stored in Bloom filters. */
|
|
2221
|
+
function getParquetBloomFilterPhysicalValue(
|
|
2222
|
+
value: unknown,
|
|
2223
|
+
field: ParquetField
|
|
2224
|
+
): boolean | number | bigint | string | Uint8Array {
|
|
2225
|
+
if (field.originalType?.startsWith('DECIMAL_')) {
|
|
2226
|
+
return toPrimitive(field.originalType as ParquetType, value, field) as
|
|
2227
|
+
| number
|
|
2228
|
+
| bigint
|
|
2229
|
+
| Uint8Array;
|
|
2230
|
+
}
|
|
2231
|
+
return value as boolean | number | bigint | string | Uint8Array;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
1262
2234
|
/** Returns true when at least one HTTP object validator was captured. */
|
|
1263
2235
|
function hasObjectVersion(version?: ParquetObjectVersion): version is ParquetObjectVersion {
|
|
1264
2236
|
return Boolean(version?.etag || version?.lastModified);
|