@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.
Files changed (326) 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 +15005 -3143
  35. package/dist/index.cjs.map +3 -3
  36. package/dist/index.d.ts +7 -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 +15766 -4896
  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-js-loader-types.d.ts +2 -0
  116. package/dist/parquet-js-loader-types.d.ts.map +1 -1
  117. package/dist/parquet-js-loader-types.js +2 -1
  118. package/dist/parquet-js-loader-types.js.map +1 -1
  119. package/dist/parquet-js-loader.d.ts +1 -0
  120. package/dist/parquet-js-loader.d.ts.map +1 -1
  121. package/dist/parquet-js-loader.js +2 -1
  122. package/dist/parquet-js-loader.js.map +1 -1
  123. package/dist/parquet-js-writer.d.ts +23 -2
  124. package/dist/parquet-js-writer.d.ts.map +1 -1
  125. package/dist/parquet-js-writer.js +1 -1
  126. package/dist/parquet-js-writer.js.map +1 -1
  127. package/dist/parquet-loader-base.js +1 -1
  128. package/dist/parquet-loader-options.d.ts +11 -0
  129. package/dist/parquet-loader-options.d.ts.map +1 -1
  130. package/dist/parquet-loader-options.js.map +1 -1
  131. package/dist/parquet-source-capabilities.d.ts +8 -1
  132. package/dist/parquet-source-capabilities.d.ts.map +1 -1
  133. package/dist/parquet-source-capabilities.js +10 -0
  134. package/dist/parquet-source-capabilities.js.map +1 -1
  135. package/dist/parquet-source-loader-types.d.ts +10 -0
  136. package/dist/parquet-source-loader-types.d.ts.map +1 -1
  137. package/dist/parquet-source-loader-types.js +11 -1
  138. package/dist/parquet-source-loader-types.js.map +1 -1
  139. package/dist/parquet-source-loader.cjs +14911 -4316
  140. package/dist/parquet-source-loader.cjs.map +3 -3
  141. package/dist/parquet-source-loader.d.ts +42 -4
  142. package/dist/parquet-source-loader.d.ts.map +1 -1
  143. package/dist/parquet-source-loader.js +585 -38
  144. package/dist/parquet-source-loader.js.map +1 -1
  145. package/dist/parquet-source-types.d.ts +180 -30
  146. package/dist/parquet-source-types.d.ts.map +1 -1
  147. package/dist/parquet-source-worker.js +7055 -6989
  148. package/dist/parquet-source-worker.js.map +4 -4
  149. package/dist/parquet-worker-node.cjs +8505 -1041
  150. package/dist/parquet-worker-node.cjs.map +4 -4
  151. package/dist/parquet-worker-url.js +5 -1
  152. package/dist/parquet-worker-url.js.map +1 -1
  153. package/dist/parquet-worker.js +289 -218
  154. package/dist/parquet-worker.js.map +4 -4
  155. package/dist/parquetjs/codecs/declare.d.ts +15 -0
  156. package/dist/parquetjs/codecs/declare.d.ts.map +1 -1
  157. package/dist/parquetjs/codecs/declare.js +10 -0
  158. package/dist/parquetjs/codecs/declare.js.map +1 -1
  159. package/dist/parquetjs/codecs/delta.d.ts.map +1 -1
  160. package/dist/parquetjs/codecs/delta.js +356 -7
  161. package/dist/parquetjs/codecs/delta.js.map +1 -1
  162. package/dist/parquetjs/codecs/index.d.ts.map +1 -1
  163. package/dist/parquetjs/codecs/index.js +4 -0
  164. package/dist/parquetjs/codecs/index.js.map +1 -1
  165. package/dist/parquetjs/codecs/plain.d.ts +1 -1
  166. package/dist/parquetjs/codecs/plain.d.ts.map +1 -1
  167. package/dist/parquetjs/codecs/plain.js +153 -12
  168. package/dist/parquetjs/codecs/plain.js.map +1 -1
  169. package/dist/parquetjs/codecs/rle.d.ts +4 -0
  170. package/dist/parquetjs/codecs/rle.d.ts.map +1 -1
  171. package/dist/parquetjs/codecs/rle.js +406 -3
  172. package/dist/parquetjs/codecs/rle.js.map +1 -1
  173. package/dist/parquetjs/compression.d.ts +9 -0
  174. package/dist/parquetjs/compression.d.ts.map +1 -1
  175. package/dist/parquetjs/compression.js +86 -67
  176. package/dist/parquetjs/compression.js.map +1 -1
  177. package/dist/parquetjs/encoder/dictionary-planner.d.ts.map +1 -1
  178. package/dist/parquetjs/encoder/dictionary-planner.js +5 -2
  179. package/dist/parquetjs/encoder/dictionary-planner.js.map +1 -1
  180. package/dist/parquetjs/encoder/parquet-encoder.d.ts +43 -1
  181. package/dist/parquetjs/encoder/parquet-encoder.d.ts.map +1 -1
  182. package/dist/parquetjs/encoder/parquet-encoder.js +691 -34
  183. package/dist/parquetjs/encoder/parquet-encoder.js.map +1 -1
  184. package/dist/parquetjs/parquet-thrift/BoundingBox.d.ts +29 -0
  185. package/dist/parquetjs/parquet-thrift/BoundingBox.d.ts.map +1 -0
  186. package/dist/parquetjs/parquet-thrift/BoundingBox.js +85 -0
  187. package/dist/parquetjs/parquet-thrift/BoundingBox.js.map +1 -0
  188. package/dist/parquetjs/parquet-thrift/ColumnChunk.d.ts +5 -0
  189. package/dist/parquetjs/parquet-thrift/ColumnChunk.d.ts.map +1 -1
  190. package/dist/parquetjs/parquet-thrift/ColumnChunk.js +35 -0
  191. package/dist/parquetjs/parquet-thrift/ColumnChunk.js.map +1 -1
  192. package/dist/parquetjs/parquet-thrift/ColumnCryptoMetaData.d.ts +35 -0
  193. package/dist/parquetjs/parquet-thrift/ColumnCryptoMetaData.d.ts.map +1 -0
  194. package/dist/parquetjs/parquet-thrift/ColumnCryptoMetaData.js +135 -0
  195. package/dist/parquetjs/parquet-thrift/ColumnCryptoMetaData.js.map +1 -0
  196. package/dist/parquetjs/parquet-thrift/ColumnMetaData.d.ts +10 -0
  197. package/dist/parquetjs/parquet-thrift/ColumnMetaData.d.ts.map +1 -1
  198. package/dist/parquetjs/parquet-thrift/ColumnMetaData.js +73 -0
  199. package/dist/parquetjs/parquet-thrift/ColumnMetaData.js.map +1 -1
  200. package/dist/parquetjs/parquet-thrift/EncryptionAlgorithm.d.ts +22 -0
  201. package/dist/parquetjs/parquet-thrift/EncryptionAlgorithm.d.ts.map +1 -0
  202. package/dist/parquetjs/parquet-thrift/EncryptionAlgorithm.js +111 -0
  203. package/dist/parquetjs/parquet-thrift/EncryptionAlgorithm.js.map +1 -0
  204. package/dist/parquetjs/parquet-thrift/FileCryptoMetaData.d.ts +16 -0
  205. package/dist/parquetjs/parquet-thrift/FileCryptoMetaData.d.ts.map +1 -0
  206. package/dist/parquetjs/parquet-thrift/FileCryptoMetaData.js +57 -0
  207. package/dist/parquetjs/parquet-thrift/FileCryptoMetaData.js.map +1 -0
  208. package/dist/parquetjs/parquet-thrift/FileMetaData.d.ts +5 -0
  209. package/dist/parquetjs/parquet-thrift/FileMetaData.d.ts.map +1 -1
  210. package/dist/parquetjs/parquet-thrift/FileMetaData.js +35 -0
  211. package/dist/parquetjs/parquet-thrift/FileMetaData.js.map +1 -1
  212. package/dist/parquetjs/parquet-thrift/GeospatialStatistics.d.ts +18 -0
  213. package/dist/parquetjs/parquet-thrift/GeospatialStatistics.d.ts.map +1 -0
  214. package/dist/parquetjs/parquet-thrift/GeospatialStatistics.js +62 -0
  215. package/dist/parquetjs/parquet-thrift/GeospatialStatistics.js.map +1 -0
  216. package/dist/parquetjs/parquet-thrift/SizeStatistics.d.ts +26 -0
  217. package/dist/parquetjs/parquet-thrift/SizeStatistics.d.ts.map +1 -0
  218. package/dist/parquetjs/parquet-thrift/SizeStatistics.js +85 -0
  219. package/dist/parquetjs/parquet-thrift/SizeStatistics.js.map +1 -0
  220. package/dist/parquetjs/parquet-thrift/index.d.ts +6 -0
  221. package/dist/parquetjs/parquet-thrift/index.d.ts.map +1 -1
  222. package/dist/parquetjs/parquet-thrift/index.js +6 -0
  223. package/dist/parquetjs/parquet-thrift/index.js.map +1 -1
  224. package/dist/parquetjs/parser/decoders.d.ts +10 -1
  225. package/dist/parquetjs/parser/decoders.d.ts.map +1 -1
  226. package/dist/parquetjs/parser/decoders.js +318 -14
  227. package/dist/parquetjs/parser/decoders.js.map +1 -1
  228. package/dist/parquetjs/parser/parquet-reader.d.ts +78 -12
  229. package/dist/parquetjs/parser/parquet-reader.d.ts.map +1 -1
  230. package/dist/parquetjs/parser/parquet-reader.js +605 -66
  231. package/dist/parquetjs/parser/parquet-reader.js.map +1 -1
  232. package/dist/parquetjs/schema/declare.d.ts +22 -3
  233. package/dist/parquetjs/schema/declare.d.ts.map +1 -1
  234. package/dist/parquetjs/schema/declare.js.map +1 -1
  235. package/dist/parquetjs/schema/schema.d.ts.map +1 -1
  236. package/dist/parquetjs/schema/schema.js +10 -0
  237. package/dist/parquetjs/schema/schema.js.map +1 -1
  238. package/dist/parquetjs/schema/shred.d.ts.map +1 -1
  239. package/dist/parquetjs/schema/shred.js +219 -11
  240. package/dist/parquetjs/schema/shred.js.map +1 -1
  241. package/dist/parquetjs/schema/types.js +19 -21
  242. package/dist/parquetjs/schema/types.js.map +1 -1
  243. package/dist/parquetjs/schema/variant.d.ts +3 -0
  244. package/dist/parquetjs/schema/variant.d.ts.map +1 -0
  245. package/dist/parquetjs/schema/variant.js +248 -0
  246. package/dist/parquetjs/schema/variant.js.map +1 -0
  247. package/dist/parquetjs/utils/crc32.d.ts +3 -0
  248. package/dist/parquetjs/utils/crc32.d.ts.map +1 -0
  249. package/dist/parquetjs/utils/crc32.js +15 -0
  250. package/dist/parquetjs/utils/crc32.js.map +1 -0
  251. package/dist/parquetjs/utils/read-utils.d.ts +11 -1
  252. package/dist/parquetjs/utils/read-utils.d.ts.map +1 -1
  253. package/dist/parquetjs/utils/read-utils.js +21 -1
  254. package/dist/parquetjs/utils/read-utils.js.map +1 -1
  255. package/dist/unbundled.d.ts +2 -1
  256. package/dist/unbundled.d.ts.map +1 -1
  257. package/dist/unbundled.js +1 -0
  258. package/dist/unbundled.js.map +1 -1
  259. package/package.json +36 -11
  260. package/src/bundled.ts +3 -0
  261. package/src/delta-format.ts +12 -0
  262. package/src/delta-source-loader-types.ts +40 -0
  263. package/src/delta-source.ts +305 -0
  264. package/src/delta-types.ts +40 -0
  265. package/src/iceberg-rest-catalog.ts +122 -0
  266. package/src/iceberg-table-source.ts +1037 -0
  267. package/src/iceberg-types.ts +165 -0
  268. package/src/index.ts +50 -1
  269. package/src/lib/arrow/convert-schema-from-parquet.ts +113 -19
  270. package/src/lib/arrow/create-nested-arrow-vector.ts +116 -0
  271. package/src/lib/encoders/encode-table-to-parquet-js.ts +268 -7
  272. package/src/lib/geo/geoparquet-covering.ts +201 -0
  273. package/src/lib/geo/geospatial-metadata.ts +42 -15
  274. package/src/lib/parquet-bloom-filter-planner.ts +86 -0
  275. package/src/lib/parquet-bloom-filter.ts +345 -0
  276. package/src/lib/parquet-encryption.ts +344 -0
  277. package/src/lib/parquet-page-index.ts +180 -34
  278. package/src/lib/parquet-predicate.ts +52 -16
  279. package/src/lib/parquet-source-worker-decoder.ts +116 -20
  280. package/src/lib/parquet-source-worker-types.ts +25 -0
  281. package/src/lib/parsers/get-parquet-schema.ts +3 -1
  282. package/src/lib/parsers/parse-parquet-to-arrow-js.ts +397 -17
  283. package/src/lib/parsers/parse-parquet-to-arrow.ts +4 -5
  284. package/src/lib/parsers/parse-parquet-to-columns.ts +3 -1
  285. package/src/lib/parsers/parse-parquet-to-json.ts +18 -2
  286. package/src/lib/slice-parquet-batch.ts +32 -0
  287. package/src/lib/sources/parquet-range-file.ts +35 -29
  288. package/src/lib/utils/normalize-parquet-options.ts +17 -1
  289. package/src/parquet-dataset-source.ts +397 -85
  290. package/src/parquet-js-loader-types.ts +1 -0
  291. package/src/parquet-js-loader.ts +2 -1
  292. package/src/parquet-js-writer.ts +30 -0
  293. package/src/parquet-loader-options.ts +11 -0
  294. package/src/parquet-source-capabilities.ts +18 -1
  295. package/src/parquet-source-loader-types.ts +10 -0
  296. package/src/parquet-source-loader.ts +929 -52
  297. package/src/parquet-source-types.ts +197 -28
  298. package/src/parquet-worker-url.ts +6 -1
  299. package/src/parquetjs/codecs/declare.ts +27 -0
  300. package/src/parquetjs/codecs/delta.ts +466 -6
  301. package/src/parquetjs/codecs/index.ts +4 -0
  302. package/src/parquetjs/codecs/plain.ts +177 -11
  303. package/src/parquetjs/codecs/rle.ts +573 -3
  304. package/src/parquetjs/compression.ts +102 -76
  305. package/src/parquetjs/encoder/dictionary-planner.ts +7 -2
  306. package/src/parquetjs/encoder/parquet-encoder.ts +1016 -38
  307. package/src/parquetjs/parquet-thrift/BoundingBox.ts +108 -0
  308. package/src/parquetjs/parquet-thrift/ColumnChunk.ts +35 -0
  309. package/src/parquetjs/parquet-thrift/ColumnCryptoMetaData.ts +139 -0
  310. package/src/parquetjs/parquet-thrift/ColumnMetaData.ts +73 -0
  311. package/src/parquetjs/parquet-thrift/EncryptionAlgorithm.ts +127 -0
  312. package/src/parquetjs/parquet-thrift/FileCryptoMetaData.ts +60 -0
  313. package/src/parquetjs/parquet-thrift/FileMetaData.ts +35 -0
  314. package/src/parquetjs/parquet-thrift/GeospatialStatistics.ts +68 -0
  315. package/src/parquetjs/parquet-thrift/SizeStatistics.ts +98 -0
  316. package/src/parquetjs/parquet-thrift/index.ts +6 -0
  317. package/src/parquetjs/parser/decoders.ts +451 -26
  318. package/src/parquetjs/parser/parquet-reader.ts +935 -87
  319. package/src/parquetjs/schema/declare.ts +22 -2
  320. package/src/parquetjs/schema/schema.ts +10 -0
  321. package/src/parquetjs/schema/shred.ts +238 -11
  322. package/src/parquetjs/schema/types.ts +16 -25
  323. package/src/parquetjs/schema/variant.ts +313 -0
  324. package/src/parquetjs/utils/crc32.ts +15 -0
  325. package/src/parquetjs/utils/read-utils.ts +26 -1
  326. package/src/unbundled.ts +14 -0
