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