@loaders.gl/parquet 5.0.0-alpha.2 → 5.0.0-alpha.3
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 +15005 -3143
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +7 -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 +15766 -4896
- 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-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 +14911 -4316
- package/dist/parquet-source-loader.cjs.map +3 -3
- package/dist/parquet-source-loader.d.ts +42 -4
- package/dist/parquet-source-loader.d.ts.map +1 -1
- package/dist/parquet-source-loader.js +585 -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 +7055 -6989
- package/dist/parquet-source-worker.js.map +4 -4
- package/dist/parquet-worker-node.cjs +8505 -1041
- 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/parquet-reader.d.ts +78 -12
- package/dist/parquetjs/parser/parquet-reader.d.ts.map +1 -1
- package/dist/parquetjs/parser/parquet-reader.js +605 -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 -11
- package/src/bundled.ts +3 -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 +50 -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-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 +929 -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/parquet-reader.ts +935 -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,22 @@
|
|
|
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
7
|
import { getSchemaFromParquetReader } from "./lib/parsers/get-parquet-schema.js";
|
|
8
|
+
import { canGeoParquetRowGroupMatch, combineParquetPredicates, createGeoParquetBoundingBoxPredicate } from "./lib/geo/geoparquet-covering.js";
|
|
9
|
+
import { decodeParquetSplitBlockBloomFilter, encodeParquetBloomFilterValue, hashParquetBloomFilterValue, checkParquetSplitBlockBloomFilter } from "./lib/parquet-bloom-filter.js";
|
|
10
|
+
import { getParquetBloomFilterProbes } from "./lib/parquet-bloom-filter-planner.js";
|
|
8
11
|
import { canParquetRowGroupMatch, copyParquetPredicate, filterParquetRowIndices, gatherParquetColumns, getParquetPredicateColumns, validateParquetPredicate } from "./lib/parquet-predicate.js";
|
|
9
12
|
import { createParquetPagePruningPlan, getParquetPageReadRanges } from "./lib/parquet-page-index.js";
|
|
10
13
|
import { canDecodeParquetSourceOnWorker, decodeParquetSourceRowGroupOnWorker } from "./lib/parquet-source-worker-client.js";
|
|
14
|
+
import { sliceParquetBatch } from "./lib/slice-parquet-batch.js";
|
|
11
15
|
import { ParquetRangeFile } from "./lib/sources/parquet-range-file.js";
|
|
12
|
-
import {
|
|
16
|
+
import { toPrimitive } from "./parquetjs/schema/types.js";
|
|
17
|
+
import { PARQUET_TABLE_QUERY_CAPABILITIES, PARQUET_SOURCE_CAPABILITIES } from "./parquet-source-capabilities.js";
|
|
13
18
|
import { ParquetSourceLoader as ParquetSourceLoaderMetadata } from "./parquet-source-loader-types.js";
|
|
14
|
-
export { PARQUET_SOURCE_CAPABILITIES } from "./parquet-source-capabilities.js";
|
|
19
|
+
export { PARQUET_TABLE_QUERY_CAPABILITIES, PARQUET_SOURCE_CAPABILITIES } from "./parquet-source-capabilities.js";
|
|
15
20
|
import { preloadCompressions } from "./parquetjs/compression.js";
|
|
16
21
|
import { CompressionCodec, Encoding } from "./parquetjs/parquet-thrift/index.js";
|
|
17
22
|
import { ParquetReader } from "./parquetjs/parser/parquet-reader.js";
|
|
@@ -28,6 +33,8 @@ export const ParquetSourceLoaderWithParser = {
|
|
|
28
33
|
export { ParquetSourceLoaderWithParser as ParquetSourceLoader };
|
|
29
34
|
/** Reusable Parquet source that caches footer/schema state and selectively reads byte ranges. */
|
|
30
35
|
export class ParquetSource extends DataSource {
|
|
36
|
+
/** Common projection, predicate, limit, streaming, and cancellation capabilities. */
|
|
37
|
+
tableQueryCapabilities = PARQUET_TABLE_QUERY_CAPABILITIES;
|
|
31
38
|
/** Immutable feature support for the current range-backed source implementation. */
|
|
32
39
|
capabilities = PARQUET_SOURCE_CAPABILITIES;
|
|
33
40
|
/** Shared initialization for this source instance. */
|
|
@@ -66,39 +73,118 @@ export class ParquetSource extends DataSource {
|
|
|
66
73
|
formatSpecificMetadata: initialization.fileMetadata
|
|
67
74
|
};
|
|
68
75
|
}
|
|
76
|
+
/** Discovers schema, capabilities, and footer statistics without decoding Parquet data pages. */
|
|
77
|
+
async getQueryMetadata(options = {}) {
|
|
78
|
+
const [schema, metadata] = await Promise.all([
|
|
79
|
+
this.getSchema({ signal: options.signal }),
|
|
80
|
+
this.getMetadata({ signal: options.signal })
|
|
81
|
+
]);
|
|
82
|
+
return createScanQueryMetadata({
|
|
83
|
+
sourceType: 'parquet',
|
|
84
|
+
queryType: 'table',
|
|
85
|
+
execution: { status: 'supported', method: 'read' },
|
|
86
|
+
name: metadata.name,
|
|
87
|
+
schema,
|
|
88
|
+
capabilities: { table: this.tableQueryCapabilities },
|
|
89
|
+
columnRoles: getParquetColumnRoles(schema.fields.map(field => field.name)),
|
|
90
|
+
statistics: { rowCount: metadata.rowCount, byteLength: metadata.fileByteLength }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** Plans a portable query using Parquet metadata without decoding data pages. */
|
|
94
|
+
async getScanPlan(options = {}) {
|
|
95
|
+
const readOptions = this.getReadOptions(options);
|
|
96
|
+
const initialization = await this.getInitialization(readOptions.signal);
|
|
97
|
+
const sourceColumnNames = initialization.schema.fields.map(field => field.name);
|
|
98
|
+
const signal = readOptions.signal ?? new AbortController().signal;
|
|
99
|
+
const physicalPlan = await this.planRowGroups(initialization, readOptions, signal);
|
|
100
|
+
const pagePlans = await this.planPages(initialization, readOptions, physicalPlan, signal);
|
|
101
|
+
const explanation = explainTableQuery(sourceColumnNames, {
|
|
102
|
+
columns: readOptions.columns,
|
|
103
|
+
predicate: physicalPlan.predicate,
|
|
104
|
+
limit: readOptions.limit
|
|
105
|
+
}, PARQUET_TABLE_QUERY_CAPABILITIES);
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
...explanation,
|
|
108
|
+
source: 'parquet',
|
|
109
|
+
rowGroups: Object.freeze({
|
|
110
|
+
indices: Object.freeze([...physicalPlan.rowGroupIndices]),
|
|
111
|
+
requested: physicalPlan.requested,
|
|
112
|
+
selected: physicalPlan.rowGroupIndices.length,
|
|
113
|
+
prunedByCallback: physicalPlan.prunedByCallback,
|
|
114
|
+
prunedBySpatial: physicalPlan.prunedBySpatial,
|
|
115
|
+
prunedByStatistics: physicalPlan.prunedByStatistics,
|
|
116
|
+
prunedByBloomFilter: physicalPlan.prunedByBloomFilter
|
|
117
|
+
}),
|
|
118
|
+
bloomFilters: Object.freeze({
|
|
119
|
+
read: physicalPlan.bloomFiltersRead,
|
|
120
|
+
bytesRead: physicalPlan.bloomFilterBytesRead
|
|
121
|
+
}),
|
|
122
|
+
pages: Object.freeze({
|
|
123
|
+
rowGroupsPlanned: new Set(pagePlans.map(plan => plan.rowGroupIndex)).size,
|
|
124
|
+
indexesRead: pagePlans.reduce((sum, plan) => sum + plan.indexesRead, 0),
|
|
125
|
+
total: pagePlans.reduce((sum, plan) => sum + plan.totalPages, 0),
|
|
126
|
+
selected: pagePlans.reduce((sum, plan) => sum + plan.selectedPages, 0),
|
|
127
|
+
rowsPruned: pagePlans.reduce((sum, plan) => sum + plan.rowsPruned, 0),
|
|
128
|
+
plans: Object.freeze(pagePlans)
|
|
129
|
+
})
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/** Explains the common logical query and Parquet-specific physical scan plan. */
|
|
133
|
+
async explain(options = {}) {
|
|
134
|
+
return await this.getScanPlan(options);
|
|
135
|
+
}
|
|
136
|
+
/** Executes a previously computed physical plan while preserving its row-group decisions. */
|
|
137
|
+
async *executeScanPlan(plan, options = {}) {
|
|
138
|
+
if (plan.source !== 'parquet') {
|
|
139
|
+
throw new Error('ParquetSource can only execute Parquet scan plans');
|
|
140
|
+
}
|
|
141
|
+
const predicateStep = plan.plan.find(step => step.kind === 'filter');
|
|
142
|
+
const limitStep = plan.plan.find(step => step.kind === 'limit');
|
|
143
|
+
yield* this.read({
|
|
144
|
+
...options,
|
|
145
|
+
columns: options.columns ?? plan.outputColumns,
|
|
146
|
+
predicate: options.predicate ??
|
|
147
|
+
(predicateStep?.kind === 'filter' ? predicateStep.predicate : undefined),
|
|
148
|
+
limit: options.limit ?? (limitStep?.kind === 'limit' ? limitStep.limit : undefined),
|
|
149
|
+
rowGroups: plan.rowGroups.indices
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** Common scan-architecture alias for selective Parquet reads. */
|
|
153
|
+
scan(options = {}) {
|
|
154
|
+
return this.read(options);
|
|
155
|
+
}
|
|
69
156
|
/** Returns a copy of cumulative transport, decode, conversion, and pruning telemetry. */
|
|
70
157
|
getTelemetry() {
|
|
71
158
|
return Object.freeze({ ...this.telemetry });
|
|
72
159
|
}
|
|
73
160
|
/** Selectively fetches row groups and columns as ordered Arrow batches with source provenance. */
|
|
74
161
|
async *read(options = {}) {
|
|
162
|
+
const startedAt = Date.now();
|
|
163
|
+
const telemetryBefore = this.getTelemetry();
|
|
75
164
|
const readOptions = this.getReadOptions(options);
|
|
165
|
+
if (readOptions.limit === 0) {
|
|
166
|
+
emitScanExecutionTelemetry(options.onTelemetry, createParquetScanExecutionTelemetry(telemetryBefore, this.getTelemetry(), startedAt, 'early-terminated', 'limit'));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
76
169
|
const readContext = createReadAbortContext(readOptions.signal);
|
|
77
170
|
const inFlightReads = new Set();
|
|
78
171
|
let completed = false;
|
|
172
|
+
let limitReached = false;
|
|
79
173
|
let readError;
|
|
80
174
|
this.activeReadControllers.add(readContext.abortController);
|
|
81
175
|
try {
|
|
82
176
|
const initialization = await this.getInitialization(readContext.abortController.signal);
|
|
83
177
|
await this.getCompressionInitialization();
|
|
84
178
|
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;
|
|
179
|
+
const physicalPlan = await this.planRowGroups(initialization, readOptions, readContext.abortController.signal);
|
|
180
|
+
const { predicate, rowGroupIndices } = physicalPlan;
|
|
98
181
|
this.recordTelemetry('row-group-prune', {
|
|
99
|
-
rowGroupsRequested:
|
|
100
|
-
rowGroupsPruned:
|
|
101
|
-
rowGroupsPrunedByStatistics
|
|
182
|
+
rowGroupsRequested: physicalPlan.requested,
|
|
183
|
+
rowGroupsPruned: physicalPlan.requested - rowGroupIndices.length,
|
|
184
|
+
rowGroupsPrunedByStatistics: physicalPlan.prunedByStatistics,
|
|
185
|
+
rowGroupsPrunedByBloomFilter: physicalPlan.prunedByBloomFilter,
|
|
186
|
+
bloomFiltersRead: physicalPlan.bloomFiltersRead,
|
|
187
|
+
bloomFilterBytesRead: physicalPlan.bloomFilterBytesRead
|
|
102
188
|
}, {});
|
|
103
189
|
const columns = normalizeColumns(readOptions.columns, initialization.schema);
|
|
104
190
|
const predicateColumns = predicate ? getParquetPredicateColumns(predicate) : [];
|
|
@@ -109,9 +195,11 @@ export class ParquetSource extends DataSource {
|
|
|
109
195
|
const projectedSchema = projectSchema(initialization.schema, columns);
|
|
110
196
|
const projectedColumnNames = columns.length ? new Set(columns) : undefined;
|
|
111
197
|
const workerOptions = this.getWorkerOptions(concurrency, readContext.abortController.signal);
|
|
112
|
-
const decodeOnWorker = canDecodeParquetSourceOnWorker(workerOptions)
|
|
198
|
+
const decodeOnWorker = canDecodeParquetSourceOnWorker(workerOptions) &&
|
|
199
|
+
(!initialization.reader.encrypted || Boolean(this.options.parquet?.keyRetriever));
|
|
113
200
|
const scheduledReads = new Map();
|
|
114
201
|
let nextPositionToSchedule = 0;
|
|
202
|
+
let remainingRows = readOptions.limit ?? Number.POSITIVE_INFINITY;
|
|
115
203
|
const scheduleReads = () => {
|
|
116
204
|
while (nextPositionToSchedule < rowGroupIndices.length &&
|
|
117
205
|
scheduledReads.size < concurrency) {
|
|
@@ -139,9 +227,15 @@ export class ParquetSource extends DataSource {
|
|
|
139
227
|
const arrowTable = hydrateArrowTable(workerBatch.arrowTable);
|
|
140
228
|
const deserializationDurationMs = getCurrentTime() - deserializationStartTime;
|
|
141
229
|
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:
|
|
230
|
+
const batch = sliceParquetBatch(createParquetBatchFromArrow(initialization.metadata, projectedSchema, rowGroupIndex, workerBatch.rowGroupRowOffset, arrowTable, workerBatch.rowCount, workerBatch.rowGroupRowIndices), Math.min(workerBatch.rowCount, remainingRows));
|
|
231
|
+
this.recordTelemetry('batch', { batchesEmitted: 1, rowsEmitted: batch.length }, { rowGroupIndex, rowCount: batch.length });
|
|
144
232
|
yield batch;
|
|
233
|
+
remainingRows -= batch.length;
|
|
234
|
+
if (remainingRows === 0) {
|
|
235
|
+
limitReached = true;
|
|
236
|
+
completed = true;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
145
239
|
}
|
|
146
240
|
continue;
|
|
147
241
|
}
|
|
@@ -149,7 +243,7 @@ export class ParquetSource extends DataSource {
|
|
|
149
243
|
const outputBatchSize = batchSize || Math.max(outputRowCount, 1);
|
|
150
244
|
for (let outputRowOffset = 0; outputRowOffset < outputRowCount; outputRowOffset += outputBatchSize) {
|
|
151
245
|
throwIfAborted(readContext.abortController.signal);
|
|
152
|
-
const batchRowCount = Math.min(outputBatchSize, outputRowCount - outputRowOffset);
|
|
246
|
+
const batchRowCount = Math.min(outputBatchSize, outputRowCount - outputRowOffset, remainingRows);
|
|
153
247
|
const batchRowIndices = rowIndices
|
|
154
248
|
? rowIndices.slice(outputRowOffset, outputRowOffset + batchRowCount)
|
|
155
249
|
: undefined;
|
|
@@ -161,6 +255,12 @@ export class ParquetSource extends DataSource {
|
|
|
161
255
|
this.recordTelemetry('arrow-conversion', { arrowConversionDurationMs: conversionDurationMs }, { rowGroupIndex, durationMs: conversionDurationMs });
|
|
162
256
|
this.recordTelemetry('batch', { batchesEmitted: 1, rowsEmitted: batchRowCount }, { rowGroupIndex, rowCount: batchRowCount });
|
|
163
257
|
yield batch;
|
|
258
|
+
remainingRows -= batch.length;
|
|
259
|
+
if (remainingRows === 0) {
|
|
260
|
+
limitReached = true;
|
|
261
|
+
completed = true;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
164
264
|
}
|
|
165
265
|
}
|
|
166
266
|
completed = true;
|
|
@@ -179,12 +279,125 @@ export class ParquetSource extends DataSource {
|
|
|
179
279
|
if (!completed && readError === undefined) {
|
|
180
280
|
this.recordTelemetry('cancel', { cancellationCount: 1 }, {});
|
|
181
281
|
}
|
|
282
|
+
const cancelled = readContext.abortController.signal.aborted || readOptions.signal?.aborted;
|
|
182
283
|
readContext.abortController.abort();
|
|
183
284
|
readContext.removeSignalListener();
|
|
184
285
|
this.activeReadControllers.delete(readContext.abortController);
|
|
185
286
|
await Promise.allSettled([...inFlightReads]);
|
|
287
|
+
const status = readError
|
|
288
|
+
? cancelled
|
|
289
|
+
? 'cancelled'
|
|
290
|
+
: 'failed'
|
|
291
|
+
: limitReached || !completed
|
|
292
|
+
? 'early-terminated'
|
|
293
|
+
: 'completed';
|
|
294
|
+
emitScanExecutionTelemetry(options.onTelemetry, createParquetScanExecutionTelemetry(telemetryBefore, this.getTelemetry(), startedAt, status, limitReached
|
|
295
|
+
? 'limit'
|
|
296
|
+
: !completed && readError === undefined
|
|
297
|
+
? 'consumer-return'
|
|
298
|
+
: undefined, readError));
|
|
186
299
|
}
|
|
187
300
|
}
|
|
301
|
+
/** Produces the physical row-group plan shared by explain and execution. */
|
|
302
|
+
async planRowGroups(initialization, readOptions, signal) {
|
|
303
|
+
throwIfAborted(signal);
|
|
304
|
+
const candidateRowGroupIndices = normalizeRowGroupIndices(readOptions.rowGroups, initialization.fileMetadata.row_groups.length);
|
|
305
|
+
const callbackFilteredRowGroupIndices = readOptions.rowGroupFilter
|
|
306
|
+
? candidateRowGroupIndices.filter(rowGroupIndex => readOptions.rowGroupFilter(initialization.metadata.rowGroups[rowGroupIndex]))
|
|
307
|
+
: candidateRowGroupIndices;
|
|
308
|
+
const spatiallyFilteredRowGroupIndices = readOptions.bbox
|
|
309
|
+
? callbackFilteredRowGroupIndices.filter(rowGroupIndex => canGeoParquetRowGroupMatch(initialization.metadata, initialization.metadata.rowGroups[rowGroupIndex], readOptions.bbox, readOptions.geometryColumn))
|
|
310
|
+
: callbackFilteredRowGroupIndices;
|
|
311
|
+
const spatialPredicate = readOptions.bbox
|
|
312
|
+
? createGeoParquetBoundingBoxPredicate(initialization.metadata, readOptions.bbox, readOptions.geometryColumn)
|
|
313
|
+
: undefined;
|
|
314
|
+
const predicate = combineParquetPredicates(readOptions.predicate, spatialPredicate);
|
|
315
|
+
if (predicate) {
|
|
316
|
+
validateParquetPredicate(predicate, new Set(initialization.schema.fields.map(field => field.name)));
|
|
317
|
+
}
|
|
318
|
+
const statisticsRowGroupIndices = predicate
|
|
319
|
+
? spatiallyFilteredRowGroupIndices.filter(rowGroupIndex => canParquetRowGroupMatch(predicate, initialization.metadata.rowGroups[rowGroupIndex]))
|
|
320
|
+
: spatiallyFilteredRowGroupIndices;
|
|
321
|
+
const bloomFilterResult = predicate
|
|
322
|
+
? await filterParquetRowGroupsWithBloomFilters(initialization, statisticsRowGroupIndices, predicate, signal)
|
|
323
|
+
: { rowGroupIndices: statisticsRowGroupIndices, filtersRead: 0, bytesRead: 0 };
|
|
324
|
+
return {
|
|
325
|
+
predicate,
|
|
326
|
+
rowGroupIndices: bloomFilterResult.rowGroupIndices,
|
|
327
|
+
requested: candidateRowGroupIndices.length,
|
|
328
|
+
prunedByCallback: candidateRowGroupIndices.length - callbackFilteredRowGroupIndices.length,
|
|
329
|
+
prunedBySpatial: callbackFilteredRowGroupIndices.length - spatiallyFilteredRowGroupIndices.length,
|
|
330
|
+
prunedByStatistics: spatiallyFilteredRowGroupIndices.length - statisticsRowGroupIndices.length,
|
|
331
|
+
prunedByBloomFilter: statisticsRowGroupIndices.length - bloomFilterResult.rowGroupIndices.length,
|
|
332
|
+
bloomFiltersRead: bloomFilterResult.filtersRead,
|
|
333
|
+
bloomFilterBytesRead: bloomFilterResult.bytesRead
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/** Produces explainable page-index and byte-range plans for retained row groups. */
|
|
337
|
+
async planPages(initialization, readOptions, rowGroupPlan, signal) {
|
|
338
|
+
if (!rowGroupPlan.predicate)
|
|
339
|
+
return [];
|
|
340
|
+
const columns = normalizeColumns(readOptions.columns, initialization.schema);
|
|
341
|
+
const predicateColumns = getParquetPredicateColumns(rowGroupPlan.predicate);
|
|
342
|
+
const decodedColumns = columns.length === 0 ? [] : [...new Set([...columns, ...predicateColumns])];
|
|
343
|
+
const hasHiddenPredicateColumns = columns.length > 0 && predicateColumns.some(column => !columns.includes(column));
|
|
344
|
+
const phases = hasHiddenPredicateColumns
|
|
345
|
+
? [
|
|
346
|
+
{
|
|
347
|
+
phase: 'predicate',
|
|
348
|
+
columns: predicateColumns.map(column => [column]),
|
|
349
|
+
predicate: rowGroupPlan.predicate
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
phase: 'projection',
|
|
353
|
+
columns: columns.map(column => [column]),
|
|
354
|
+
predicate: undefined
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
: [
|
|
358
|
+
{
|
|
359
|
+
phase: 'combined',
|
|
360
|
+
columns: decodedColumns.map(column => [column]),
|
|
361
|
+
predicate: rowGroupPlan.predicate
|
|
362
|
+
}
|
|
363
|
+
];
|
|
364
|
+
const plans = [];
|
|
365
|
+
for (const rowGroupIndex of rowGroupPlan.rowGroupIndices) {
|
|
366
|
+
const rowGroup = initialization.fileMetadata.row_groups[rowGroupIndex];
|
|
367
|
+
let predicateProvedEmpty = false;
|
|
368
|
+
for (const phase of phases) {
|
|
369
|
+
throwIfAborted(signal);
|
|
370
|
+
if (phase.phase === 'projection' && predicateProvedEmpty) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
await initialization.reader.resolveColumnMetadata(rowGroup, rowGroupIndex, phase.columns);
|
|
374
|
+
const pagePlan = phase.predicate
|
|
375
|
+
? await createParquetPagePruningPlan(initialization.file, rowGroup, initialization.parquetSchema, phase.columns, phase.predicate, signal, {
|
|
376
|
+
rowGroupOrdinal: rowGroupIndex,
|
|
377
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
378
|
+
decryptModule: (bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk) => initialization.reader.decryptIndexModule(bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk)
|
|
379
|
+
})
|
|
380
|
+
: undefined;
|
|
381
|
+
plans.push(pagePlan
|
|
382
|
+
? Object.freeze({
|
|
383
|
+
rowGroupIndex,
|
|
384
|
+
phase: phase.phase,
|
|
385
|
+
columns: Object.freeze(phase.columns.map(column => Object.freeze([...column]))),
|
|
386
|
+
rowRanges: Object.freeze(pagePlan.rowRanges.map(rowRange => Object.freeze({ ...rowRange }))),
|
|
387
|
+
indexesRead: pagePlan.indexCount,
|
|
388
|
+
totalPages: pagePlan.totalPageCount,
|
|
389
|
+
selectedPages: pagePlan.selectedPageCount,
|
|
390
|
+
rowsPruned: pagePlan.prunedRowCount,
|
|
391
|
+
ranges: Object.freeze(getParquetPageReadRanges(rowGroup, phase.columns, pagePlan, initialization.parquetSchema).map(range => Object.freeze({ ...range })))
|
|
392
|
+
})
|
|
393
|
+
: createFullColumnScanPlan(rowGroup, rowGroupIndex, phase.phase, phase.columns));
|
|
394
|
+
if (phase.phase === 'predicate' && pagePlan?.rowRanges.length === 0) {
|
|
395
|
+
predicateProvedEmpty = true;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return plans;
|
|
400
|
+
}
|
|
188
401
|
/** Closes the underlying readable file and aborts active remote requests. */
|
|
189
402
|
async close() {
|
|
190
403
|
if (this.closed) {
|
|
@@ -196,6 +409,7 @@ export class ParquetSource extends DataSource {
|
|
|
196
409
|
}
|
|
197
410
|
await this.readableFile?.close();
|
|
198
411
|
const initialization = await this.initializationPromise?.catch(() => null);
|
|
412
|
+
initialization?.reader.close();
|
|
199
413
|
if (initialization?.file !== this.readableFile) {
|
|
200
414
|
await initialization?.file.close();
|
|
201
415
|
}
|
|
@@ -230,11 +444,17 @@ export class ParquetSource extends DataSource {
|
|
|
230
444
|
const rowGroups = options.rowGroups ?? this.options.parquet?.rowGroups;
|
|
231
445
|
const columns = options.columns ?? this.options.parquet?.columns;
|
|
232
446
|
const predicate = options.predicate ?? this.options.parquet?.predicate;
|
|
447
|
+
const bbox = options.bbox ?? this.options.parquet?.bbox;
|
|
448
|
+
const limit = options.limit ?? this.options.parquet?.limit;
|
|
449
|
+
validateTableQueryLimit(limit);
|
|
233
450
|
return {
|
|
234
451
|
rowGroups: rowGroups && [...rowGroups],
|
|
235
452
|
columns: columns && [...columns],
|
|
236
453
|
rowGroupFilter: options.rowGroupFilter ?? this.options.parquet?.rowGroupFilter,
|
|
237
454
|
predicate: predicate ? copyParquetPredicate(predicate) : undefined,
|
|
455
|
+
limit,
|
|
456
|
+
bbox: bbox ? [...bbox] : undefined,
|
|
457
|
+
geometryColumn: options.geometryColumn ?? this.options.parquet?.geometryColumn,
|
|
238
458
|
batchSize: options.batchSize ?? this.options.parquet?.batchSize,
|
|
239
459
|
concurrency: options.concurrency ?? this.options.parquet?.concurrency,
|
|
240
460
|
signal: options.signal
|
|
@@ -260,9 +480,19 @@ export class ParquetSource extends DataSource {
|
|
|
260
480
|
}
|
|
261
481
|
/** Fetches and decodes one selected row group using the cached footer and schema. */
|
|
262
482
|
async readRowGroup(initialization, rowGroupIndex, columnList, projectedSchema, batchSize, predicate, projectedColumnNames, workerOptions, signal) {
|
|
483
|
+
if (predicate &&
|
|
484
|
+
projectedColumnNames &&
|
|
485
|
+
getParquetPredicateColumns(predicate).some(column => !projectedColumnNames.has(column))) {
|
|
486
|
+
return await this.readRowGroupWithLateMaterialization(initialization, rowGroupIndex, columnList, projectedSchema, batchSize, predicate, projectedColumnNames, workerOptions, signal);
|
|
487
|
+
}
|
|
263
488
|
const rowGroup = initialization.fileMetadata.row_groups[rowGroupIndex];
|
|
489
|
+
await initialization.reader.resolveColumnMetadata(rowGroup, rowGroupIndex, columnList);
|
|
264
490
|
const pagePlan = predicate
|
|
265
|
-
? await createParquetPagePruningPlan(initialization.file, rowGroup, initialization.parquetSchema, columnList, predicate, signal
|
|
491
|
+
? await createParquetPagePruningPlan(initialization.file, rowGroup, initialization.parquetSchema, columnList, predicate, signal, {
|
|
492
|
+
rowGroupOrdinal: rowGroupIndex,
|
|
493
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
494
|
+
decryptModule: (bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk) => initialization.reader.decryptIndexModule(bytes, module, rowGroupOrdinal, columnOrdinal, columnChunk)
|
|
495
|
+
})
|
|
266
496
|
: undefined;
|
|
267
497
|
if (pagePlan) {
|
|
268
498
|
this.recordPagePruningTelemetry(rowGroupIndex, pagePlan);
|
|
@@ -275,9 +505,9 @@ export class ParquetSource extends DataSource {
|
|
|
275
505
|
}
|
|
276
506
|
const decodeStartTime = getCurrentTime();
|
|
277
507
|
const decodedRowGroups = pagePlan
|
|
278
|
-
? await Promise.all(pagePlan.rowRanges.map(rowRange => initialization.reader.readRowGroupRange(initialization.parquetSchema, rowGroup, columnList, rowRange, pagePlan.pageLocations, signal)))
|
|
508
|
+
? await Promise.all(pagePlan.rowRanges.map(rowRange => initialization.reader.readRowGroupRange(initialization.parquetSchema, rowGroup, columnList, rowRange, pagePlan.pageLocations, signal, rowGroupIndex)))
|
|
279
509
|
: [
|
|
280
|
-
await initialization.reader.readRowGroup(initialization.parquetSchema, rowGroup, columnList, signal)
|
|
510
|
+
await initialization.reader.readRowGroup(initialization.parquetSchema, rowGroup, columnList, signal, rowGroupIndex)
|
|
281
511
|
];
|
|
282
512
|
throwIfAborted(signal);
|
|
283
513
|
const columns = concatenateMaterializedColumns(decodedRowGroups.map(decodedRowGroup => initialization.parquetSchema.materializeColumns(decodedRowGroup)));
|
|
@@ -315,12 +545,25 @@ export class ParquetSource extends DataSource {
|
|
|
315
545
|
return Boolean(path && (columnList.length === 0 || fieldIndexOf(columnList, path) >= 0));
|
|
316
546
|
});
|
|
317
547
|
const rangeDescriptors = pagePlan
|
|
318
|
-
? getParquetPageReadRanges(rowGroup, columnList, pagePlan)
|
|
548
|
+
? getParquetPageReadRanges(rowGroup, columnList, pagePlan, initialization.parquetSchema)
|
|
319
549
|
: selectedColumnChunks.map(getColumnChunkRange);
|
|
320
550
|
const ranges = await Promise.all(rangeDescriptors.map(async ({ offset, length }) => {
|
|
321
551
|
return { offset, data: await initialization.file.read(offset, length, signal) };
|
|
322
552
|
}));
|
|
323
553
|
throwIfAborted(signal);
|
|
554
|
+
const workerColumnChunks = await Promise.all(selectedColumnChunks.map(async (columnChunk) => {
|
|
555
|
+
const descriptor = createParquetSourceWorkerColumnChunk(columnChunk);
|
|
556
|
+
descriptor.columnOrdinal = rowGroup.columns.indexOf(columnChunk);
|
|
557
|
+
if (columnChunk.crypto_metadata) {
|
|
558
|
+
const columnOrdinal = rowGroup.columns.indexOf(columnChunk);
|
|
559
|
+
const key = await initialization.reader.getColumnKeyForWorker(columnChunk, rowGroupIndex, columnOrdinal);
|
|
560
|
+
descriptor.encrypted = true;
|
|
561
|
+
descriptor.keyMetadata = key.keyMetadata?.slice().buffer;
|
|
562
|
+
descriptor.keyMaterial = key.keyMaterial.slice().buffer;
|
|
563
|
+
}
|
|
564
|
+
return descriptor;
|
|
565
|
+
}));
|
|
566
|
+
const encryptionContext = initialization.reader.getEncryptionContextForWorker();
|
|
324
567
|
let workerResult;
|
|
325
568
|
try {
|
|
326
569
|
workerResult = await decodeParquetSourceRowGroupOnWorker({
|
|
@@ -329,12 +572,21 @@ export class ParquetSource extends DataSource {
|
|
|
329
572
|
uncompressedByteLength: Number(rowGroup.total_byte_size),
|
|
330
573
|
schemaDefinition: initialization.parquetSchema.schema,
|
|
331
574
|
projectedSchema,
|
|
332
|
-
columnChunks:
|
|
575
|
+
columnChunks: workerColumnChunks,
|
|
333
576
|
ranges,
|
|
334
577
|
batchSize: batchSize || Math.max(Number(rowGroup.num_rows), 1),
|
|
335
578
|
predicate: predicate ? copyParquetPredicate(predicate) : undefined,
|
|
336
579
|
pagePlan,
|
|
337
|
-
|
|
580
|
+
encryption: encryptionContext
|
|
581
|
+
? {
|
|
582
|
+
algorithm: encryptionContext.algorithm,
|
|
583
|
+
aadPrefix: encryptionContext.aadPrefix?.slice().buffer,
|
|
584
|
+
fileUnique: encryptionContext.fileUnique.slice().buffer
|
|
585
|
+
}
|
|
586
|
+
: undefined,
|
|
587
|
+
preserveBinary: Boolean(this.options.parquet?.preserveBinary),
|
|
588
|
+
int96AsTimestamp: Boolean(this.options.parquet?.int96AsTimestamp),
|
|
589
|
+
verifyPageChecksums: Boolean(this.options.parquet?.verifyPageChecksums)
|
|
338
590
|
}, workerOptions);
|
|
339
591
|
}
|
|
340
592
|
catch (error) {
|
|
@@ -352,6 +604,35 @@ export class ParquetSource extends DataSource {
|
|
|
352
604
|
}
|
|
353
605
|
return { rowGroupIndex, rowCount: workerResult.rowCount, workerResult };
|
|
354
606
|
}
|
|
607
|
+
/** Decodes predicate columns first, avoiding projected-column decoding for empty matches. */
|
|
608
|
+
async readRowGroupWithLateMaterialization(initialization, rowGroupIndex, columnList, projectedSchema, batchSize, predicate, projectedColumnNames, workerOptions, signal) {
|
|
609
|
+
const predicateColumns = getParquetPredicateColumns(predicate);
|
|
610
|
+
const predicateColumnList = predicateColumns.map(column => [column]);
|
|
611
|
+
const filterResult = await this.readRowGroup(initialization, rowGroupIndex, predicateColumnList, projectSchema(initialization.schema, predicateColumns), batchSize, predicate, new Set(predicateColumns), workerOptions, signal);
|
|
612
|
+
const rowIndices = filterResult.rowIndices;
|
|
613
|
+
const selectedRowIndices = rowIndices || getWorkerRowIndices(filterResult.workerResult);
|
|
614
|
+
if (!selectedRowIndices || selectedRowIndices.length === 0) {
|
|
615
|
+
return { rowGroupIndex, rowCount: 0, columns: {}, rowIndices: [] };
|
|
616
|
+
}
|
|
617
|
+
const projectedColumnList = columnList.filter(columnPath => projectedColumnNames.has(columnPath[0]));
|
|
618
|
+
const projectedResult = await this.readRowGroup(initialization, rowGroupIndex, projectedColumnList, projectedSchema, batchSize, undefined, projectedColumnNames, workerOptions, signal);
|
|
619
|
+
if (workerOptions && projectedResult.workerResult) {
|
|
620
|
+
return {
|
|
621
|
+
rowGroupIndex,
|
|
622
|
+
rowCount: selectedRowIndices.length,
|
|
623
|
+
rowIndices: selectedRowIndices,
|
|
624
|
+
workerResult: gatherWorkerProjectedRows(projectedResult.workerResult, selectedRowIndices, batchSize)
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
rowGroupIndex,
|
|
629
|
+
rowCount: selectedRowIndices.length,
|
|
630
|
+
rowIndices: selectedRowIndices,
|
|
631
|
+
columns: projectedResult.columns
|
|
632
|
+
? gatherParquetColumns(projectedResult.columns, selectedRowIndices, projectedColumnNames)
|
|
633
|
+
: {}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
355
636
|
/** Records the rows and pages avoided by one conservative page-index plan. */
|
|
356
637
|
recordPagePruningTelemetry(rowGroupIndex, plan) {
|
|
357
638
|
this.recordTelemetry('page-index-prune', {
|
|
@@ -369,6 +650,11 @@ export class ParquetSource extends DataSource {
|
|
|
369
650
|
try {
|
|
370
651
|
const reader = new ParquetReader(file, {
|
|
371
652
|
preserveBinary: this.options.parquet?.preserveBinary,
|
|
653
|
+
int96AsTimestamp: this.options.parquet?.int96AsTimestamp,
|
|
654
|
+
verifyPageChecksums: this.options.parquet?.verifyPageChecksums,
|
|
655
|
+
verifyFooterSignature: this.options.parquet?.verifyFooterSignature,
|
|
656
|
+
keyRetriever: this.options.parquet?.keyRetriever,
|
|
657
|
+
aadPrefix: this.options.parquet?.aadPrefix,
|
|
372
658
|
signal
|
|
373
659
|
});
|
|
374
660
|
const fileMetadata = await reader.getFileMetadata();
|
|
@@ -421,6 +707,26 @@ export class ParquetSource extends DataSource {
|
|
|
421
707
|
}
|
|
422
708
|
}
|
|
423
709
|
}
|
|
710
|
+
function getParquetColumnRoles(columnNames) {
|
|
711
|
+
const roles = {};
|
|
712
|
+
for (const columnName of columnNames) {
|
|
713
|
+
const normalizedName = columnName.toLowerCase();
|
|
714
|
+
if (normalizedName === 'geometry' || normalizedName === 'geom' || normalizedName === 'shape') {
|
|
715
|
+
roles[columnName] = 'geometry';
|
|
716
|
+
}
|
|
717
|
+
else if (normalizedName === 'longitude' ||
|
|
718
|
+
normalizedName === 'lon' ||
|
|
719
|
+
normalizedName === 'x') {
|
|
720
|
+
roles[columnName] = 'longitude';
|
|
721
|
+
}
|
|
722
|
+
else if (normalizedName === 'latitude' ||
|
|
723
|
+
normalizedName === 'lat' ||
|
|
724
|
+
normalizedName === 'y') {
|
|
725
|
+
roles[columnName] = 'latitude';
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return roles;
|
|
729
|
+
}
|
|
424
730
|
/** Normalizes a decoded Parquet footer into source metadata. */
|
|
425
731
|
function createParquetSourceMetadata(data, url, file, fileMetadata, schema, parquetSchema, objectVersion) {
|
|
426
732
|
let rowOffset = 0;
|
|
@@ -428,7 +734,7 @@ function createParquetSourceMetadata(data, url, file, fileMetadata, schema, parq
|
|
|
428
734
|
const columns = rowGroup.columns
|
|
429
735
|
.filter(columnChunk => Boolean(columnChunk.meta_data))
|
|
430
736
|
.map(columnChunk => createColumnChunkMetadata(columnChunk, parquetSchema));
|
|
431
|
-
const normalizedRowGroup = createRowGroupMetadata(index, rowOffset, Number(rowGroup.num_rows), Number(rowGroup.total_byte_size), columns);
|
|
737
|
+
const normalizedRowGroup = createRowGroupMetadata(index, rowOffset, Number(rowGroup.num_rows), Number(rowGroup.total_byte_size), columns, rowGroup.sorting_columns);
|
|
432
738
|
rowOffset += normalizedRowGroup.rowCount;
|
|
433
739
|
return normalizedRowGroup;
|
|
434
740
|
});
|
|
@@ -455,6 +761,8 @@ function createColumnChunkMetadata(columnChunk, parquetSchema) {
|
|
|
455
761
|
? undefined
|
|
456
762
|
: Number(columnMetadata.dictionary_page_offset);
|
|
457
763
|
const statistics = createColumnChunkStatistics(columnMetadata.statistics, parquetSchema.findField(columnMetadata.path_in_schema));
|
|
764
|
+
const geospatialStatistics = createGeospatialStatistics(columnMetadata.geospatial_statistics);
|
|
765
|
+
const sizeStatistics = createSizeStatistics(columnMetadata.size_statistics);
|
|
458
766
|
const compressedByteLength = Number(columnMetadata.total_compressed_size);
|
|
459
767
|
const uncompressedByteLength = Number(columnMetadata.total_uncompressed_size);
|
|
460
768
|
return Object.freeze({
|
|
@@ -478,7 +786,75 @@ function createColumnChunkMetadata(columnChunk, parquetSchema) {
|
|
|
478
786
|
? undefined
|
|
479
787
|
: Number(columnChunk.offset_index_offset),
|
|
480
788
|
offsetIndexByteLength: columnChunk.offset_index_length,
|
|
481
|
-
|
|
789
|
+
bloomFilterOffset: columnMetadata.bloom_filter_offset === undefined
|
|
790
|
+
? undefined
|
|
791
|
+
: Number(columnMetadata.bloom_filter_offset),
|
|
792
|
+
bloomFilterByteLength: columnMetadata.bloom_filter_length,
|
|
793
|
+
statistics,
|
|
794
|
+
sizeStatistics,
|
|
795
|
+
geospatialStatistics
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
/** Normalizes optional Parquet size statistics without inventing missing counts. */
|
|
799
|
+
function createSizeStatistics(statistics) {
|
|
800
|
+
if (!statistics)
|
|
801
|
+
return undefined;
|
|
802
|
+
const result = {
|
|
803
|
+
unencodedByteArrayDataBytes: statistics.unencoded_byte_array_data_bytes === undefined
|
|
804
|
+
? undefined
|
|
805
|
+
: toSafeNumber(statistics.unencoded_byte_array_data_bytes),
|
|
806
|
+
repetitionLevelHistogram: normalizeSizeStatisticsList(statistics.repetition_level_histogram),
|
|
807
|
+
definitionLevelHistogram: normalizeSizeStatisticsList(statistics.definition_level_histogram)
|
|
808
|
+
};
|
|
809
|
+
if (result.unencodedByteArrayDataBytes === undefined &&
|
|
810
|
+
result.repetitionLevelHistogram === undefined &&
|
|
811
|
+
result.definitionLevelHistogram === undefined) {
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
814
|
+
return Object.freeze({
|
|
815
|
+
...result,
|
|
816
|
+
repetitionLevelHistogram: result.repetitionLevelHistogram
|
|
817
|
+
? Object.freeze(result.repetitionLevelHistogram)
|
|
818
|
+
: undefined,
|
|
819
|
+
definitionLevelHistogram: result.definitionLevelHistogram
|
|
820
|
+
? Object.freeze(result.definitionLevelHistogram)
|
|
821
|
+
: undefined
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
function toSafeNumber(value) {
|
|
825
|
+
const numberValue = typeof value === 'number' ? value : value.toNumber?.();
|
|
826
|
+
return numberValue !== undefined && Number.isSafeInteger(numberValue) && numberValue >= 0
|
|
827
|
+
? numberValue
|
|
828
|
+
: undefined;
|
|
829
|
+
}
|
|
830
|
+
/** Converts a Thrift int64 histogram only when every entry is a safe non-negative integer. */
|
|
831
|
+
function normalizeSizeStatisticsList(values) {
|
|
832
|
+
if (!values)
|
|
833
|
+
return undefined;
|
|
834
|
+
const normalized = values.map(toSafeNumber);
|
|
835
|
+
return normalized.every((value) => value !== undefined) ? normalized : undefined;
|
|
836
|
+
}
|
|
837
|
+
/** Normalizes native Parquet geospatial statistics from the decoded footer. */
|
|
838
|
+
function createGeospatialStatistics(statistics) {
|
|
839
|
+
if (!statistics)
|
|
840
|
+
return undefined;
|
|
841
|
+
const bbox = statistics.bbox
|
|
842
|
+
? Object.freeze({
|
|
843
|
+
xmin: statistics.bbox.xmin,
|
|
844
|
+
xmax: statistics.bbox.xmax,
|
|
845
|
+
ymin: statistics.bbox.ymin,
|
|
846
|
+
ymax: statistics.bbox.ymax,
|
|
847
|
+
zmin: statistics.bbox.zmin,
|
|
848
|
+
zmax: statistics.bbox.zmax,
|
|
849
|
+
mmin: statistics.bbox.mmin,
|
|
850
|
+
mmax: statistics.bbox.mmax
|
|
851
|
+
})
|
|
852
|
+
: undefined;
|
|
853
|
+
return Object.freeze({
|
|
854
|
+
bbox,
|
|
855
|
+
geometryTypes: statistics.geospatial_types
|
|
856
|
+
? Object.freeze([...statistics.geospatial_types])
|
|
857
|
+
: undefined
|
|
482
858
|
});
|
|
483
859
|
}
|
|
484
860
|
/** Decodes optional footer statistics into the column's logical value representation. */
|
|
@@ -547,7 +923,7 @@ function decodeStatisticsValueSafely(bytes, field) {
|
|
|
547
923
|
}
|
|
548
924
|
}
|
|
549
925
|
/** Normalizes one decoded row group and derives its compressed byte length. */
|
|
550
|
-
function createRowGroupMetadata(index, rowOffset, rowCount, uncompressedByteLength, columns) {
|
|
926
|
+
function createRowGroupMetadata(index, rowOffset, rowCount, uncompressedByteLength, columns, sortingColumns) {
|
|
551
927
|
const compressedByteLength = columns.reduce((sum, column) => sum + column.compressedByteLength, 0);
|
|
552
928
|
return Object.freeze({
|
|
553
929
|
index,
|
|
@@ -557,9 +933,37 @@ function createRowGroupMetadata(index, rowOffset, rowCount, uncompressedByteLeng
|
|
|
557
933
|
uncompressedSize: uncompressedByteLength,
|
|
558
934
|
compressedByteLength,
|
|
559
935
|
compressedSize: compressedByteLength,
|
|
560
|
-
columns: Object.freeze(columns)
|
|
936
|
+
columns: Object.freeze(columns),
|
|
937
|
+
sortingColumns: Object.freeze((sortingColumns || []).map(sortingColumn => Object.freeze({
|
|
938
|
+
columnIndex: sortingColumn.column_idx,
|
|
939
|
+
descending: sortingColumn.descending,
|
|
940
|
+
nullsFirst: sortingColumn.nulls_first
|
|
941
|
+
})))
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
/** Describes a phase that reads complete column chunks because no page index is available. */
|
|
945
|
+
function createFullColumnScanPlan(rowGroup, rowGroupIndex, phase, columns) {
|
|
946
|
+
const selectedColumnChunks = rowGroup.columns.filter(columnChunk => {
|
|
947
|
+
const path = columnChunk.meta_data?.path_in_schema;
|
|
948
|
+
return Boolean(path && (columns.length === 0 || columns.some(column => pathMatchesSelection(column, path))));
|
|
949
|
+
});
|
|
950
|
+
const rowCount = Number(rowGroup.num_rows);
|
|
951
|
+
return Object.freeze({
|
|
952
|
+
rowGroupIndex,
|
|
953
|
+
phase,
|
|
954
|
+
columns: Object.freeze(columns.map(column => Object.freeze([...column]))),
|
|
955
|
+
rowRanges: Object.freeze([Object.freeze({ start: 0, end: rowCount })]),
|
|
956
|
+
indexesRead: 0,
|
|
957
|
+
totalPages: 0,
|
|
958
|
+
selectedPages: 0,
|
|
959
|
+
rowsPruned: 0,
|
|
960
|
+
ranges: Object.freeze(selectedColumnChunks.map(columnChunk => Object.freeze(getColumnChunkRange(columnChunk))))
|
|
561
961
|
});
|
|
562
962
|
}
|
|
963
|
+
/** Returns whether a requested path selects a footer leaf or one of its nested children. */
|
|
964
|
+
function pathMatchesSelection(selection, path) {
|
|
965
|
+
return selection.length <= path.length && selection.every((part, index) => part === path[index]);
|
|
966
|
+
}
|
|
563
967
|
/** Returns the complete contiguous byte range occupied by one Parquet column chunk. */
|
|
564
968
|
function getColumnChunkRange(columnChunk) {
|
|
565
969
|
if (columnChunk.file_path) {
|
|
@@ -623,11 +1027,8 @@ function createParquetBatchFromArrow(metadata, schema, rowGroupIndex, rowGroupRo
|
|
|
623
1027
|
: undefined
|
|
624
1028
|
});
|
|
625
1029
|
return {
|
|
626
|
-
|
|
627
|
-
shape: 'arrow-table',
|
|
1030
|
+
...makeTableScanBatch({ shape: 'arrow-table', schema, data }),
|
|
628
1031
|
schemaType: 'explicit',
|
|
629
|
-
schema,
|
|
630
|
-
data,
|
|
631
1032
|
length: rowCount,
|
|
632
1033
|
metadata: provenance,
|
|
633
1034
|
...provenance
|
|
@@ -731,6 +1132,9 @@ function createParquetTelemetry() {
|
|
|
731
1132
|
rowGroupsRequested: 0,
|
|
732
1133
|
rowGroupsPruned: 0,
|
|
733
1134
|
rowGroupsPrunedByStatistics: 0,
|
|
1135
|
+
rowGroupsPrunedByBloomFilter: 0,
|
|
1136
|
+
bloomFiltersRead: 0,
|
|
1137
|
+
bloomFilterBytesRead: 0,
|
|
734
1138
|
rowGroupsPrunedByPageIndex: 0,
|
|
735
1139
|
pageIndexesRead: 0,
|
|
736
1140
|
pagesRead: 0,
|
|
@@ -745,6 +1149,34 @@ function createParquetTelemetry() {
|
|
|
745
1149
|
failedReadCount: 0
|
|
746
1150
|
};
|
|
747
1151
|
}
|
|
1152
|
+
/** Converts cumulative Parquet counters into one portable read-scoped execution snapshot. */
|
|
1153
|
+
function createParquetScanExecutionTelemetry(before, after, startedAt, status, earlyTerminationReason, error) {
|
|
1154
|
+
const delta = Object.fromEntries(Object.keys(after).map(key => {
|
|
1155
|
+
const telemetryKey = key;
|
|
1156
|
+
return [key, after[telemetryKey] - before[telemetryKey]];
|
|
1157
|
+
}));
|
|
1158
|
+
const rowsRead = Math.max(delta.predicateRowsTested, delta.rowsEmitted);
|
|
1159
|
+
return Object.freeze({
|
|
1160
|
+
status,
|
|
1161
|
+
sourcesPlanned: 1,
|
|
1162
|
+
sourcesRead: status === 'early-terminated' && earlyTerminationReason === 'limit' && rowsRead === 0 ? 0 : 1,
|
|
1163
|
+
batchesRead: delta.batchesEmitted,
|
|
1164
|
+
batchesDecoded: delta.rowGroupsDecoded,
|
|
1165
|
+
rowsRead,
|
|
1166
|
+
rowsTested: delta.predicateRowsTested || undefined,
|
|
1167
|
+
rowsRetained: delta.predicateRowsMatched || undefined,
|
|
1168
|
+
rowsReturned: delta.rowsEmitted,
|
|
1169
|
+
bytesRead: delta.downloadedBytes,
|
|
1170
|
+
bytesFetched: delta.downloadedBytes,
|
|
1171
|
+
filesOpened: rowsRead || delta.downloadedBytes ? 1 : 0,
|
|
1172
|
+
tasksOpened: delta.rowGroupsDecoded,
|
|
1173
|
+
rowsPruned: delta.rowsPrunedByPageIndex,
|
|
1174
|
+
durationMilliseconds: Date.now() - startedAt,
|
|
1175
|
+
earlyTerminationReason,
|
|
1176
|
+
details: Object.freeze({ ...delta }),
|
|
1177
|
+
...(error === undefined ? {} : { error })
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
748
1180
|
/** Returns a monotonic timestamp when available and falls back to wall-clock time. */
|
|
749
1181
|
function getCurrentTime() {
|
|
750
1182
|
return globalThis.performance?.now() ?? Date.now();
|
|
@@ -805,6 +1237,121 @@ function getSourceName(data, url) {
|
|
|
805
1237
|
: 'parquet';
|
|
806
1238
|
return sourceName.replace(/\.parquet$/i, '') || 'parquet';
|
|
807
1239
|
}
|
|
1240
|
+
/** Reconstructs exact row-group indexes from worker predicate batches. */
|
|
1241
|
+
function getWorkerRowIndices(workerResult) {
|
|
1242
|
+
if (!workerResult)
|
|
1243
|
+
return [];
|
|
1244
|
+
return workerResult.batches.flatMap(batch => batch.rowGroupRowIndices
|
|
1245
|
+
? [...batch.rowGroupRowIndices]
|
|
1246
|
+
: Array.from({ length: batch.rowCount }, (_, index) => batch.rowGroupRowOffset + index));
|
|
1247
|
+
}
|
|
1248
|
+
/** Gathers projected worker Arrow batches through the shared indexed Arrow table utility. */
|
|
1249
|
+
function gatherWorkerProjectedRows(workerResult, rowIndices, batchSize) {
|
|
1250
|
+
const tables = workerResult.batches.map(batch => hydrateArrowTable(batch.arrowTable));
|
|
1251
|
+
if (tables.length === 0) {
|
|
1252
|
+
return { ...workerResult, rowCount: 0, batches: [] };
|
|
1253
|
+
}
|
|
1254
|
+
const table = tables.length === 1 ? tables[0] : tables[0].concat(...tables.slice(1));
|
|
1255
|
+
const indexedTable = new IndexedArrowTable(table, rowIndices);
|
|
1256
|
+
const outputBatchSize = batchSize || Math.max(rowIndices.length, 1);
|
|
1257
|
+
const batches = [];
|
|
1258
|
+
for (let offset = 0; offset < rowIndices.length; offset += outputBatchSize) {
|
|
1259
|
+
const end = Math.min(offset + outputBatchSize, rowIndices.length);
|
|
1260
|
+
const batchIndexes = rowIndices.slice(offset, end);
|
|
1261
|
+
const batchTable = indexedTable.slice(offset, end).materializeArrowTable();
|
|
1262
|
+
batches.push({
|
|
1263
|
+
rowGroupRowOffset: batchIndexes[0],
|
|
1264
|
+
rowCount: batchIndexes.length,
|
|
1265
|
+
rowGroupRowIndices: [...batchIndexes],
|
|
1266
|
+
arrowTable: dehydrateArrowTable(batchTable)
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
return {
|
|
1270
|
+
sourceRowCount: workerResult.sourceRowCount,
|
|
1271
|
+
rowCount: rowIndices.length,
|
|
1272
|
+
batches,
|
|
1273
|
+
decodeDurationMs: workerResult.decodeDurationMs,
|
|
1274
|
+
arrowConversionDurationMs: workerResult.arrowConversionDurationMs
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
/** Applies only proven-negative Bloom-filter checks to candidate row groups. */
|
|
1278
|
+
async function filterParquetRowGroupsWithBloomFilters(initialization, rowGroupIndices, predicate, signal) {
|
|
1279
|
+
const rowGroups = [];
|
|
1280
|
+
let filtersRead = 0;
|
|
1281
|
+
let bytesRead = 0;
|
|
1282
|
+
for (const rowGroupIndex of rowGroupIndices) {
|
|
1283
|
+
const rowGroup = initialization.metadata.rowGroups[rowGroupIndex];
|
|
1284
|
+
const probes = getParquetBloomFilterProbes(predicate, rowGroup);
|
|
1285
|
+
let keepRowGroup = true;
|
|
1286
|
+
for (const probe of probes) {
|
|
1287
|
+
if (probe.column.bloomFilterOffset === undefined ||
|
|
1288
|
+
probe.column.bloomFilterByteLength === undefined) {
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
let field;
|
|
1292
|
+
try {
|
|
1293
|
+
field = initialization.parquetSchema.findField([...probe.column.path]);
|
|
1294
|
+
}
|
|
1295
|
+
catch {
|
|
1296
|
+
throwIfAborted(signal);
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
if (!field.primitiveType)
|
|
1300
|
+
continue;
|
|
1301
|
+
let filter;
|
|
1302
|
+
try {
|
|
1303
|
+
const rawColumnOrdinal = initialization.fileMetadata.row_groups[rowGroupIndex].columns.findIndex(columnChunk => JSON.stringify(columnChunk.meta_data?.path_in_schema) ===
|
|
1304
|
+
JSON.stringify(probe.column.path));
|
|
1305
|
+
const rawColumnChunk = rawColumnOrdinal >= 0
|
|
1306
|
+
? initialization.fileMetadata.row_groups[rowGroupIndex].columns[rawColumnOrdinal]
|
|
1307
|
+
: undefined;
|
|
1308
|
+
const data = await initialization.file.read(probe.column.bloomFilterOffset, probe.column.bloomFilterByteLength, signal);
|
|
1309
|
+
const decodedBloomFilter = rawColumnChunk
|
|
1310
|
+
? await initialization.reader.decryptBloomFilter(toUint8Array(data), rowGroupIndex, rawColumnOrdinal, rawColumnChunk)
|
|
1311
|
+
: toUint8Array(data);
|
|
1312
|
+
filter = decodeParquetSplitBlockBloomFilter(decodedBloomFilter);
|
|
1313
|
+
filtersRead++;
|
|
1314
|
+
bytesRead += data.byteLength;
|
|
1315
|
+
}
|
|
1316
|
+
catch {
|
|
1317
|
+
throwIfAborted(signal);
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (filter.algorithm !== 'BLOCK' ||
|
|
1321
|
+
filter.hash !== 'XXHASH' ||
|
|
1322
|
+
filter.compression !== 'UNCOMPRESSED') {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
const mayContainValue = probe.values.some(value => {
|
|
1326
|
+
try {
|
|
1327
|
+
if (value instanceof Date)
|
|
1328
|
+
return true;
|
|
1329
|
+
const physicalValue = getParquetBloomFilterPhysicalValue(value, field);
|
|
1330
|
+
const encoded = encodeParquetBloomFilterValue(physicalValue, field.primitiveType, field.typeLength);
|
|
1331
|
+
return checkParquetSplitBlockBloomFilter(filter.bitset, hashParquetBloomFilterValue(encoded));
|
|
1332
|
+
}
|
|
1333
|
+
catch {
|
|
1334
|
+
throwIfAborted(signal);
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
if (!mayContainValue) {
|
|
1339
|
+
keepRowGroup = false;
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (keepRowGroup)
|
|
1344
|
+
rowGroups.push(rowGroupIndex);
|
|
1345
|
+
}
|
|
1346
|
+
return { rowGroupIndices: rowGroups, filtersRead, bytesRead };
|
|
1347
|
+
}
|
|
1348
|
+
/** Converts logical predicate values into the physical values stored in Bloom filters. */
|
|
1349
|
+
function getParquetBloomFilterPhysicalValue(value, field) {
|
|
1350
|
+
if (field.originalType?.startsWith('DECIMAL_')) {
|
|
1351
|
+
return toPrimitive(field.originalType, value, field);
|
|
1352
|
+
}
|
|
1353
|
+
return value;
|
|
1354
|
+
}
|
|
808
1355
|
/** Returns true when at least one HTTP object validator was captured. */
|
|
809
1356
|
function hasObjectVersion(version) {
|
|
810
1357
|
return Boolean(version?.etag || version?.lastModified);
|