@@ -0,0 +1,797 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+ import { createScanQueryMetadata, DataSource as BaseDataSource, emitScanExecutionTelemetry } from '@loaders.gl/loader-utils';
5
+ import * as arrow from 'apache-arrow';
6
+ import { parseAvro } from '@loaders.gl/avro/avro-parser';
7
+ import { ParquetDatasetSource } from "./parquet-dataset-source.js";
8
+ import { PARQUET_TABLE_QUERY_CAPABILITIES } from "./parquet-source-capabilities.js";
9
+ /**
10
+ * Read-only Iceberg table metadata source.
11
+ *
12
+ * The source owns Iceberg metadata and manifest discovery, then delegates selected data files to
13
+ * `ParquetDatasetSource` for projection, filtering, range access, workers, and Arrow materialization.
14
+ */
15
+ export class IcebergTableSource extends BaseDataSource {
16
+ metadataPromise = null;
17
+ closeController = new AbortController();
18
+ closed = false;
19
+ constructor(data, options = {}, coreApi) {
20
+ super(data, options, undefined, coreApi);
21
+ }
22
+ /** Loads and validates the table metadata JSON. Results are cached for the source lifetime. */
23
+ async getMetadata(signal) {
24
+ this.assertOpen();
25
+ if (!this.metadataPromise) {
26
+ this.metadataPromise = this.loadMetadata(signal).catch(error => {
27
+ this.metadataPromise = null;
28
+ throw error;
29
+ });
30
+ }
31
+ return this.metadataPromise;
32
+ }
33
+ /** Returns the snapshot selected by the table metadata, if one exists. */
34
+ async getCurrentSnapshot(signal) {
35
+ return this.getSnapshot(undefined, undefined, signal);
36
+ }
37
+ /** Returns a selected snapshot, defaulting to the table's current snapshot. */
38
+ async getSnapshot(snapshotId, snapshotRef, signal) {
39
+ const metadata = await this.getMetadata(signal);
40
+ if (snapshotId !== undefined && snapshotRef !== undefined) {
41
+ throw new Error('Iceberg snapshotId and snapshotRef are mutually exclusive');
42
+ }
43
+ const reference = snapshotRef ? metadata.refs?.[snapshotRef] : undefined;
44
+ if (snapshotRef && !reference)
45
+ throw new Error(`Iceberg snapshot reference not found: ${snapshotRef}`);
46
+ const selectedSnapshotId = snapshotId ?? reference?.['snapshot-id'] ?? metadata['current-snapshot-id'];
47
+ if (selectedSnapshotId === undefined || selectedSnapshotId === -1)
48
+ return undefined;
49
+ return metadata.snapshots?.find(snapshot => snapshot['snapshot-id'] === selectedSnapshotId);
50
+ }
51
+ /** Builds a data and delete-file plan from one snapshot without opening data files. */
52
+ async getScanPlan(signal, snapshotId, snapshotRef) {
53
+ const metadata = await this.getMetadata(signal);
54
+ const snapshot = await this.getSnapshot(snapshotId, snapshotRef, signal);
55
+ if (!snapshot?.['manifest-list'])
56
+ return { dataFiles: [], deleteFiles: [], snapshotId, snapshotRef };
57
+ const schemaId = snapshot['schema-id'] ?? metadata['current-schema-id'];
58
+ const manifestList = await this.readAvroRecords(resolveIcebergLocation(metadata.location, snapshot['manifest-list']), signal);
59
+ const dataFiles = [];
60
+ const deleteFiles = [];
61
+ for (const manifest of manifestList) {
62
+ const isDelete = isDeleteManifest(manifest);
63
+ if (!isDelete && !isDataManifest(manifest))
64
+ continue;
65
+ const entries = await this.readAvroRecords(resolveIcebergLocation(metadata.location, manifest.manifest_path), signal);
66
+ for (const entry of entries) {
67
+ if (isDeletedManifestEntry(entry))
68
+ continue;
69
+ const dataFile = entry.data_file;
70
+ if (!dataFile || typeof dataFile.file_path !== 'string')
71
+ continue;
72
+ if (isDelete) {
73
+ deleteFiles.push(createIcebergDeleteFile(metadata.location, dataFile, manifest, snapshot['snapshot-id'], schemaId));
74
+ }
75
+ else if (dataFile.file_format?.toLowerCase() === 'parquet') {
76
+ dataFiles.push(createIcebergParquetFile(metadata.location, dataFile, manifest, snapshot['snapshot-id'], schemaId));
77
+ }
78
+ }
79
+ }
80
+ return { dataFiles, deleteFiles, snapshotId: snapshot['snapshot-id'], snapshotRef };
81
+ }
82
+ /** Reads the selected snapshot's active Parquet data files. */
83
+ async getParquetFiles(signal, snapshotId, snapshotRef) {
84
+ return (await this.getScanPlan(signal, snapshotId, snapshotRef)).dataFiles;
85
+ }
86
+ /** Reads delete files from the selected snapshot without applying them to Parquet rows. */
87
+ async getDeleteFiles(signal, snapshotId, snapshotRef) {
88
+ return (await this.getScanPlan(signal, snapshotId, snapshotRef)).deleteFiles;
89
+ }
90
+ /** Discovers the current snapshot schema and aggregate file statistics without decoding rows. */
91
+ async getQueryMetadata(options = {}) {
92
+ this.assertOpen();
93
+ const metadata = await this.getMetadata(options.signal);
94
+ const plan = await this.getScanPlan(options.signal);
95
+ if (!plan.dataFiles.length) {
96
+ throw new Error('Iceberg snapshot contains no active Parquet data files');
97
+ }
98
+ const parquetSource = this.createParquetDataset(plan, metadata, {});
99
+ try {
100
+ const schema = await parquetSource.getSchema({ signal: options.signal });
101
+ return createScanQueryMetadata({
102
+ sourceType: 'iceberg',
103
+ queryType: 'table',
104
+ execution: { status: 'supported', method: 'read' },
105
+ name: this.data,
106
+ schema,
107
+ capabilities: { table: PARQUET_TABLE_QUERY_CAPABILITIES },
108
+ statistics: {
109
+ rowCount: plan.dataFiles.reduce((sum, file) => sum + Number(file.recordCount || 0), 0),
110
+ byteLength: plan.dataFiles.reduce((sum, file) => sum + Number(file.fileSize || 0), 0)
111
+ }
112
+ });
113
+ }
114
+ finally {
115
+ await parquetSource.close();
116
+ }
117
+ }
118
+ /** Explains Iceberg manifest pruning and the delegated Parquet dataset plan. */
119
+ async explain(options = {}) {
120
+ this.assertOpen();
121
+ const metadata = await this.getMetadata(options.signal);
122
+ const plan = await this.getScanPlan(options.signal, options.snapshotId, options.snapshotRef);
123
+ const parquetSource = this.createParquetDataset(plan, metadata, options);
124
+ try {
125
+ return await parquetSource.getScanPlan(options);
126
+ }
127
+ finally {
128
+ await parquetSource.close();
129
+ }
130
+ }
131
+ /** Common table-scan entry point for the selected Iceberg snapshot. */
132
+ read(options = {}) {
133
+ return this.scan(options);
134
+ }
135
+ /** Reads the current snapshot as Arrow batches through the existing Parquet dataset source. */
136
+ async *scan(options = {}) {
137
+ this.assertOpen();
138
+ const metadata = await this.getMetadata(options.signal);
139
+ const plan = await this.getScanPlan(options.signal, options.snapshotId, options.snapshotRef);
140
+ const positionDeletes = options.applyDeletes
141
+ ? await this.loadPositionDeletes(plan.deleteFiles, metadata.location, options.signal)
142
+ : undefined;
143
+ const equalityDeletes = options.applyDeletes
144
+ ? await this.loadEqualityDeletes(plan.deleteFiles, metadata, options.signal)
145
+ : [];
146
+ const equalityColumns = getEqualityDeleteColumns(equalityDeletes);
147
+ let childTelemetry;
148
+ const readOptions = {
149
+ ...addEqualityColumns(options, equalityColumns),
150
+ onTelemetry: (telemetry) => {
151
+ childTelemetry = telemetry;
152
+ }
153
+ };
154
+ const parquetSource = this.createParquetDataset(plan, metadata, options);
155
+ let completed = false;
156
+ let readError;
157
+ let rowsReturned = 0;
158
+ try {
159
+ for await (const batch of parquetSource.read(readOptions)) {
160
+ const filteredBatch = options.applyDeletes
161
+ ? applyIcebergDeletes(batch, positionDeletes, equalityDeletes, options.columns)
162
+ : batch;
163
+ if (filteredBatch.length > 0) {
164
+ rowsReturned += filteredBatch.length;
165
+ yield filteredBatch;
166
+ }
167
+ }
168
+ completed = true;
169
+ }
170
+ catch (error) {
171
+ readError = error;
172
+ throw error;
173
+ }
174
+ finally {
175
+ await parquetSource.close();
176
+ if (childTelemetry) {
177
+ const cancelled = options.signal?.aborted === true;
178
+ const status = readError
179
+ ? cancelled
180
+ ? 'cancelled'
181
+ : 'failed'
182
+ : completed
183
+ ? childTelemetry.status
184
+ : 'early-terminated';
185
+ emitScanExecutionTelemetry(options.onTelemetry, {
186
+ ...childTelemetry,
187
+ status,
188
+ rowsReturned,
189
+ ...(readError === undefined ? {} : { error: readError })
190
+ });
191
+ }
192
+ }
193
+ }
194
+ /** Creates the delegated Parquet dataset after Iceberg metadata-level pruning. */
195
+ createParquetDataset(plan, metadata, options) {
196
+ return new ParquetDatasetSource(plan.dataFiles
197
+ .filter(file => canMatchIcebergPredicate(file, options.predicate, metadata) &&
198
+ canMatchIcebergSpatialFilter(file, options.spatialFilter, metadata))
199
+ .map(file => ({
200
+ data: file.data,
201
+ id: file.data,
202
+ partitions: getIcebergPartitions(file.partition, file.partitionSpecId, file.schemaId, metadata),
203
+ metadata: {
204
+ iceberg: {
205
+ fileSize: file.fileSize,
206
+ recordCount: file.recordCount,
207
+ partition: file.partition,
208
+ lowerBounds: file.lowerBounds,
209
+ upperBounds: file.upperBounds,
210
+ dataSequenceNumber: file.dataSequenceNumber
211
+ }
212
+ }
213
+ })), this.options, this.coreApi);
214
+ }
215
+ /** Permanently closes the source and aborts an in-flight metadata request. */
216
+ async close() {
217
+ if (!this.closed) {
218
+ this.closed = true;
219
+ this.closeController.abort();
220
+ }
221
+ }
222
+ async loadMetadata(signal) {
223
+ const abortController = new AbortController();
224
+ const abortSignal = combineAbortSignals(signal, this.closeController.signal, abortController);
225
+ try {
226
+ const response = await this.fetch(this.data, {
227
+ headers: this.options.iceberg?.headers,
228
+ signal: abortSignal.signal
229
+ });
230
+ if (!response.ok) {
231
+ throw new Error(`Iceberg metadata request failed with HTTP ${response.status}`);
232
+ }
233
+ const metadata = (await response.json());
234
+ validateIcebergTableMetadata(metadata);
235
+ return metadata;
236
+ }
237
+ finally {
238
+ abortSignal.dispose();
239
+ }
240
+ }
241
+ async readAvroRecords(url, signal) {
242
+ const abortController = new AbortController();
243
+ const abortSignal = combineAbortSignals(signal, this.closeController.signal, abortController);
244
+ try {
245
+ const response = await this.fetch(url, {
246
+ headers: this.options.iceberg?.headers,
247
+ signal: abortSignal.signal
248
+ });
249
+ if (!response.ok)
250
+ throw new Error(`Iceberg manifest request failed with HTTP ${response.status}`);
251
+ const table = await parseAvro(await response.arrayBuffer(), { longType: 'number' });
252
+ return table.data.toArray();
253
+ }
254
+ finally {
255
+ abortSignal.dispose();
256
+ }
257
+ }
258
+ /** Reads position-delete files into a source-file to row-position index. */
259
+ async loadPositionDeletes(deleteFiles, baseLocation, signal) {
260
+ const positions = new Map();
261
+ for (const deleteFile of deleteFiles) {
262
+ if (!isPositionDelete(deleteFile))
263
+ continue;
264
+ const records = await this.readAvroRecords(deleteFile.data, signal);
265
+ const filePositions = new Map();
266
+ for (const record of records) {
267
+ if (typeof record.file_path !== 'string' || typeof record.pos !== 'number')
268
+ continue;
269
+ const filePath = resolveIcebergLocation(baseLocation, record.file_path);
270
+ const rowPositions = filePositions.get(filePath) || new Set();
271
+ rowPositions.add(record.pos);
272
+ filePositions.set(filePath, rowPositions);
273
+ }
274
+ for (const [filePath, rowPositions] of filePositions) {
275
+ const deletes = positions.get(filePath) || [];
276
+ deletes.push({ positions: rowPositions, dataSequenceNumber: deleteFile.dataSequenceNumber });
277
+ positions.set(filePath, deletes);
278
+ }
279
+ }
280
+ return positions;
281
+ }
282
+ /** Reads equality-delete files and resolves their Iceberg field IDs to current schema names. */
283
+ async loadEqualityDeletes(deleteFiles, metadata, signal) {
284
+ const equalityDeletes = [];
285
+ for (const deleteFile of deleteFiles) {
286
+ if (!isEqualityDelete(deleteFile))
287
+ continue;
288
+ if (deleteFile.format.toLowerCase() !== 'avro') {
289
+ throw new Error(`Unsupported equality delete format: ${deleteFile.format}`);
290
+ }
291
+ const fieldIds = deleteFile.equalityFieldIds;
292
+ if (!fieldIds?.length)
293
+ throw new Error('Equality delete file has no field IDs');
294
+ const fields = resolveIcebergFieldNames(fieldIds, metadata, deleteFile.schemaId);
295
+ const records = await this.readAvroRecords(deleteFile.data, signal);
296
+ equalityDeletes.push({
297
+ fields,
298
+ partition: deleteFile.partition,
299
+ dataSequenceNumber: deleteFile.dataSequenceNumber,
300
+ rows: records.map(record => fields.map((field, index) => getDeleteRecordValue(record, field, fieldIds[index])))
301
+ });
302
+ }
303
+ return equalityDeletes;
304
+ }
305
+ assertOpen() {
306
+ if (this.closed)
307
+ throw new Error('Iceberg table source is closed');
308
+ }
309
+ }
310
+ /** Identifies a position-delete plan entry. */
311
+ function isPositionDelete(deleteFile) {
312
+ return deleteFile.content === 1 || deleteFile.content === 'position';
313
+ }
314
+ /** Identifies an equality-delete plan entry. */
315
+ function isEqualityDelete(deleteFile) {
316
+ return deleteFile.content === 2 || deleteFile.content === 'equality';
317
+ }
318
+ /** Adds equality-delete columns to the Parquet read without changing requested output columns. */
319
+ function addEqualityColumns(options, equalityColumns) {
320
+ if (!options.applyDeletes || equalityColumns.length === 0 || !options.columns)
321
+ return options;
322
+ const columns = [...options.columns];
323
+ for (const column of equalityColumns) {
324
+ if (!columns.includes(column))
325
+ columns.push(column);
326
+ }
327
+ return { ...options, columns };
328
+ }
329
+ /** Removes position and equality-deleted rows while retaining Arrow output and provenance. */
330
+ function applyIcebergDeletes(batch, positionsByFile, equalityDeletes, outputColumns) {
331
+ const sourcePositions = positionsByFile?.get(batch.sourceUrl || batch.source);
332
+ const keptIndexes = [];
333
+ for (let index = 0; index < batch.length; index++) {
334
+ const sourcePosition = batch.rowIndices?.[index] ?? batch.rowOffset + index;
335
+ if (sourcePositions?.some(deleteFile => deleteFile.positions.has(sourcePosition) &&
336
+ isDeleteSequenceApplicable(batch, deleteFile.dataSequenceNumber)))
337
+ continue;
338
+ if (equalityDeletes.some(deleteFile => matchesEqualityDelete(batch, index, deleteFile))) {
339
+ continue;
340
+ }
341
+ keptIndexes.push(index);
342
+ }
343
+ const allColumns = batch.data.schema.fields.map(field => field.name);
344
+ const selectedColumns = outputColumns ? [...outputColumns] : allColumns;
345
+ const needsColumnProjection = selectedColumns.length !== allColumns.length ||
346
+ selectedColumns.some((column, index) => column !== allColumns[index]);
347
+ if (keptIndexes.length === batch.length && !needsColumnProjection)
348
+ return batch;
349
+ const columns = {};
350
+ for (const columnName of selectedColumns) {
351
+ const column = batch.data.getChild(columnName);
352
+ if (!column)
353
+ throw new Error(`Equality delete column is missing from Parquet output: ${columnName}`);
354
+ columns[columnName] = keptIndexes.map(index => column.get(index));
355
+ }
356
+ const rowGroupRowIndices = keptIndexes.map(index => batch.rowGroupRowIndices?.[index] ?? batch.rowGroupRowOffset + index);
357
+ const rowIndices = keptIndexes.map(index => batch.rowIndices?.[index] ?? batch.rowOffset + index);
358
+ const data = arrow.tableFromArrays(columns);
359
+ return {
360
+ ...batch,
361
+ data,
362
+ length: keptIndexes.length,
363
+ rowCount: keptIndexes.length,
364
+ rowGroupRowIndices,
365
+ rowIndices,
366
+ metadata: batch.metadata ? { ...batch.metadata, rowCount: keptIndexes.length } : batch.metadata
367
+ };
368
+ }
369
+ /** Tests one decoded row against one equality-delete key. */
370
+ function matchesEqualityDelete(batch, rowIndex, deleteFile) {
371
+ if (!matchesDeletePartition(batch, deleteFile) || !matchesDeleteSequence(batch, deleteFile)) {
372
+ return false;
373
+ }
374
+ return deleteFile.rows.some(values => deleteFile.fields.every((field, index) => {
375
+ const column = batch.data.getChild(field);
376
+ return column ? compareDeleteValues(column.get(rowIndex), values[index]) : false;
377
+ }));
378
+ }
379
+ /** Conservatively limits equality deletes to the data-file partition they describe. */
380
+ function matchesDeletePartition(batch, deleteFile) {
381
+ if (!deleteFile.partition || !batch.datasetPartitions)
382
+ return true;
383
+ return Object.entries(deleteFile.partition).every(([key, value]) => {
384
+ const actual = batch.datasetPartitions?.[key];
385
+ return actual === undefined || compareDeleteValues(actual, value);
386
+ });
387
+ }
388
+ /** Avoids applying a delete whose sequence is not newer than the data file. */
389
+ function matchesDeleteSequence(batch, deleteFile) {
390
+ return isDeleteSequenceApplicable(batch, deleteFile.dataSequenceNumber);
391
+ }
392
+ /** Returns whether a delete sequence is newer than the current data-file sequence. */
393
+ function isDeleteSequenceApplicable(batch, deleteSequence) {
394
+ if (deleteSequence === undefined)
395
+ return true;
396
+ const metadata = batch.datasetFileMetadata?.iceberg;
397
+ if (!metadata || typeof metadata !== 'object')
398
+ return true;
399
+ const dataSequenceNumber = metadata.dataSequenceNumber;
400
+ return typeof dataSequenceNumber !== 'number' || deleteSequence > dataSequenceNumber;
401
+ }
402
+ /** Compares delete-key values, including binary, date, and numeric representations. */
403
+ function compareDeleteValues(left, right) {
404
+ if (left instanceof Uint8Array && right instanceof Uint8Array) {
405
+ return left.length === right.length && left.every((value, index) => value === right[index]);
406
+ }
407
+ if (left instanceof Date && right instanceof Date)
408
+ return left.getTime() === right.getTime();
409
+ if ((typeof left === 'number' || typeof left === 'bigint') &&
410
+ (typeof right === 'number' || typeof right === 'bigint')) {
411
+ return Number(left) === Number(right);
412
+ }
413
+ return Object.is(left, right);
414
+ }
415
+ /** Resolves equality-delete field IDs through the schema selected by the snapshot. */
416
+ function resolveIcebergFieldNames(fieldIds, metadata, schemaId) {
417
+ const schema = metadata.schemas?.find(candidate => candidate['schema-id'] === (schemaId ?? metadata['current-schema-id']));
418
+ return fieldIds.map(fieldId => {
419
+ const field = schema?.fields?.find(candidate => candidate.id === fieldId);
420
+ if (typeof field?.name !== 'string') {
421
+ throw new Error(`Equality delete field ID ${fieldId} is absent from the selected Iceberg schema`);
422
+ }
423
+ return field.name;
424
+ });
425
+ }
426
+ /** Reads a delete value by schema name, falling back to a numeric field-ID key. */
427
+ function getDeleteRecordValue(record, fieldName, fieldId) {
428
+ if (Object.prototype.hasOwnProperty.call(record, fieldName))
429
+ return record[fieldName];
430
+ return record[String(fieldId)];
431
+ }
432
+ /** Returns the unique top-level columns required to evaluate equality deletes. */
433
+ function getEqualityDeleteColumns(equalityDeletes) {
434
+ return [...new Set(equalityDeletes.flatMap(deleteFile => deleteFile.fields))];
435
+ }
436
+ /** Conservatively tests whether an Iceberg file's bounds can satisfy a Parquet predicate. */
437
+ function canMatchIcebergPredicate(file, predicate, metadata) {
438
+ if (!predicate)
439
+ return true;
440
+ if (predicate.op === 'and')
441
+ return predicate.args.every(child => canMatchIcebergPredicate(file, child, metadata));
442
+ if (predicate.op === 'or')
443
+ return predicate.args.some(child => canMatchIcebergPredicate(file, child, metadata));
444
+ if (predicate.op === 'not')
445
+ return true;
446
+ if (predicate.op === 'isNull')
447
+ return true;
448
+ const leaf = predicate;
449
+ const property = getPredicateProperty(leaf);
450
+ const bounds = getIcebergBounds(file, property, metadata);
451
+ if (!bounds)
452
+ return true;
453
+ if (leaf.op === 'in') {
454
+ return leaf.args[1].some(value => canMatchIcebergComparison(bounds, '=', value));
455
+ }
456
+ return canMatchIcebergComparison(bounds, leaf.op, leaf.args[1]);
457
+ }
458
+ /** Conservatively determines whether a data file can intersect a spatial envelope. */
459
+ function canMatchIcebergSpatialFilter(file, spatialFilter, metadata) {
460
+ if (!spatialFilter)
461
+ return true;
462
+ const bounds = getIcebergSpatialBounds(file, spatialFilter.column, metadata);
463
+ if (!bounds)
464
+ return true;
465
+ return !areBoundingBoxesDisjoint(bounds, spatialFilter.bbox);
466
+ }
467
+ /** Resolves a geometry column's conservative bounds from Iceberg lower/upper bounds. */
468
+ function getIcebergSpatialBounds(file, property, metadata) {
469
+ const lowerBounds = file.lowerBounds;
470
+ const upperBounds = file.upperBounds;
471
+ if (!lowerBounds || !upperBounds)
472
+ return undefined;
473
+ const schema = metadata.schemas?.find(candidate => candidate['schema-id'] === (file.schemaId ?? metadata['current-schema-id']));
474
+ const field = schema?.fields?.find(candidate => candidate.name === property);
475
+ const fieldId = typeof field?.id === 'number' ? field.id : undefined;
476
+ const lower = lowerBounds[property] ?? (fieldId === undefined ? undefined : lowerBounds[fieldId]);
477
+ const upper = upperBounds[property] ?? (fieldId === undefined ? undefined : upperBounds[fieldId]);
478
+ return normalizeIcebergBoundingBox(lower, upper);
479
+ }
480
+ /** Normalizes common Iceberg/GeoParquet bounding-box representations without guessing unknown data. */
481
+ function normalizeIcebergBoundingBox(lower, upper) {
482
+ const lowerValues = getBoundingBoxValues(lower);
483
+ const upperValues = getBoundingBoxValues(upper);
484
+ if (lowerValues?.length === 4) {
485
+ return [lowerValues[0], lowerValues[1], lowerValues[2], lowerValues[3]];
486
+ }
487
+ if (lowerValues?.length === 2 && upperValues?.length === 2) {
488
+ return [lowerValues[0], lowerValues[1], upperValues[0], upperValues[1]];
489
+ }
490
+ return undefined;
491
+ }
492
+ /** Reads numeric bounds from arrays or common named-bound objects. */
493
+ function getBoundingBoxValues(value) {
494
+ if (Array.isArray(value) && (value.length === 2 || value.length === 4)) {
495
+ const numbers = value.map(getNumberValue);
496
+ return numbers.every(isNumber) ? numbers : undefined;
497
+ }
498
+ if (!value || typeof value !== 'object')
499
+ return undefined;
500
+ const record = value;
501
+ const keys = ['xmin', 'ymin', 'xmax', 'ymax'];
502
+ const numbers = keys.map(key => getNumberValue(record[key]));
503
+ return numbers.every(isNumber) ? numbers : undefined;
504
+ }
505
+ /** Tests two axis-aligned envelopes without excluding touching boundaries. */
506
+ function areBoundingBoxesDisjoint(left, right) {
507
+ return left[2] < right[0] || left[0] > right[2] || left[3] < right[1] || left[1] > right[3];
508
+ }
509
+ /** Returns the top-level field name used by a Parquet predicate. */
510
+ function getPredicateProperty(predicate) {
511
+ const property = predicate.args[0].property;
512
+ return typeof property === 'string' ? property : property[0];
513
+ }
514
+ /** Resolves an Iceberg field's lower and upper bounds by name or schema field identifier. */
515
+ function getIcebergBounds(file, property, metadata) {
516
+ const lowerBounds = file.lowerBounds;
517
+ const upperBounds = file.upperBounds;
518
+ if (!lowerBounds || !upperBounds)
519
+ return undefined;
520
+ let fieldId;
521
+ const schema = metadata.schemas?.find(candidate => candidate['schema-id'] === (file.schemaId ?? metadata['current-schema-id']));
522
+ let fieldType;
523
+ for (const field of schema?.fields || []) {
524
+ if (field.name === property) {
525
+ fieldType = field.type;
526
+ if (typeof field.id === 'number')
527
+ fieldId = field.id;
528
+ }
529
+ }
530
+ const lower = decodeIcebergBound(lowerBounds[property] ?? (fieldId === undefined ? undefined : lowerBounds[fieldId]), fieldType);
531
+ const upper = decodeIcebergBound(upperBounds[property] ?? (fieldId === undefined ? undefined : upperBounds[fieldId]), fieldType);
532
+ if (lower === undefined || upper === undefined)
533
+ return undefined;
534
+ return { lower, upper };
535
+ }
536
+ /** Decodes an Iceberg binary bound when the table schema identifies a primitive type. */
537
+ function decodeIcebergBound(value, fieldType) {
538
+ if (!(value instanceof Uint8Array))
539
+ return value;
540
+ const typeName = typeof fieldType === 'string'
541
+ ? fieldType
542
+ : fieldType && typeof fieldType === 'object' && 'type' in fieldType
543
+ ? fieldType.type
544
+ : undefined;
545
+ if (typeof typeName !== 'string')
546
+ return undefined;
547
+ const dataView = new DataView(value.buffer, value.byteOffset, value.byteLength);
548
+ switch (typeName) {
549
+ case 'boolean':
550
+ return value.length === 1 ? value[0] !== 0 : undefined;
551
+ case 'int':
552
+ case 'date':
553
+ return value.length === 4 ? dataView.getInt32(0, false) : undefined;
554
+ case 'long':
555
+ case 'time':
556
+ case 'timestamp':
557
+ case 'timestz':
558
+ return value.length === 8 ? dataView.getBigInt64(0, false) : undefined;
559
+ case 'float':
560
+ return value.length === 4 ? dataView.getFloat32(0, false) : undefined;
561
+ case 'double':
562
+ return value.length === 8 ? dataView.getFloat64(0, false) : undefined;
563
+ case 'string':
564
+ return new TextDecoder().decode(value);
565
+ default:
566
+ return undefined;
567
+ }
568
+ }
569
+ /** Converts scalar Iceberg partition fields into the dataset source's reusable partition filter. */
570
+ function getIcebergPartitions(partition, partitionSpecId, schemaId, metadata) {
571
+ if (!partition)
572
+ return undefined;
573
+ const result = {};
574
+ for (const [key, value] of Object.entries(partition)) {
575
+ if (value === null ||
576
+ typeof value === 'string' ||
577
+ typeof value === 'number' ||
578
+ typeof value === 'boolean') {
579
+ result[key] = value;
580
+ }
581
+ }
582
+ const partitionSpec = metadata['partition-specs']?.find(candidate => candidate['spec-id'] === partitionSpecId);
583
+ const schema = metadata.schemas?.find(candidate => candidate['schema-id'] === (schemaId ?? metadata['current-schema-id']));
584
+ for (const field of partitionSpec?.fields || []) {
585
+ if (field.transform !== 'identity' || typeof field['source-id'] !== 'number')
586
+ continue;
587
+ const schemaField = schema?.fields?.find(candidate => candidate.id === field['source-id']);
588
+ if (typeof schemaField?.name !== 'string' || typeof field.name !== 'string')
589
+ continue;
590
+ const value = result[field.name];
591
+ if (value !== undefined)
592
+ result[schemaField.name] = value;
593
+ }
594
+ return result;
595
+ }
596
+ /** Normalizes Avro map values, which may decode as JavaScript Maps or plain objects. */
597
+ function normalizeIcebergMap(value) {
598
+ if (!value)
599
+ return undefined;
600
+ if (value instanceof Map)
601
+ return Object.fromEntries(value.entries());
602
+ if (typeof value === 'object')
603
+ return value;
604
+ return undefined;
605
+ }
606
+ /** Reads a field from plain manifest records and Arrow StructRow-like records. */
607
+ function getIcebergRecordValue(record, key) {
608
+ if (!record || typeof record !== 'object')
609
+ return undefined;
610
+ if (key in record)
611
+ return record[key];
612
+ if ('get' in record && typeof record.get === 'function') {
613
+ return record.get(key);
614
+ }
615
+ return undefined;
616
+ }
617
+ /** Normalizes Avro array values that may be decoded as Arrow vectors or JavaScript arrays. */
618
+ function normalizeNumberArray(value) {
619
+ if (typeof value === 'number')
620
+ return [value];
621
+ if (Array.isArray(value))
622
+ return value.map(getNumberValue).filter(isNumber);
623
+ if (value && typeof value === 'object' && 'toArray' in value) {
624
+ const arrayValue = value.toArray();
625
+ return arrayValue.map(getNumberValue).filter(isNumber);
626
+ }
627
+ if (value && typeof value !== 'string' && Symbol.iterator in Object(value)) {
628
+ return Array.from(value)
629
+ .map(getNumberValue)
630
+ .filter(isNumber);
631
+ }
632
+ return undefined;
633
+ }
634
+ /** Extracts a number from a JavaScript value or an Arrow scalar wrapper. */
635
+ function getNumberValue(value) {
636
+ if (typeof value === 'number')
637
+ return value;
638
+ if (typeof value === 'bigint')
639
+ return Number(value);
640
+ if (value && typeof value === 'object' && 'value' in value) {
641
+ const scalarValue = value.value;
642
+ if (typeof scalarValue === 'number')
643
+ return scalarValue;
644
+ if (typeof scalarValue === 'bigint')
645
+ return Number(scalarValue);
646
+ }
647
+ if (value && typeof value === 'object' && 'toJSON' in value) {
648
+ const jsonValue = value.toJSON();
649
+ return typeof jsonValue === 'number' ? jsonValue : undefined;
650
+ }
651
+ return undefined;
652
+ }
653
+ /** Narrows an optional number after delete-array normalization. */
654
+ function isNumber(value) {
655
+ return value !== undefined;
656
+ }
657
+ /** Applies a comparison to file bounds without pruning values that cannot be compared safely. */
658
+ function canMatchIcebergComparison(bounds, operator, value) {
659
+ const lowerComparison = compareIcebergValues(bounds.lower, value);
660
+ const upperComparison = compareIcebergValues(bounds.upper, value);
661
+ if (lowerComparison === undefined || upperComparison === undefined)
662
+ return true;
663
+ if (operator === '=')
664
+ return lowerComparison <= 0 && upperComparison >= 0;
665
+ if (operator === '<>')
666
+ return bounds.lower !== bounds.upper || lowerComparison !== 0;
667
+ if (operator === '<')
668
+ return lowerComparison < 0;
669
+ if (operator === '<=')
670
+ return lowerComparison <= 0;
671
+ if (operator === '>')
672
+ return upperComparison > 0;
673
+ return upperComparison >= 0;
674
+ }
675
+ /** Compares manifest-bound values when their JavaScript representations are safely compatible. */
676
+ function compareIcebergValues(left, right) {
677
+ if (left instanceof Uint8Array || right instanceof Uint8Array)
678
+ return undefined;
679
+ if (left instanceof Date || right instanceof Date) {
680
+ if (!(left instanceof Date) || !(right instanceof Date))
681
+ return undefined;
682
+ return left.getTime() - right.getTime();
683
+ }
684
+ const numeric = (typeof left === 'number' || typeof left === 'bigint') &&
685
+ (typeof right === 'number' || typeof right === 'bigint');
686
+ if (!numeric && typeof left !== typeof right)
687
+ return undefined;
688
+ if (!['string', 'number', 'bigint', 'boolean'].includes(typeof left))
689
+ return undefined;
690
+ if (!['string', 'number', 'bigint', 'boolean'].includes(typeof right))
691
+ return undefined;
692
+ const comparableLeft = left;
693
+ const comparableRight = right;
694
+ return comparableLeft < comparableRight ? -1 : comparableLeft > comparableRight ? 1 : 0;
695
+ }
696
+ function isDataManifest(manifest) {
697
+ return manifest.content === undefined || manifest.content === 0 || manifest.content === 'data';
698
+ }
699
+ /** Identifies delete manifests without depending on a particular Iceberg version. */
700
+ function isDeleteManifest(manifest) {
701
+ return manifest.content === 1 || manifest.content === 'deletes' || manifest.content === 'delete';
702
+ }
703
+ /** Creates a Parquet data-file plan from an Iceberg manifest entry. */
704
+ function createIcebergParquetFile(baseLocation, dataFile, manifest, snapshotId, schemaId) {
705
+ return {
706
+ data: resolveIcebergLocation(baseLocation, dataFile.file_path),
707
+ fileSize: dataFile.file_size_in_bytes,
708
+ recordCount: dataFile.record_count,
709
+ partition: normalizeIcebergMap(dataFile.partition),
710
+ lowerBounds: normalizeIcebergMap(dataFile.lower_bounds),
711
+ upperBounds: normalizeIcebergMap(dataFile.upper_bounds),
712
+ manifestPath: manifest.manifest_path,
713
+ partitionSpecId: manifest.partition_spec_id,
714
+ snapshotId,
715
+ schemaId,
716
+ dataSequenceNumber: dataFile.data_sequence_number ??
717
+ getIcebergRecordValue(dataFile, 'data_sequence_number')
718
+ };
719
+ }
720
+ /** Creates a delete-file plan while retaining only metadata needed by a future delete reader. */
721
+ function createIcebergDeleteFile(baseLocation, dataFile, manifest, snapshotId, schemaId) {
722
+ return {
723
+ data: resolveIcebergLocation(baseLocation, dataFile.file_path),
724
+ format: dataFile.file_format,
725
+ content: dataFile.content ?? manifest.content,
726
+ referencedDataFile: typeof dataFile.referenced_data_file === 'string'
727
+ ? resolveIcebergLocation(baseLocation, dataFile.referenced_data_file)
728
+ : undefined,
729
+ equalityFieldIds: normalizeNumberArray(getIcebergRecordValue(dataFile, 'equality_ids') ??
730
+ getIcebergRecordValue(dataFile, 'equality-ids')),
731
+ fileSize: dataFile.file_size_in_bytes,
732
+ recordCount: dataFile.record_count,
733
+ partition: normalizeIcebergMap(dataFile.partition),
734
+ manifestPath: manifest.manifest_path,
735
+ partitionSpecId: manifest.partition_spec_id,
736
+ snapshotId,
737
+ schemaId,
738
+ dataSequenceNumber: dataFile.data_sequence_number ??
739
+ getIcebergRecordValue(dataFile, 'data_sequence_number')
740
+ };
741
+ }
742
+ function isDeletedManifestEntry(entry) {
743
+ return entry.status === 2 || entry.status === 'deleted' || entry.status === 'DELETE';
744
+ }
745
+ function resolveIcebergLocation(baseLocation, childLocation) {
746
+ if (/^(?:s3a?):\/\//i.test(childLocation))
747
+ return resolveS3Location(childLocation);
748
+ if (/^[a-z][a-z\d+.-]*:/i.test(childLocation))
749
+ return childLocation;
750
+ try {
751
+ const baseUrl = new URL(baseLocation.endsWith('/') ? baseLocation : `${baseLocation}/`);
752
+ return normalizeS3Location(new URL(childLocation, baseUrl).toString());
753
+ }
754
+ catch {
755
+ return normalizeS3Location(`${baseLocation.replace(/\/+$/, '')}/${childLocation.replace(/^\/+/, '')}`);
756
+ }
757
+ }
758
+ /** Maps S3 URI forms used in table metadata to browser-fetchable virtual-host-neutral URLs. */
759
+ function normalizeS3Location(location) {
760
+ return /^(?:s3a?):\/\//i.test(location) ? resolveS3Location(location) : location;
761
+ }
762
+ /** Resolves an S3 or S3A URI through the anonymous S3 HTTPS endpoint. */
763
+ function resolveS3Location(location) {
764
+ const match = location.match(/^s3a?:\/\/([^/]+)\/(.*)$/i);
765
+ if (!match)
766
+ return location;
767
+ return `https://s3.amazonaws.com/${match[1]}/${match[2]}`;
768
+ }
769
+ function validateIcebergTableMetadata(metadata) {
770
+ if (!metadata || typeof metadata !== 'object') {
771
+ throw new Error('Iceberg metadata must be a JSON object');
772
+ }
773
+ if (![1, 2, 3].includes(metadata['format-version'])) {
774
+ throw new Error(`Unsupported Iceberg metadata format version: ${String(metadata['format-version'])}`);
775
+ }
776
+ if (typeof metadata.location !== 'string' || metadata.location.length === 0) {
777
+ throw new Error('Iceberg metadata location must be a non-empty string');
778
+ }
779
+ if (metadata.snapshots !== undefined && !Array.isArray(metadata.snapshots)) {
780
+ throw new Error('Iceberg metadata snapshots must be an array');
781
+ }
782
+ }
783
+ function combineAbortSignals(signal, closeSignal, controller) {
784
+ const signals = [signal, closeSignal].filter((candidate) => Boolean(candidate));
785
+ const abort = () => controller.abort();
786
+ for (const candidate of signals) {
787
+ if (candidate.aborted)
788
+ controller.abort();
789
+ else
790
+ candidate.addEventListener('abort', abort, { once: true });
791
+ }
792
+ return {
793
+ signal: controller.signal,
794
+ dispose: () => signals.forEach(candidate => candidate.removeEventListener('abort', abort))
795
+ };
796
+ }
797
+ //# sourceMappingURL=iceberg-table-source.js.map