Quazim0t0 commited on
Commit
9ae1216
·
verified ·
1 Parent(s): 3dbd21f

web: sync-guard gradient repair, FineWeb-Edu hardcoded (parquet /data streaming), dispatch-optimized backward (bit-identical), IEEE-754 oracle + corpus mutation tests, TEST_RESULTS.md

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. web/TEST_RESULTS.md +93 -0
  2. web/node_modules/.package-lock.json +35 -0
  3. web/node_modules/hyparquet/LICENSE +7 -0
  4. web/node_modules/hyparquet/README.md +248 -0
  5. web/node_modules/hyparquet/package.json +68 -0
  6. web/node_modules/hyparquet/src/assemble.js +257 -0
  7. web/node_modules/hyparquet/src/bloom.js +224 -0
  8. web/node_modules/hyparquet/src/column.js +202 -0
  9. web/node_modules/hyparquet/src/constants.js +95 -0
  10. web/node_modules/hyparquet/src/convert.js +210 -0
  11. web/node_modules/hyparquet/src/datapage.js +246 -0
  12. web/node_modules/hyparquet/src/delta.js +104 -0
  13. web/node_modules/hyparquet/src/encoding.js +162 -0
  14. web/node_modules/hyparquet/src/filter.js +170 -0
  15. web/node_modules/hyparquet/src/geoparquet.js +38 -0
  16. web/node_modules/hyparquet/src/index.js +51 -0
  17. web/node_modules/hyparquet/src/indexes.js +46 -0
  18. web/node_modules/hyparquet/src/metadata.js +339 -0
  19. web/node_modules/hyparquet/src/node.js +34 -0
  20. web/node_modules/hyparquet/src/plain.js +180 -0
  21. web/node_modules/hyparquet/src/plan.js +189 -0
  22. web/node_modules/hyparquet/src/query.js +163 -0
  23. web/node_modules/hyparquet/src/read.js +230 -0
  24. web/node_modules/hyparquet/src/rowgroup.js +225 -0
  25. web/node_modules/hyparquet/src/schema.js +159 -0
  26. web/node_modules/hyparquet/src/snappy.js +127 -0
  27. web/node_modules/hyparquet/src/thrift.js +156 -0
  28. web/node_modules/hyparquet/src/types.d.ts +563 -0
  29. web/node_modules/hyparquet/src/utils.js +290 -0
  30. web/node_modules/hyparquet/src/variant.js +366 -0
  31. web/node_modules/hyparquet/src/wkb.js +128 -0
  32. web/node_modules/hyparquet/src/xxhash.js +102 -0
  33. web/node_modules/hyparquet/types/assemble.d.ts +25 -0
  34. web/node_modules/hyparquet/types/assemble.d.ts.map +1 -0
  35. web/node_modules/hyparquet/types/bloom.d.ts +51 -0
  36. web/node_modules/hyparquet/types/bloom.d.ts.map +1 -0
  37. web/node_modules/hyparquet/types/column.d.ts +33 -0
  38. web/node_modules/hyparquet/types/column.d.ts.map +1 -0
  39. web/node_modules/hyparquet/types/constants.d.ts +17 -0
  40. web/node_modules/hyparquet/types/constants.d.ts.map +1 -0
  41. web/node_modules/hyparquet/types/convert.d.ts +38 -0
  42. web/node_modules/hyparquet/types/convert.d.ts.map +1 -0
  43. web/node_modules/hyparquet/types/datapage.d.ts +33 -0
  44. web/node_modules/hyparquet/types/datapage.d.ts.map +1 -0
  45. web/node_modules/hyparquet/types/delta.d.ts +20 -0
  46. web/node_modules/hyparquet/types/delta.d.ts.map +1 -0
  47. web/node_modules/hyparquet/types/encoding.d.ts +23 -0
  48. web/node_modules/hyparquet/types/encoding.d.ts.map +1 -0
  49. web/node_modules/hyparquet/types/filter.d.ts +43 -0
  50. web/node_modules/hyparquet/types/filter.d.ts.map +1 -0
web/TEST_RESULTS.md ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Test results — 2026-07-15
2
+
3
+ All suites run with `npm test` (chains all nine). Every suite exits 0.
4
+ Hardware for GPU numbers: NVIDIA via WebGPU, DP4A int8 dot path, exact-gated
5
+ against the verified units at init.
6
+
7
+ | suite | what it proves | result |
8
+ |---|---|---|
9
+ | `test_core.js` | float trainer converges, replicas bit-identical | PASS — loss 33.71 → 0.000000, replica diff 0.000e+0 |
10
+ | `test_verified.js` | training THROUGH the int8 units converges, replicas bit-identical | PASS — loss 300.6 → 1.41, replica diff 0.000e+0 |
11
+ | `test_ieee.js` | the JS epilogue mirror is IEEE-754 spec-correct, not merely agreeable | PASS — 500k+ checks, 0 disagreements; rejects the old round-once mirror on 34% of inputs |
12
+ | `test_gates.js` | the exact kernel gate rejects real bugs, accepts the real kernel | PASS — 5/5 injected bugs rejected, clean kernel accepted |
13
+ | `test_metamorphic.js` | correctness properties that need no reference implementation | PASS — 6/6 properties hold; catches stride/swap/acc= mutants |
14
+ | `test_corpus.js` | mutation-scores the oracles with an external bug taxonomy | PASS — properties 2/4 (2/2 loop, 0/2 math), differential 4/4, control clean |
15
+ | `test_optimizer.js` | DaisyAdam beats SGD through the units, deterministic replicas | PASS — 1.59 vs 1.95, replica diff 0.000e+0 |
16
+ | `test_transformer.js` | the transformer LM trains through the units end to end | PASS — loss 4.75 → 1.26 (baseline 4.56), replica diff 0.000e+0 |
17
+ | `test_unit_backward.js` | int8 STE gradients do not damage convergence | PASS — units/float loss ratio 1.007 |
18
+
19
+ ## The IEEE-754 oracle (`test_ieee.js`)
20
+
21
+ Built from the binary32 definition in exact BigInt arithmetic — no
22
+ `Math.fround` anywhere in the oracle, so neither side was tuned to the other.
23
+
24
+ ```
25
+ i32ToF32Spec matches Math.fround on 200000 random int32 (incl. |s| > 2^24)
26
+ mulF32Spec matches the correctly-rounded product on 300000 draws (subnormal..overflow)
27
+ Verified.epi vs the oracle: 200000 random triples, 0 disagreements
28
+ tie-to-even ladder around 2^24: 378 cases, 0 disagreements
29
+ bgemmJS outputs rebuilt from the raw int32 accumulator via the oracle: 0 disagreements
30
+ the oracle REJECTS the round-once mirror shipped before: 68314/200000 inputs (34.16%)
31
+ ```
32
+
33
+ That last line is the teeth: an oracle that never disagrees with anything
34
+ proves nothing. This one rejects the exact bug the old `1e-6` tolerance hid.
35
+
36
+ ## Oracle mutation scores (`test_corpus.js`)
37
+
38
+ Bugs ported from an external taxonomy
39
+ ([dipankarsarkar/gpuemu-corpus](https://huggingface.co/datasets/dipankarsarkar/gpuemu-corpus))
40
+ so the bug list has a different author than the checks.
41
+
42
+ | bug | lives in | properties | differential |
43
+ |---|---|---|---|
44
+ | `acc=` instead of `acc+=` | loop | CAUGHT (sensitivity) | CAUGHT |
45
+ | missing bounds guard (mult-of-8) | loop | CAUGHT (nonTriviality) | CAUGHT |
46
+ | dropped constant factor (2×) | math | missed | CAUGHT |
47
+ | wrong leaky-ReLU alpha | math | missed | CAUGHT |
48
+
49
+ The split is the finding: properties are a **loop-bug detector** (2/2), blind
50
+ to structure-preserving value bugs (0/2). Those are owned by the differential
51
+ gate against the spec-correct mirror. Each check class has an owner.
52
+
53
+ ## Backward rework: bit-identity + GPU wall clock
54
+
55
+ The backward was reworked for dispatch efficiency: independent GEMMs
56
+ overlapped, the QKV weight-gradient and dln1in trios fused into single
57
+ batched (batch=3) GEMMs, and the `g.emb` operand quantized column-wise in one
58
+ pass instead of transpose-then-quantize. **None of this may change a bit** —
59
+ block scales are per-row/per-column per batch element, so fusion is exact,
60
+ and every fused sum keeps the original per-add f32 rounding schedule.
61
+
62
+ Bit-identity, old backward vs new (CPU mirrors, Node):
63
+
64
+ ```
65
+ char-96, float backward: loss + all 20480 gradient floats bit-identical
66
+ char-96, unit backward: loss + all 20480 gradient floats bit-identical
67
+ Spikewhale 16k, float backward: loss + all 545792 gradient floats bit-identical
68
+ Spikewhale 16k, unit backward: loss + all 545792 gradient floats bit-identical
69
+ ```
70
+
71
+ GPU (c=32 t=32 b=8 layers=2 heads=2, 16512-token vocab, 15 measured steps,
72
+ gradient FNV hash compared old vs new on-device):
73
+
74
+ | config | ms/step | grad hash |
75
+ |---|---|---|
76
+ | old backward, float | 286 | `7ff11308` |
77
+ | old backward, units | 466 | `62596547` |
78
+ | new backward, float | 286 | `7ff11308` (identical) |
79
+ | new backward, units | 346 | `62596547` (identical) |
80
+
81
+ - float path: unchanged speed, unchanged bits — this is what ships enabled.
82
+ - unit-backward path (dormant, `cfg.unitBackward`): cost drops **1.63× → 1.21×**
83
+ vs float. Because the rework is bit-identical, the convergence curves from
84
+ the unit-backward experiment stand unchanged; only the wall-clock exchange
85
+ rate moved. At equal wall clock float still wins (~1.5% at the 200-step
86
+ horizon), so `unitBackward` stays off by default.
87
+
88
+ One bug was caught during the rework, by the bit-identity check itself: the
89
+ fused q+k+v sum initially ran in f64 and rounded once, where the old code
90
+ rounded to f32 after each add — a last-ulp fork that would have split
91
+ replicas. Fixed by matching the rounding schedule (`Math.fround` per add).
92
+ Same lesson as the epilogue mirror: match the rounding schedule, not just
93
+ the values.
web/node_modules/.package-lock.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "daisychain-web",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "node_modules/hyparquet": {
8
+ "version": "1.26.2",
9
+ "resolved": "https://registry.npmjs.org/hyparquet/-/hyparquet-1.26.2.tgz",
10
+ "integrity": "sha512-6qjyK7R2tZ4vnYP1J8NpcCeDPK1UIYk3xh5XgtQUnUM1iwkYbvI6Mz3xtmpMd6AfCCeoUoJVbEq29k5GReZRWA==",
11
+ "license": "MIT"
12
+ },
13
+ "node_modules/ws": {
14
+ "version": "8.21.0",
15
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
16
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
17
+ "license": "MIT",
18
+ "engines": {
19
+ "node": ">=10.0.0"
20
+ },
21
+ "peerDependencies": {
22
+ "bufferutil": "^4.0.1",
23
+ "utf-8-validate": ">=5.0.2"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "bufferutil": {
27
+ "optional": true
28
+ },
29
+ "utf-8-validate": {
30
+ "optional": true
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
web/node_modules/hyparquet/LICENSE ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
web/node_modules/hyparquet/README.md ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # hyparquet
2
+
3
+ ![hyparquet parakeet](hyparquet.jpg)
4
+
5
+ [![npm](https://img.shields.io/npm/v/hyparquet)](https://www.npmjs.com/package/hyparquet)
6
+ [![minzipped](https://img.shields.io/bundlephobia/minzip/hyparquet)](https://www.npmjs.com/package/hyparquet)
7
+ [![workflow status](https://github.com/hyparam/hyparquet/actions/workflows/ci.yml/badge.svg)](https://github.com/hyparam/hyparquet/actions)
8
+ [![mit license](https://img.shields.io/badge/License-MIT-orange.svg)](https://opensource.org/licenses/MIT)
9
+ ![coverage](https://img.shields.io/badge/Coverage-92-darkred)
10
+ [![dependencies](https://img.shields.io/badge/Dependencies-0-blueviolet)](https://www.npmjs.com/package/hyparquet?activeTab=dependencies)
11
+
12
+ Dependency free since 2023!
13
+
14
+ ## What is hyparquet?
15
+
16
+ **Hyparquet** is a JavaScript library for parsing [Apache Parquet](https://parquet.apache.org) files in the browser. Apache Parquet is a popular columnar storage format that is widely used in data engineering, data science, and machine learning applications for storing large datasets. Hyparquet is designed to read parquet files efficiently over http, so that parquet files in cloud storage can be queried directly from the browser without needing a server.
17
+
18
+ > Part of **[HypStack](https://hypstack.ai/)**, an open-source stack for AI observability.
19
+
20
+ - Works in browsers and node.js
21
+ - Pure JavaScript, no dependencies
22
+ - Supports all parquet types, encodings, and compression codecs
23
+ - Minimizes data fetching using HTTP range requests
24
+ - Includes TypeScript definitions
25
+
26
+ ## Parquet Viewer
27
+
28
+ **Try hyparquet online**: Drag and drop your parquet file onto [hyperparam.app](https://hyperparam.app) to view it directly in your browser. This service is powered by hyparquet's in-browser capabilities.
29
+
30
+ [![hyperparam parquet viewer](./hyperparam.png)](https://hyperparam.app/)
31
+
32
+ ## Quick Start
33
+
34
+ ### Browser Example
35
+
36
+ In the browser use `asyncBufferFromUrl` to wrap a url for reading asynchronously over the network.
37
+ It is recommended that you filter by row and column to limit fetch size:
38
+
39
+ ```javascript
40
+ const { asyncBufferFromUrl, parquetReadObjects } = await import('https://cdn.jsdelivr.net/npm/hyparquet/src/hyparquet.min.js')
41
+
42
+ const url = 'https://hyperparam-public.s3.amazonaws.com/bunnies.parquet'
43
+ const file = await asyncBufferFromUrl({ url }) // wrap url for async fetching
44
+ const data = await parquetReadObjects({
45
+ file,
46
+ columns: ['Breed Name', 'Lifespan'],
47
+ rowStart: 10,
48
+ rowEnd: 20,
49
+ })
50
+ ```
51
+
52
+ ### Node.js Example
53
+
54
+ To read the contents of a local parquet file in a node.js environment use `asyncBufferFromFile`:
55
+
56
+ ```javascript
57
+ const { asyncBufferFromFile, parquetReadObjects } = await import('hyparquet')
58
+
59
+ const file = await asyncBufferFromFile('example.parquet')
60
+ const data = await parquetReadObjects({ file })
61
+ ```
62
+
63
+ Note: hyparquet is published as an ES module, so dynamic `import()` may be required for old versions of node.
64
+
65
+ ## Parquet Writing
66
+
67
+ To create parquet files from javascript, check out the [hyparquet-writer](https://github.com/hyparam/hyparquet-writer) package.
68
+
69
+ ## Advanced Usage
70
+
71
+ ### Reading Metadata
72
+
73
+ You can read just the metadata, including schema and data statistics using the `parquetMetadataAsync` function. This is useful for getting the schema, number of rows, and column names without reading the entire file.
74
+
75
+ ```javascript
76
+ import { parquetMetadataAsync, parquetSchema } from 'hyparquet'
77
+
78
+ const file = await asyncBufferFromUrl({ url })
79
+ const metadata = await parquetMetadataAsync(file)
80
+ // Get total number of rows (convert bigint to number)
81
+ const numRows = Number(metadata.num_rows)
82
+ // Get nested table schema
83
+ const schema = parquetSchema(metadata)
84
+ // Get top-level column header names
85
+ const columnNames = schema.children.map(e => e.element.name)
86
+ ```
87
+
88
+ ### AsyncBuffer
89
+
90
+ Hyparquet requires an argument `file` of type `AsyncBuffer`. An `AsyncBuffer` is similar to a js `ArrayBuffer` but the `slice` method can return async `Promise<ArrayBuffer>`. This makes it a useful way to represent a remote file.
91
+
92
+ ```typescript
93
+ type Awaitable<T> = T | Promise<T>
94
+ interface AsyncBuffer {
95
+ byteLength: number
96
+ slice(start: number, end?: number): Awaitable<ArrayBuffer>
97
+ }
98
+ ```
99
+
100
+ In most cases, you should probably use `asyncBufferFromUrl` or `asyncBufferFromFile` to create an `AsyncBuffer` for hyparquet.
101
+
102
+ #### asyncBufferFromUrl
103
+
104
+ If you want to read a parquet file remotely over http, use `asyncBufferFromUrl` to wrap an http url as an `AsyncBuffer` using http range requests.
105
+
106
+ - Pass `requestInit` option to provide additional fetch headers for authentication (optional)
107
+ - Pass `byteLength` if you know the file size to save a round trip HEAD request (optional)
108
+
109
+ ```typescript
110
+ const url = 'https://s3.hyperparam.app/wiki_en.parquet'
111
+ const requestInit = { headers: { Authorization: 'Bearer my_token' } } // auth header
112
+ const byteLength = 415958713 // optional
113
+ const file: AsyncBuffer = await asyncBufferFromUrl({ url, requestInit, byteLength })
114
+ const data = await parquetReadObjects({ file })
115
+ ```
116
+
117
+ #### asyncBufferFromFile
118
+
119
+ If you are in a node.js environment, use `asyncBufferFromFile` to wrap a local file as an `AsyncBuffer`:
120
+
121
+ ```typescript
122
+ import { asyncBufferFromFile, parquetReadObjects } from 'hyparquet'
123
+
124
+ const file: AsyncBuffer = await asyncBufferFromFile('example.parquet')
125
+ const data = await parquetReadObjects({ file })
126
+ ```
127
+
128
+ #### ArrayBuffer
129
+
130
+ You can provide an `ArrayBuffer` anywhere that an `AsyncBuffer` is expected. This is useful if you already have the entire parquet file in memory.
131
+
132
+ ### parquetRead vs parquetReadObjects
133
+
134
+ #### parquetReadObjects
135
+
136
+ `parquetReadObjects` is a convenience wrapper around `parquetRead` that returns the complete rows as `Promise<Record<string, any>[]>`. This is the simplest way to read parquet files.
137
+
138
+ ```typescript
139
+ parquetReadObjects({ file }): Promise<Record<string, any>[]>
140
+ ```
141
+
142
+ #### parquetRead
143
+
144
+ `parquetRead` is the "base" function for reading parquet files.
145
+ It returns a `Promise<void>` that resolves when the file has been read or rejected if an error occurs.
146
+ Data is returned via `onComplete` or `onChunk` or `onPage` callbacks passed as arguments.
147
+
148
+ The reason for this design is that parquet is a column-oriented format, and returning data in row-oriented format requires transposing the column data. This is an expensive operation in javascript. If you don't pass in an `onComplete` argument to `parquetRead`, hyparquet will skip this transpose step and save memory.
149
+
150
+ ### Chunk Streaming
151
+
152
+ The `onChunk` callback returns column-oriented data as it is ready. `onChunk` will always return top-level columns, including structs, assembled as a single column. This may require waiting for multiple sub-columns to all load before assembly can occur.
153
+
154
+ The `onPage` callback returns column-oriented page data as it is ready. `onPage` will NOT assemble struct columns and will always return individual sub-column data. Note that `onPage` _will_ assemble nested lists.
155
+
156
+ In some cases, `onPage` can return data sooner than `onChunk`.
157
+
158
+ ```typescript
159
+ interface ColumnData {
160
+ columnName: string
161
+ columnData: ArrayLike<any>
162
+ rowStart: number
163
+ rowEnd: number
164
+ }
165
+ await parquetRead({
166
+ file,
167
+ onChunk(chunk: ColumnData) {
168
+ console.log('chunk', chunk)
169
+ },
170
+ onPage(chunk: ColumnData) {
171
+ console.log('page', chunk)
172
+ },
173
+ })
174
+ ```
175
+
176
+ ### Returned row format
177
+
178
+ By default, the `onComplete` function returns an **array** of values for each row: `[value]`. If you would prefer each row to be an **object**: `{ columnName: value }`, set the option `rowFormat` to `'object'`.
179
+
180
+ ```javascript
181
+ import { parquetRead } from 'hyparquet'
182
+
183
+ await parquetRead({
184
+ file,
185
+ rowFormat: 'object',
186
+ onComplete: data => console.log(data),
187
+ })
188
+ ```
189
+
190
+ The `parquetReadObjects` function defaults to `rowFormat: 'object'`.
191
+
192
+ ### Binary columns
193
+
194
+ Hyparquet defaults to decoding binary columns as utf8 text strings. A parquet `BYTE_ARRAY` column may contain arbitrary binary data or utf8 encoded text data. In theory, a column should be annotated as [LogicalType](https://github.com/apache/parquet-format/blob/master/LogicalTypes.md) STRING if it contains utf8 text. But in practice, many parquet files omit this annotation. Hyparquet's default decoding behavior can be disabled by setting the `utf8` option to `false`. The `utf8` option only affects `BYTE_ARRAY` columns _without_ an annotation.
195
+
196
+ If Hyparquet detects a [GeoParquet](https://geoparquet.org/) file, any geospatial column will be marked with the GEOMETRY or GEOGRAPHY logical type and decoded to GeoJSON geometries. Set the `geoparquet` option to `false` to disable this behavior.
197
+
198
+ ## Compression
199
+
200
+ By default, hyparquet supports uncompressed and snappy-compressed parquet files.
201
+ To support the full range of parquet compression codecs (gzip, brotli, zstd, etc), use the [hyparquet-compressors](https://github.com/hyparam/hyparquet-compressors) package.
202
+
203
+ ```javascript
204
+ import { parquetReadObjects } from 'hyparquet'
205
+ import { compressors } from 'hyparquet-compressors'
206
+
207
+ const data = await parquetReadObjects({ file, compressors })
208
+ ```
209
+
210
+ | Codec | hyparquet | with hyparquet-compressors |
211
+ |---------------|-----------|----------------------------|
212
+ | Uncompressed | ✅ | ✅ |
213
+ | Snappy | ✅ | ✅ |
214
+ | GZip | ❌ | ✅ |
215
+ | LZO | ❌ | ✅ |
216
+ | Brotli | ❌ | ✅ |
217
+ | LZ4 | ❌ | ✅ |
218
+ | ZSTD | ❌ | ✅ |
219
+ | LZ4_RAW | ❌ | ✅ |
220
+
221
+ ## References
222
+
223
+ - https://github.com/apache/parquet-format
224
+ - https://github.com/apache/parquet-testing
225
+ - https://github.com/apache/thrift
226
+ - https://github.com/apache/arrow
227
+ - https://github.com/dask/fastparquet
228
+ - https://github.com/duckdb/duckdb
229
+ - https://github.com/google/snappy
230
+ - https://github.com/hyparam/hightable
231
+ - https://github.com/hyparam/hysnappy
232
+ - https://github.com/hyparam/hyparquet-compressors
233
+ - https://github.com/ironSource/parquetjs
234
+ - https://github.com/zhipeng-jia/snappyjs
235
+
236
+ Sample project that shows how to build a parquet viewer using hyparquet, react, and [HighTable](https://github.com/hyparam/hightable):
237
+
238
+ - Hyparquet Demo: [https://hyparam.github.io/demos/hyparquet/](https://hyparam.github.io/demos/hyparquet/)
239
+ - Hyparquet Demo Source Code: [https://github.com/hyparam/demos/tree/master/hyparquet](https://github.com/hyparam/demos/tree/master/hyparquet)
240
+
241
+
242
+
243
+ ## Contributions
244
+
245
+ Contributions are welcome!
246
+ If you have suggestions, bug reports, or feature requests, please open an issue or submit a pull request.
247
+
248
+ Hyparquet development is supported by an open-source grant from Hugging Face :hugs:
web/node_modules/hyparquet/package.json ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "hyparquet",
3
+ "version": "1.26.2",
4
+ "description": "Parquet file parser for JavaScript",
5
+ "author": "Hyperparam",
6
+ "homepage": "https://hyperparam.app",
7
+ "keywords": [
8
+ "ai",
9
+ "data",
10
+ "dataset",
11
+ "hyperparam",
12
+ "hyparquet",
13
+ "geoparquet",
14
+ "llm",
15
+ "ml",
16
+ "parquet",
17
+ "parquetjs",
18
+ "parser",
19
+ "snappy",
20
+ "thrift"
21
+ ],
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/hyparam/hyparquet.git"
26
+ },
27
+ "files": [
28
+ "src",
29
+ "types"
30
+ ],
31
+ "type": "module",
32
+ "types": "types/index.d.ts",
33
+ "main": "src/index.js",
34
+ "exports": {
35
+ ".": {
36
+ "browser": {
37
+ "types": "./types/index.d.ts",
38
+ "import": "./src/index.js"
39
+ },
40
+ "default": {
41
+ "types": "./types/node.d.ts",
42
+ "import": "./src/node.js"
43
+ }
44
+ },
45
+ "./src/*.js": {
46
+ "types": "./types/*.d.ts",
47
+ "import": "./src/*.js"
48
+ }
49
+ },
50
+ "sideEffects": false,
51
+ "scripts": {
52
+ "build:types": "tsc -p ./tsconfig.build.json",
53
+ "coverage": "vitest run --coverage --coverage.include=src",
54
+ "lint": "eslint",
55
+ "lint:fix": "eslint --fix",
56
+ "prepare": "npm run build:types",
57
+ "test": "vitest run"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "26.0.1",
61
+ "@vitest/coverage-v8": "4.1.9",
62
+ "eslint": "9.39.4",
63
+ "eslint-plugin-jsdoc": "63.0.10",
64
+ "hyparquet-compressors": "1.1.1",
65
+ "typescript": "6.0.3",
66
+ "vitest": "4.1.9"
67
+ }
68
+ }
web/node_modules/hyparquet/src/assemble.js ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getMaxDefinitionLevel, isListLike, isMapLike } from './schema.js'
2
+ import { decodeVariantColumn } from './variant.js'
3
+
4
+ /**
5
+ * Reconstructs a complex nested structure from flat arrays of values and
6
+ * definition and repetition levels, according to Dremel encoding.
7
+ *
8
+ * @param {any[]} output
9
+ * @param {number[] | undefined} definitionLevels
10
+ * @param {number[]} repetitionLevels
11
+ * @param {DecodedArray} values
12
+ * @param {SchemaTree[]} schemaPath
13
+ * @returns {DecodedArray}
14
+ */
15
+ export function assembleLists(output, definitionLevels, repetitionLevels, values, schemaPath) {
16
+ const maxDefinitionLevel = getMaxDefinitionLevel(schemaPath)
17
+ // If no def/rep levels, synthesize def levels at max
18
+ if (!definitionLevels?.length && !repetitionLevels.length) {
19
+ if (!maxDefinitionLevel || !values.length) return values
20
+ definitionLevels = new Array(values.length).fill(maxDefinitionLevel)
21
+ }
22
+ const n = definitionLevels?.length || repetitionLevels.length
23
+ const repetitionPath = schemaPath.map(({ element }) => element.repetition_type)
24
+ let valueIndex = 0
25
+
26
+ // Track state of nested structures
27
+ const containerStack = [output]
28
+ let currentContainer = output
29
+ let currentDepth = 0 // schema depth
30
+ let currentDefLevel = 0 // list depth
31
+ let currentRepLevel = 0
32
+
33
+ if (repetitionLevels[0]) {
34
+ // continue previous row
35
+ while (currentDepth < repetitionPath.length - 2 && currentRepLevel < repetitionLevels[0]) {
36
+ currentDepth++
37
+ if (repetitionPath[currentDepth] !== 'REQUIRED') {
38
+ // go into last list
39
+ currentContainer = currentContainer.at(-1)
40
+ containerStack.push(currentContainer)
41
+ currentDefLevel++
42
+ }
43
+ if (repetitionPath[currentDepth] === 'REPEATED') currentRepLevel++
44
+ }
45
+ }
46
+
47
+ for (let i = 0; i < n; i++) {
48
+ // assert(currentDefLevel === containerStack.length - 1)
49
+ const def = definitionLevels?.length ? definitionLevels[i] : maxDefinitionLevel
50
+ const rep = repetitionLevels[i]
51
+
52
+ // Pop up to start of rep level
53
+ while (currentDepth && (rep < currentRepLevel || repetitionPath[currentDepth] !== 'REPEATED')) {
54
+ if (repetitionPath[currentDepth] !== 'REQUIRED') {
55
+ containerStack.pop()
56
+ currentDefLevel--
57
+ }
58
+ if (repetitionPath[currentDepth] === 'REPEATED') currentRepLevel--
59
+ currentDepth--
60
+ }
61
+ // @ts-expect-error won't be empty
62
+ currentContainer = containerStack.at(-1)
63
+
64
+ // Go deeper to end of definition level
65
+ while (
66
+ (currentDepth < repetitionPath.length - 2 || repetitionPath[currentDepth + 1] === 'REPEATED') &&
67
+ (currentDefLevel < def || repetitionPath[currentDepth + 1] === 'REQUIRED')
68
+ ) {
69
+ currentDepth++
70
+ if (repetitionPath[currentDepth] !== 'REQUIRED') {
71
+ /** @type {any[]} */
72
+ const newList = []
73
+ currentContainer.push(newList)
74
+ currentContainer = newList
75
+ containerStack.push(newList)
76
+ currentDefLevel++
77
+ }
78
+ if (repetitionPath[currentDepth] === 'REPEATED') currentRepLevel++
79
+ }
80
+
81
+ // Add value or null based on definition level
82
+ if (def === maxDefinitionLevel) {
83
+ // assert(currentDepth === maxDefinitionLevel || currentDepth === repetitionPath.length - 2)
84
+ currentContainer.push(values[valueIndex++])
85
+ } else if (currentDepth === repetitionPath.length - 2) {
86
+ currentContainer.push(null)
87
+ } else {
88
+ currentContainer.push([])
89
+ }
90
+ }
91
+
92
+ // Handle edge cases for empty inputs or single-level data
93
+ if (!output.length) {
94
+ // return max definition level of nested lists
95
+ for (let i = 0; i < maxDefinitionLevel; i++) {
96
+ /** @type {any[]} */
97
+ const newList = []
98
+ currentContainer.push(newList)
99
+ currentContainer = newList
100
+ }
101
+ }
102
+
103
+ return output
104
+ }
105
+
106
+ /**
107
+ * Assemble a nested structure from subcolumn data.
108
+ *
109
+ * @param {Map<string, DecodedArray>} subcolumnData
110
+ * @param {SchemaTree} schema top-level schema element
111
+ * @param {ParquetParsers} parsers
112
+ * @param {number} [depth] depth of nested structure
113
+ */
114
+ export function assembleNested(subcolumnData, schema, parsers, depth = 0) {
115
+ const path = schema.path.join('.')
116
+ const optional = schema.element.repetition_type === 'OPTIONAL'
117
+ const nextDepth = optional ? depth + 1 : depth
118
+
119
+ if (isListLike(schema)) {
120
+ let sublist = schema.children[0]
121
+ let subDepth = nextDepth
122
+ if (sublist.children.length === 1) {
123
+ sublist = sublist.children[0]
124
+ subDepth++
125
+ }
126
+ assembleNested(subcolumnData, sublist, parsers, subDepth)
127
+
128
+ const subcolumn = sublist.path.join('.')
129
+ const values = subcolumnData.get(subcolumn)
130
+ if (!values) throw new Error('parquet list column missing values')
131
+ if (optional) flattenAtDepth(values, depth)
132
+ subcolumnData.set(path, values)
133
+ subcolumnData.delete(subcolumn)
134
+ return
135
+ }
136
+
137
+ if (isMapLike(schema)) {
138
+ const mapName = schema.children[0].element.name
139
+
140
+ // Assemble keys and values
141
+ assembleNested(subcolumnData, schema.children[0].children[0], parsers, nextDepth + 1)
142
+ assembleNested(subcolumnData, schema.children[0].children[1], parsers, nextDepth + 1)
143
+
144
+ const keys = subcolumnData.get(`${path}.${mapName}.key`)
145
+ const values = subcolumnData.get(`${path}.${mapName}.value`)
146
+
147
+ if (!keys) throw new Error('parquet map column missing keys')
148
+ if (!values) throw new Error('parquet map column missing values')
149
+ if (keys.length !== values.length) {
150
+ throw new Error('parquet map column key/value length mismatch')
151
+ }
152
+
153
+ const out = assembleMaps(keys, values, nextDepth)
154
+ if (optional) flattenAtDepth(out, depth)
155
+
156
+ subcolumnData.delete(`${path}.${mapName}.key`)
157
+ subcolumnData.delete(`${path}.${mapName}.value`)
158
+ subcolumnData.set(path, out)
159
+ return
160
+ }
161
+
162
+ // Struct-like column
163
+ if (schema.children.length) {
164
+ // construct a meta struct and then invert
165
+ const invertDepth = schema.element.repetition_type === 'REQUIRED' ? depth : depth + 1
166
+ /** @type {Record<string, any>} */
167
+ const struct = {}
168
+ for (const child of schema.children) {
169
+ assembleNested(subcolumnData, child, parsers, invertDepth)
170
+ const childData = subcolumnData.get(child.path.join('.'))
171
+ if (!childData) throw new Error('parquet struct missing child data')
172
+ struct[child.element.name] = childData
173
+ }
174
+ // remove children
175
+ for (const child of schema.children) {
176
+ subcolumnData.delete(child.path.join('.'))
177
+ }
178
+
179
+ // invert struct by depth
180
+ let inverted = invertStruct(struct, invertDepth)
181
+ if (schema.element.logical_type?.type === 'VARIANT') {
182
+ inverted = decodeVariantColumn(inverted, parsers)
183
+ }
184
+ if (optional) flattenAtDepth(inverted, depth)
185
+ subcolumnData.set(path, inverted)
186
+ }
187
+ }
188
+
189
+ /**
190
+ * @import {DecodedArray, ParquetParsers, SchemaTree} from '../src/types.js'
191
+ * @param {DecodedArray} arr
192
+ * @param {number} depth
193
+ */
194
+ function flattenAtDepth(arr, depth) {
195
+ for (let i = 0; i < arr.length; i++) {
196
+ if (depth) {
197
+ flattenAtDepth(arr[i], depth - 1)
198
+ } else {
199
+ arr[i] = arr[i][0]
200
+ }
201
+ }
202
+ }
203
+
204
+ /**
205
+ * @param {DecodedArray} keys
206
+ * @param {DecodedArray} values
207
+ * @param {number} depth
208
+ * @returns {any[]}
209
+ */
210
+ function assembleMaps(keys, values, depth) {
211
+ const out = []
212
+ for (let i = 0; i < keys.length; i++) {
213
+ if (depth) {
214
+ out.push(assembleMaps(keys[i], values[i], depth - 1)) // go deeper
215
+ } else {
216
+ if (keys[i]) {
217
+ /** @type {Record<string, any>} */
218
+ const obj = {}
219
+ for (let j = 0; j < keys[i].length; j++) {
220
+ const value = values[i][j]
221
+ obj[keys[i][j]] = value === undefined ? null : value
222
+ }
223
+ out.push(obj)
224
+ } else {
225
+ out.push(undefined)
226
+ }
227
+ }
228
+ }
229
+ return out
230
+ }
231
+
232
+ /**
233
+ * Invert a struct-like object by depth.
234
+ *
235
+ * @param {Record<string, any[]>} struct
236
+ * @param {number} depth
237
+ * @returns {any[]}
238
+ */
239
+ function invertStruct(struct, depth) {
240
+ const keys = Object.keys(struct)
241
+ const length = struct[keys[0]]?.length
242
+ const out = []
243
+ for (let i = 0; i < length; i++) {
244
+ /** @type {Record<string, any>} */
245
+ const obj = {}
246
+ for (const key of keys) {
247
+ if (struct[key].length !== length) throw new Error('parquet struct parsing error')
248
+ obj[key] = struct[key][i]
249
+ }
250
+ if (depth) {
251
+ out.push(invertStruct(obj, depth - 1)) // deeper
252
+ } else {
253
+ out.push(obj)
254
+ }
255
+ }
256
+ return out
257
+ }
web/node_modules/hyparquet/src/bloom.js ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Split Block Bloom Filter (https://github.com/apache/parquet-format/blob/master/BloomFilter.md)
2
+ // A bloom filter is a sequence of 32-byte blocks. Each block holds 8 little-endian uint32 words.
3
+ // Insertion sets one bit per word, chosen by salting the low 32 bits of an xxhash64.
4
+ // Membership requires all 8 bits to be set; misses are exact, hits are probabilistic.
5
+
6
+ import { deserializeTCompactProtocol } from './thrift.js'
7
+ import { xxhash64 } from './xxhash.js'
8
+
9
+ /**
10
+ * @import {BloomFilter, DataReader, ParquetQueryFilter, SchemaElement} from '../src/types.js'
11
+ */
12
+
13
+ const textEncoder = new TextEncoder()
14
+
15
+ const SALT = new Uint32Array([
16
+ 0x47b6137b, 0x44974d91, 0x8824ad5b, 0xa2b7289d,
17
+ 0x705495c7, 0x2df1424b, 0x9efc4947, 0x5c6bfb31,
18
+ ])
19
+
20
+ /**
21
+ * Map the high 32 bits of a hash to a block index in [0, numBlocks).
22
+ *
23
+ * @param {bigint} hash
24
+ * @param {number} numBlocks
25
+ * @returns {number}
26
+ */
27
+ function blockIndex(hash, numBlocks) {
28
+ return Number((hash >> 32n) * BigInt(numBlocks) >> 32n)
29
+ }
30
+
31
+ /**
32
+ * Per-block mask: 8 uint32 words, each with a single bit set at position `(low32 * SALT[i]) >> 27`.
33
+ *
34
+ * @param {bigint} hash
35
+ * @returns {Uint32Array}
36
+ */
37
+ function blockMask(hash) {
38
+ const m = new Uint32Array(8)
39
+ const low = Number(hash & 0xffffffffn) | 0
40
+ for (let i = 0; i < 8; i++) {
41
+ m[i] = 1 << (Math.imul(low, SALT[i]) >>> 27)
42
+ }
43
+ return m
44
+ }
45
+
46
+ /**
47
+ * Insert a hash into a Split Block Bloom Filter.
48
+ *
49
+ * @param {Uint32Array} blocks bloom filter words (8 * numBlocks long)
50
+ * @param {bigint} hash 64-bit xxhash of the parquet-plain-encoded value
51
+ */
52
+ export function sbbfInsert(blocks, hash) {
53
+ const offset = blockIndex(hash, blocks.length >> 3) << 3
54
+ const m = blockMask(hash)
55
+ for (let i = 0; i < 8; i++) {
56
+ blocks[offset + i] |= m[i]
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Test whether a hash might be present in a Split Block Bloom Filter.
62
+ * False positives are possible; false negatives are not.
63
+ *
64
+ * @param {Uint32Array} blocks bloom filter words (8 * numBlocks long)
65
+ * @param {bigint} hash 64-bit xxhash of the parquet-plain-encoded value
66
+ * @returns {boolean}
67
+ */
68
+ export function sbbfContains(blocks, hash) {
69
+ const offset = blockIndex(hash, blocks.length >> 3) << 3
70
+ const m = blockMask(hash)
71
+ for (let i = 0; i < 8; i++) {
72
+ if ((blocks[offset + i] & m[i]) === 0) return false
73
+ }
74
+ return true
75
+ }
76
+
77
+ /**
78
+ * Parse a Split Block Bloom Filter from a reader positioned at the BloomFilterHeader.
79
+ * Returns undefined when the header advertises an unsupported algorithm, hash, or
80
+ * compression — callers should treat that as "cannot use this bloom filter."
81
+ *
82
+ * @param {DataReader} reader
83
+ * @returns {BloomFilter | undefined}
84
+ */
85
+ export function readBloomFilter(reader) {
86
+ const header = deserializeTCompactProtocol(reader)
87
+ const numBytes = header.field_1
88
+ if (typeof numBytes !== 'number' || numBytes <= 0 || numBytes % 32 !== 0) return undefined
89
+ // BloomFilterAlgorithm / Hash / Compression are unions with a single supported variant each.
90
+ if (!header.field_2?.field_1) return undefined // algorithm must be BLOCK
91
+ if (!header.field_3?.field_1) return undefined // hash must be XXHASH
92
+ if (!header.field_4?.field_1) return undefined // compression must be UNCOMPRESSED
93
+
94
+ const { view, offset } = reader
95
+ if (offset + numBytes > view.byteLength) {
96
+ throw new Error(`parquet bloom filter truncated: need ${numBytes} bytes, have ${view.byteLength - offset}`)
97
+ }
98
+ // Reader offset is not 4-aligned in general, and we want endian-portable reads.
99
+ const blocks = new Uint32Array(numBytes >> 2)
100
+ for (let i = 0; i < blocks.length; i++) {
101
+ blocks[i] = view.getUint32(offset + i * 4, true)
102
+ }
103
+ reader.offset = offset + numBytes
104
+ return { numBytes, blocks }
105
+ }
106
+
107
+ /**
108
+ * Hash a JS filter value as its parquet PLAIN-encoded bytes, suitable for a
109
+ * bloom filter lookup. Returns undefined when the column's parser is lossy or
110
+ * ambiguous (DATE, TIMESTAMP_*, DECIMAL, JSON, BSON, INT96, FLOAT16, UUID,
111
+ * GEOMETRY, GEOGRAPHY, INTERVAL) or when the JS value type doesn't match the
112
+ * column. Callers must treat undefined as "bloom filter cannot help."
113
+ *
114
+ * @param {any} value
115
+ * @param {SchemaElement} element
116
+ * @returns {bigint | undefined}
117
+ */
118
+ export function hashParquetValue(value, element) {
119
+ if (value === null || value === undefined) return undefined
120
+ const { type, converted_type, logical_type } = element
121
+
122
+ if (type === 'BOOLEAN') {
123
+ if (typeof value !== 'boolean') return undefined
124
+ return xxhash64(new Uint8Array([value ? 1 : 0]))
125
+ }
126
+
127
+ if (type === 'FLOAT') {
128
+ if (typeof value !== 'number') return undefined
129
+ const buf = new ArrayBuffer(4)
130
+ new DataView(buf).setFloat32(0, value, true)
131
+ return xxhash64(new Uint8Array(buf))
132
+ }
133
+
134
+ if (type === 'DOUBLE') {
135
+ if (typeof value !== 'number') return undefined
136
+ const buf = new ArrayBuffer(8)
137
+ new DataView(buf).setFloat64(0, value, true)
138
+ return xxhash64(new Uint8Array(buf))
139
+ }
140
+
141
+ if (type === 'INT32') {
142
+ if (converted_type === 'DATE' || converted_type === 'DECIMAL' || converted_type === 'TIME_MILLIS') return undefined
143
+ if (logical_type?.type === 'DATE' || logical_type?.type === 'TIME' || logical_type?.type === 'DECIMAL') return undefined
144
+ if (typeof value !== 'number' || !Number.isInteger(value)) return undefined
145
+ const buf = new ArrayBuffer(4)
146
+ new DataView(buf).setInt32(0, value | 0, true)
147
+ return xxhash64(new Uint8Array(buf))
148
+ }
149
+
150
+ if (type === 'INT64') {
151
+ if (converted_type === 'TIMESTAMP_MILLIS' || converted_type === 'TIMESTAMP_MICROS') return undefined
152
+ if (converted_type === 'TIME_MICROS' || converted_type === 'DECIMAL') return undefined
153
+ if (logical_type?.type === 'TIMESTAMP' || logical_type?.type === 'TIME' || logical_type?.type === 'DECIMAL') return undefined
154
+ let bigValue
155
+ if (typeof value === 'bigint') bigValue = value
156
+ else if (typeof value === 'number' && Number.isSafeInteger(value)) bigValue = BigInt(value)
157
+ else return undefined
158
+ const buf = new ArrayBuffer(8)
159
+ new DataView(buf).setBigUint64(0, BigInt.asUintN(64, bigValue), true)
160
+ return xxhash64(new Uint8Array(buf))
161
+ }
162
+
163
+ if (type === 'BYTE_ARRAY') {
164
+ if (converted_type === 'JSON' || converted_type === 'BSON' || converted_type === 'DECIMAL') return undefined
165
+ if (logical_type?.type === 'JSON' || logical_type?.type === 'BSON' || logical_type?.type === 'VARIANT') return undefined
166
+ if (logical_type?.type === 'GEOMETRY' || logical_type?.type === 'GEOGRAPHY') return undefined
167
+ if (typeof value === 'string') return xxhash64(textEncoder.encode(value))
168
+ if (value instanceof Uint8Array) return xxhash64(value)
169
+ return undefined
170
+ }
171
+
172
+ if (type === 'FIXED_LEN_BYTE_ARRAY') {
173
+ if (converted_type === 'DECIMAL' || converted_type === 'INTERVAL') return undefined
174
+ if (logical_type?.type === 'DECIMAL' || logical_type?.type === 'UUID' || logical_type?.type === 'FLOAT16') return undefined
175
+ if (logical_type?.type === 'GEOMETRY' || logical_type?.type === 'GEOGRAPHY') return undefined
176
+ if (value instanceof Uint8Array) return xxhash64(value)
177
+ return undefined
178
+ }
179
+
180
+ // INT96 deprecated, or type missing on group columns
181
+ return undefined
182
+ }
183
+
184
+ /**
185
+ * Top-level column names that appear in $eq or $in predicates within a filter.
186
+ * These are the only columns where a bloom filter can prove a value's absence
187
+ * and let us skip a row group; any other operator can't be helped by a bloom.
188
+ *
189
+ * @param {ParquetQueryFilter | undefined} filter
190
+ * @returns {Set<string>}
191
+ */
192
+ export function bloomEligibleColumns(filter) {
193
+ /** @type {Set<string>} */
194
+ const out = new Set()
195
+ walkBloomEligible(filter, out)
196
+ return out
197
+ }
198
+
199
+ /**
200
+ * @param {ParquetQueryFilter | undefined} filter
201
+ * @param {Set<string>} out
202
+ */
203
+ function walkBloomEligible(filter, out) {
204
+ if (!filter) return
205
+ if ('$and' in filter && Array.isArray(filter.$and)) {
206
+ for (const sub of filter.$and) walkBloomEligible(sub, out)
207
+ return
208
+ }
209
+ if ('$or' in filter && Array.isArray(filter.$or)) {
210
+ for (const sub of filter.$or) walkBloomEligible(sub, out)
211
+ return
212
+ }
213
+ // $nor would need to prove presence, not absence — bloom can't help.
214
+ if ('$nor' in filter) return
215
+ for (const [field, condition] of Object.entries(filter)) {
216
+ if (field.startsWith('$')) continue
217
+ if (typeof condition === 'object' && condition !== null && !Array.isArray(condition)) {
218
+ if ('$eq' in condition || '$in' in condition) out.add(field)
219
+ } else {
220
+ // primitive / null / array condition is an implicit $eq
221
+ out.add(field)
222
+ }
223
+ }
224
+ }
web/node_modules/hyparquet/src/column.js ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {ColumnDecoder, DataReader, DecodedArray, PageHeader, PageResult, RowGroupSelect, SubColumnData} from '../src/types.js'
3
+ */
4
+
5
+ import { assembleLists } from './assemble.js'
6
+ import { Encodings, PageTypes } from './constants.js'
7
+ import { convert, convertWithDictionary } from './convert.js'
8
+ import { decompressPage, readDataPage, readDataPageV2 } from './datapage.js'
9
+ import { readPlain } from './plain.js'
10
+ import { isFlatColumn } from './schema.js'
11
+ import { deserializeTCompactProtocol } from './thrift.js'
12
+
13
+ /**
14
+ * Parse column data from a buffer.
15
+ *
16
+ * @param {DataReader} reader
17
+ * @param {RowGroupSelect} rowGroupSelect row group selection
18
+ * @param {ColumnDecoder} columnDecoder column decoder params
19
+ * @param {(chunk: SubColumnData) => void} [onPage] callback for each page
20
+ * @returns {{ data: DecodedArray[], skipped: number }}
21
+ */
22
+ export function readColumn(reader, { groupStart, selectStart, selectEnd }, columnDecoder, onPage) {
23
+ const { pathInSchema, schemaPath } = columnDecoder
24
+ const isFlat = isFlatColumn(schemaPath)
25
+ /** @type {DecodedArray[]} */
26
+ const chunks = []
27
+ /** @type {DecodedArray | undefined} */
28
+ let dictionary = undefined
29
+ /** @type {DecodedArray | undefined} */
30
+ let lastChunk = undefined
31
+ let rowCount = 0
32
+ let skipped = 0
33
+
34
+ const emitLastChunk = onPage && (() => {
35
+ lastChunk && onPage({
36
+ pathInSchema,
37
+ columnData: lastChunk,
38
+ rowStart: groupStart + rowCount - lastChunk.length,
39
+ rowEnd: groupStart + rowCount,
40
+ })
41
+ })
42
+
43
+ while (isFlat ? rowCount < selectEnd : reader.offset < reader.view.byteLength - 1) {
44
+ if (reader.offset >= reader.view.byteLength - 1) break // end of reader
45
+
46
+ // read page header
47
+ const header = parquetHeader(reader)
48
+ if (header.type === 'DICTIONARY_PAGE') {
49
+ const { data } = readPage(reader, header, columnDecoder, dictionary, undefined, 0)
50
+ if (data) dictionary = convert(data, columnDecoder)
51
+ } else {
52
+ const lastChunkLength = lastChunk?.length || 0
53
+ const result = readPage(reader, header, columnDecoder, dictionary, lastChunk, selectStart - rowCount)
54
+ if (result.skipped) {
55
+ // skipped page - just advance row count, don't add to chunks
56
+ if (!chunks.length) {
57
+ skipped += result.skipped
58
+ }
59
+ rowCount += result.skipped
60
+ } else if (result.data && lastChunk === result.data) {
61
+ // continued from previous page
62
+ rowCount += result.data.length - lastChunkLength
63
+ } else if (result.data && result.data.length) {
64
+ emitLastChunk?.()
65
+ chunks.push(result.data)
66
+ rowCount += result.data.length
67
+ lastChunk = result.data
68
+ }
69
+ }
70
+ }
71
+ emitLastChunk?.()
72
+
73
+ return { data: chunks, skipped }
74
+ }
75
+
76
+ /**
77
+ * Read a page (data or dictionary) from a buffer.
78
+ *
79
+ * @param {DataReader} reader
80
+ * @param {PageHeader} header
81
+ * @param {ColumnDecoder} columnDecoder
82
+ * @param {DecodedArray | undefined} dictionary
83
+ * @param {DecodedArray | undefined} previousChunk
84
+ * @param {number} pageStart skip this many rows in the page
85
+ * @returns {PageResult}
86
+ */
87
+ export function readPage(reader, header, columnDecoder, dictionary, previousChunk, pageStart) {
88
+ const { type, element, schemaPath, codec, compressors } = columnDecoder
89
+ // read compressed_page_size bytes
90
+ const compressedBytes = new Uint8Array(
91
+ reader.view.buffer, reader.view.byteOffset + reader.offset, header.compressed_page_size
92
+ )
93
+ reader.offset += header.compressed_page_size
94
+
95
+ // parse page data by type
96
+ if (header.type === 'DATA_PAGE') {
97
+ const daph = header.data_page_header
98
+ if (!daph) throw new Error('parquet data page header is undefined')
99
+
100
+ // skip unnecessary non-nested pages
101
+ if (pageStart > daph.num_values && isFlatColumn(schemaPath)) {
102
+ return { skipped: daph.num_values }
103
+ }
104
+
105
+ const page = decompressPage(compressedBytes, Number(header.uncompressed_page_size), codec, compressors)
106
+ const { definitionLevels, repetitionLevels, dataPage } = readDataPage(page, daph, columnDecoder)
107
+ // assert(!daph.statistics?.null_count || daph.statistics.null_count === BigInt(daph.num_values - dataPage.length))
108
+
109
+ // convert types, dereference dictionary, and assemble lists
110
+ const values = convertWithDictionary(dataPage, dictionary, daph.encoding, columnDecoder)
111
+ const output = Array.isArray(previousChunk) ? previousChunk : []
112
+ const assembled = assembleLists(output, definitionLevels, repetitionLevels, values, schemaPath)
113
+ return { skipped: 0, data: assembled }
114
+ } else if (header.type === 'DATA_PAGE_V2') {
115
+ const daph2 = header.data_page_header_v2
116
+ if (!daph2) throw new Error('parquet data page header v2 is undefined')
117
+
118
+ // skip unnecessary pages
119
+ if (pageStart > daph2.num_rows) {
120
+ return { skipped: daph2.num_values }
121
+ }
122
+
123
+ const { definitionLevels, repetitionLevels, dataPage } =
124
+ readDataPageV2(compressedBytes, header, columnDecoder)
125
+
126
+ // convert types, dereference dictionary, and assemble lists
127
+ const values = convertWithDictionary(dataPage, dictionary, daph2.encoding, columnDecoder)
128
+ const output = Array.isArray(previousChunk) ? previousChunk : []
129
+ const assembled = assembleLists(output, definitionLevels, repetitionLevels, values, schemaPath)
130
+ return { skipped: 0, data: assembled }
131
+ } else if (header.type === 'DICTIONARY_PAGE') {
132
+ const diph = header.dictionary_page_header
133
+ if (!diph) throw new Error('parquet dictionary page header is undefined')
134
+
135
+ const page = decompressPage(
136
+ compressedBytes, Number(header.uncompressed_page_size), codec, compressors
137
+ )
138
+
139
+ const reader = { view: new DataView(page.buffer, page.byteOffset, page.byteLength), offset: 0 }
140
+ const dictArray = readPlain(reader, type, diph.num_values, element.type_length)
141
+ return { skipped: 0, data: dictArray }
142
+ } else {
143
+ throw new Error(`parquet unsupported page type: ${header.type}`)
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Read parquet header from a buffer.
149
+ *
150
+ * @param {DataReader} reader
151
+ * @returns {PageHeader}
152
+ */
153
+ function parquetHeader(reader) {
154
+ const header = deserializeTCompactProtocol(reader)
155
+
156
+ // Parse parquet header from thrift data
157
+ const type = PageTypes[header.field_1]
158
+ const uncompressed_page_size = header.field_2
159
+ const compressed_page_size = header.field_3
160
+ const crc = header.field_4
161
+ const data_page_header = header.field_5 && {
162
+ num_values: header.field_5.field_1,
163
+ encoding: Encodings[header.field_5.field_2],
164
+ definition_level_encoding: Encodings[header.field_5.field_3],
165
+ repetition_level_encoding: Encodings[header.field_5.field_4],
166
+ statistics: header.field_5.field_5 && {
167
+ max: header.field_5.field_5.field_1,
168
+ min: header.field_5.field_5.field_2,
169
+ null_count: header.field_5.field_5.field_3,
170
+ distinct_count: header.field_5.field_5.field_4,
171
+ max_value: header.field_5.field_5.field_5,
172
+ min_value: header.field_5.field_5.field_6,
173
+ },
174
+ }
175
+ const index_page_header = header.field_6
176
+ const dictionary_page_header = header.field_7 && {
177
+ num_values: header.field_7.field_1,
178
+ encoding: Encodings[header.field_7.field_2],
179
+ is_sorted: header.field_7.field_3,
180
+ }
181
+ const data_page_header_v2 = header.field_8 && {
182
+ num_values: header.field_8.field_1,
183
+ num_nulls: header.field_8.field_2,
184
+ num_rows: header.field_8.field_3,
185
+ encoding: Encodings[header.field_8.field_4],
186
+ definition_levels_byte_length: header.field_8.field_5,
187
+ repetition_levels_byte_length: header.field_8.field_6,
188
+ is_compressed: header.field_8.field_7 === undefined ? true : header.field_8.field_7, // default true
189
+ statistics: header.field_8.field_8,
190
+ }
191
+
192
+ return {
193
+ type,
194
+ uncompressed_page_size,
195
+ compressed_page_size,
196
+ crc,
197
+ data_page_header,
198
+ index_page_header,
199
+ dictionary_page_header,
200
+ data_page_header_v2,
201
+ }
202
+ }
web/node_modules/hyparquet/src/constants.js ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ /** @type {import('../src/types.d.ts').ParquetType[]} */
3
+ export const ParquetTypes = [
4
+ 'BOOLEAN',
5
+ 'INT32',
6
+ 'INT64',
7
+ 'INT96', // deprecated
8
+ 'FLOAT',
9
+ 'DOUBLE',
10
+ 'BYTE_ARRAY',
11
+ 'FIXED_LEN_BYTE_ARRAY',
12
+ ]
13
+
14
+ /** @type {import('../src/types.d.ts').Encoding[]} */
15
+ export const Encodings = [
16
+ 'PLAIN',
17
+ 'GROUP_VAR_INT', // deprecated
18
+ 'PLAIN_DICTIONARY',
19
+ 'RLE',
20
+ 'BIT_PACKED', // deprecated
21
+ 'DELTA_BINARY_PACKED',
22
+ 'DELTA_LENGTH_BYTE_ARRAY',
23
+ 'DELTA_BYTE_ARRAY',
24
+ 'RLE_DICTIONARY',
25
+ 'BYTE_STREAM_SPLIT',
26
+ ]
27
+
28
+ /** @type {import('../src/types.d.ts').FieldRepetitionType[]} */
29
+ export const FieldRepetitionTypes = [
30
+ 'REQUIRED',
31
+ 'OPTIONAL',
32
+ 'REPEATED',
33
+ ]
34
+
35
+ /** @type {import('../src/types.d.ts').ConvertedType[]} */
36
+ export const ConvertedTypes = [
37
+ 'UTF8',
38
+ 'MAP',
39
+ 'MAP_KEY_VALUE',
40
+ 'LIST',
41
+ 'ENUM',
42
+ 'DECIMAL',
43
+ 'DATE',
44
+ 'TIME_MILLIS',
45
+ 'TIME_MICROS',
46
+ 'TIMESTAMP_MILLIS',
47
+ 'TIMESTAMP_MICROS',
48
+ 'UINT_8',
49
+ 'UINT_16',
50
+ 'UINT_32',
51
+ 'UINT_64',
52
+ 'INT_8',
53
+ 'INT_16',
54
+ 'INT_32',
55
+ 'INT_64',
56
+ 'JSON',
57
+ 'BSON',
58
+ 'INTERVAL',
59
+ ]
60
+
61
+ /** @type {import('../src/types.d.ts').CompressionCodec[]} */
62
+ export const CompressionCodecs = [
63
+ 'UNCOMPRESSED',
64
+ 'SNAPPY',
65
+ 'GZIP',
66
+ 'LZO',
67
+ 'BROTLI',
68
+ 'LZ4',
69
+ 'ZSTD',
70
+ 'LZ4_RAW',
71
+ ]
72
+
73
+ /** @type {import('../src/types.d.ts').PageType[]} */
74
+ export const PageTypes = [
75
+ 'DATA_PAGE',
76
+ 'INDEX_PAGE',
77
+ 'DICTIONARY_PAGE',
78
+ 'DATA_PAGE_V2',
79
+ ]
80
+
81
+ /** @type {import('../src/types.d.ts').BoundaryOrder[]} */
82
+ export const BoundaryOrders = [
83
+ 'UNORDERED',
84
+ 'ASCENDING',
85
+ 'DESCENDING',
86
+ ]
87
+
88
+ /** @type {import('../src/types.d.ts').EdgeInterpolationAlgorithm[]} */
89
+ export const EdgeInterpolationAlgorithms = [
90
+ 'SPHERICAL',
91
+ 'VINCENTY',
92
+ 'THOMAS',
93
+ 'ANDOYER',
94
+ 'KARNEY',
95
+ ]
web/node_modules/hyparquet/src/convert.js ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { wkbToGeojson } from './wkb.js'
2
+
3
+ /**
4
+ * @import {ColumnDecoder, DecodedArray, Encoding, ParquetParsers} from '../src/types.js'
5
+ */
6
+
7
+ const decoder = new TextDecoder()
8
+
9
+ /**
10
+ * Default type parsers when no custom ones are given
11
+ * @type ParquetParsers
12
+ */
13
+ export const DEFAULT_PARSERS = {
14
+ timestampFromMilliseconds(millis) {
15
+ return new Date(Number(millis))
16
+ },
17
+ timestampFromMicroseconds(micros) {
18
+ return new Date(Number(micros / 1000n))
19
+ },
20
+ timestampFromNanoseconds(nanos) {
21
+ return new Date(Number(nanos / 1000000n))
22
+ },
23
+ dateFromDays(days) {
24
+ return new Date(days * 86400000)
25
+ },
26
+ stringFromBytes(bytes) {
27
+ return bytes && decoder.decode(bytes)
28
+ },
29
+ jsonFromBytes(bytes) {
30
+ return bytes && JSON.parse(decoder.decode(bytes))
31
+ },
32
+ geometryFromBytes(bytes) {
33
+ return bytes && wkbToGeojson({ view: new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), offset: 0 })
34
+ },
35
+ geographyFromBytes(bytes) {
36
+ return bytes && wkbToGeojson({ view: new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), offset: 0 })
37
+ },
38
+ uuidFromBytes(bytes) {
39
+ if (!bytes) return undefined
40
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('')
41
+ return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20, 32)
42
+ },
43
+ }
44
+
45
+ /**
46
+ * Convert known types from primitive to rich, and dereference dictionary.
47
+ *
48
+ * @param {DecodedArray} data series of primitive types
49
+ * @param {DecodedArray | undefined} dictionary
50
+ * @param {Encoding} encoding
51
+ * @param {ColumnDecoder} columnDecoder
52
+ * @returns {DecodedArray} series of rich types
53
+ */
54
+ export function convertWithDictionary(data, dictionary, encoding, columnDecoder) {
55
+ if (dictionary && encoding.endsWith('_DICTIONARY')) {
56
+ let output = data
57
+ if (data instanceof Uint8Array && !(dictionary instanceof Uint8Array)) {
58
+ // @ts-expect-error upgrade data to match dictionary type with fancy constructor
59
+ output = new dictionary.constructor(data.length)
60
+ }
61
+ for (let i = 0; i < data.length; i++) {
62
+ output[i] = dictionary[data[i]]
63
+ }
64
+ return output
65
+ } else {
66
+ return convert(data, columnDecoder)
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Convert known types from primitive to rich.
72
+ *
73
+ * @param {DecodedArray} data series of primitive types
74
+ * @param {ColumnDecoder} columnDecoder
75
+ * @returns {DecodedArray} series of rich types
76
+ */
77
+ export function convert(data, columnDecoder) {
78
+ const { element, parsers, utf8 = true, schemaPath } = columnDecoder
79
+ const { type, converted_type: ctype, logical_type: ltype } = element
80
+ const nullable = element.repetition_type !== 'REQUIRED'
81
+
82
+ // Skip utf8 conversion for plain BYTE_ARRAY inside VARIANT
83
+ const isVariant = schemaPath?.some(s => s.element.logical_type?.type === 'VARIANT')
84
+ if (isVariant && type === 'BYTE_ARRAY' && ctype !== 'UTF8' && ltype?.type !== 'STRING') {
85
+ return data
86
+ }
87
+ if (ctype === 'DECIMAL') {
88
+ const scale = element.scale || 0
89
+ const factor = 10 ** -scale
90
+ const arr = new Array(data.length)
91
+ for (let i = 0; i < arr.length; i++) {
92
+ if (data[i] instanceof Uint8Array) {
93
+ arr[i] = parseDecimal(data[i]) * factor
94
+ } else {
95
+ arr[i] = Number(data[i]) * factor
96
+ }
97
+ }
98
+ return arr
99
+ }
100
+ if (!ctype && type === 'INT96') {
101
+ return Array.from(data).map(v => parsers.timestampFromNanoseconds(parseInt96Nanos(v)))
102
+ }
103
+ if (ctype === 'DATE') {
104
+ return Array.from(data).map(v => parsers.dateFromDays(v))
105
+ }
106
+ if (ctype === 'TIMESTAMP_MILLIS') {
107
+ return Array.from(data).map(v => parsers.timestampFromMilliseconds(v))
108
+ }
109
+ if (ctype === 'TIMESTAMP_MICROS') {
110
+ return Array.from(data).map(v => parsers.timestampFromMicroseconds(v))
111
+ }
112
+ if (ctype === 'JSON') {
113
+ return data.map(v => parsers.jsonFromBytes(v))
114
+ }
115
+ if (ctype === 'BSON') {
116
+ throw new Error('parquet bson not supported')
117
+ }
118
+ if (ctype === 'INTERVAL') {
119
+ throw new Error('parquet interval not supported')
120
+ }
121
+ if (ltype?.type === 'GEOMETRY') {
122
+ return data.map(v => parsers.geometryFromBytes(v))
123
+ }
124
+ if (ltype?.type === 'GEOGRAPHY') {
125
+ return data.map(v => parsers.geographyFromBytes(v))
126
+ }
127
+ if (ltype?.type === 'UUID') {
128
+ return data.map(v => parsers.uuidFromBytes(v))
129
+ }
130
+ if (ctype === 'UTF8' || ltype?.type === 'STRING' || utf8 && type === 'BYTE_ARRAY') {
131
+ return data.map(v => parsers.stringFromBytes(v))
132
+ }
133
+ if (ctype === 'UINT_64' || ltype?.type === 'INTEGER' && ltype.bitWidth === 64 && !ltype.isSigned) {
134
+ if (data instanceof BigInt64Array) return new BigUint64Array(data.buffer, data.byteOffset, data.length)
135
+ const arr = nullable ? new Array(data.length) : new BigUint64Array(data.length)
136
+ for (let i = 0; i < arr.length; i++) arr[i] = data[i]
137
+ return arr
138
+ }
139
+ if (ctype === 'UINT_32' || ltype?.type === 'INTEGER' && ltype.bitWidth === 32 && !ltype.isSigned) {
140
+ if (data instanceof Int32Array) return new Uint32Array(data.buffer, data.byteOffset, data.length)
141
+ const arr = nullable ? new Array(data.length) : new Uint32Array(data.length)
142
+ for (let i = 0; i < arr.length; i++) {
143
+ arr[i] = data[i] < 0 ? 4294967296 + data[i] : data[i]
144
+ }
145
+ return arr
146
+ }
147
+ if (ltype?.type === 'FLOAT16') {
148
+ return Array.from(data).map(parseFloat16)
149
+ }
150
+ if (ltype?.type === 'TIMESTAMP') {
151
+ const { unit } = ltype
152
+ /** @type {ParquetParsers[keyof ParquetParsers]} */
153
+ let parser = parsers.timestampFromMilliseconds
154
+ if (unit === 'MICROS') parser = parsers.timestampFromMicroseconds
155
+ if (unit === 'NANOS') parser = parsers.timestampFromNanoseconds
156
+ const arr = new Array(data.length)
157
+ for (let i = 0; i < arr.length; i++) {
158
+ arr[i] = parser(data[i])
159
+ }
160
+ return arr
161
+ }
162
+ return data
163
+ }
164
+
165
+ /**
166
+ * @param {Uint8Array} bytes
167
+ * @returns {number}
168
+ */
169
+ export function parseDecimal(bytes) {
170
+ if (!bytes.length) return 0
171
+
172
+ let value = 0n
173
+ for (const byte of bytes) {
174
+ value = value * 256n + BigInt(byte)
175
+ }
176
+
177
+ // handle signed
178
+ const bits = bytes.length * 8
179
+ if (value >= 2n ** BigInt(bits - 1)) {
180
+ value -= 2n ** BigInt(bits)
181
+ }
182
+
183
+ return Number(value)
184
+ }
185
+
186
+ /**
187
+ * Converts INT96 date format (hi 32bit days, lo 64bit nanos) to nanos since epoch
188
+ * @param {bigint} value
189
+ * @returns {bigint}
190
+ */
191
+ function parseInt96Nanos(value) {
192
+ const days = (value >> 64n) - 2440588n
193
+ const nano = value & 0xffffffffffffffffn
194
+ return days * 86400000000000n + nano
195
+ }
196
+
197
+ /**
198
+ * @param {Uint8Array | undefined} bytes
199
+ * @returns {number | undefined}
200
+ */
201
+ export function parseFloat16(bytes) {
202
+ if (!bytes) return undefined
203
+ const int16 = bytes[1] << 8 | bytes[0]
204
+ const sign = int16 >> 15 ? -1 : 1
205
+ const exp = int16 >> 10 & 0x1f
206
+ const frac = int16 & 0x3ff
207
+ if (exp === 0) return sign * 2 ** -14 * (frac / 1024) // subnormals
208
+ if (exp === 0x1f) return frac ? NaN : sign * Infinity
209
+ return sign * 2 ** (exp - 15) * (1 + frac / 1024)
210
+ }
web/node_modules/hyparquet/src/datapage.js ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {ColumnDecoder, CompressionCodec, Compressors, DataPage, DataPageHeader, DataPageHeaderV2, DataReader, DecodedArray, PageHeader, SchemaTree} from '../src/types.js'
3
+ */
4
+
5
+ import { deltaBinaryUnpack, deltaByteArray, deltaLengthByteArray } from './delta.js'
6
+ import { byteStreamSplit, readRleBitPackedHybrid } from './encoding.js'
7
+ import { readPlain } from './plain.js'
8
+ import { getMaxDefinitionLevel, getMaxRepetitionLevel } from './schema.js'
9
+ import { snappyUncompress } from './snappy.js'
10
+
11
+ /**
12
+ * Read a data page from uncompressed reader.
13
+ *
14
+ * @param {Uint8Array} bytes raw page data (should already be decompressed)
15
+ * @param {DataPageHeader} daph data page header
16
+ * @param {ColumnDecoder} columnDecoder
17
+ * @returns {DataPage} definition levels, repetition levels, and array of values
18
+ */
19
+ export function readDataPage(bytes, daph, { type, element, schemaPath }) {
20
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
21
+ const reader = { view, offset: 0 }
22
+ /** @type {DecodedArray} */
23
+ let dataPage
24
+
25
+ // repetition and definition levels
26
+ const repetitionLevels = readRepetitionLevels(reader, daph, schemaPath)
27
+ // assert(!repetitionLevels.length || repetitionLevels.length === daph.num_values)
28
+ const { definitionLevels, numNulls } = readDefinitionLevels(reader, daph, schemaPath)
29
+ // assert(!definitionLevels.length || definitionLevels.length === daph.num_values)
30
+
31
+ // read values based on encoding
32
+ const nValues = daph.num_values - numNulls
33
+ if (daph.encoding === 'PLAIN') {
34
+ dataPage = readPlain(reader, type, nValues, element.type_length)
35
+ } else if (
36
+ daph.encoding === 'PLAIN_DICTIONARY' ||
37
+ daph.encoding === 'RLE_DICTIONARY' ||
38
+ daph.encoding === 'RLE'
39
+ ) {
40
+ const bitWidth = type === 'BOOLEAN' ? 1 : view.getUint8(reader.offset++)
41
+ if (bitWidth) {
42
+ dataPage = new Array(nValues)
43
+ if (type === 'BOOLEAN') {
44
+ readRleBitPackedHybrid(reader, bitWidth, dataPage)
45
+ dataPage = dataPage.map(x => !!x) // convert to boolean
46
+ } else {
47
+ // assert(daph.encoding.endsWith('_DICTIONARY'))
48
+ readRleBitPackedHybrid(reader, bitWidth, dataPage, view.byteLength - reader.offset)
49
+ }
50
+ } else {
51
+ dataPage = new Uint8Array(nValues) // nValue zeroes
52
+ }
53
+ } else if (daph.encoding === 'BYTE_STREAM_SPLIT') {
54
+ dataPage = byteStreamSplit(reader, nValues, type, element.type_length)
55
+ } else if (daph.encoding === 'DELTA_BINARY_PACKED') {
56
+ const int32 = type === 'INT32'
57
+ dataPage = int32 ? new Int32Array(nValues) : new BigInt64Array(nValues)
58
+ deltaBinaryUnpack(reader, nValues, dataPage)
59
+ } else if (daph.encoding === 'DELTA_LENGTH_BYTE_ARRAY') {
60
+ dataPage = new Array(nValues)
61
+ deltaLengthByteArray(reader, nValues, dataPage)
62
+ } else {
63
+ throw new Error(`parquet unsupported encoding: ${daph.encoding}`)
64
+ }
65
+
66
+ return { definitionLevels, repetitionLevels, dataPage }
67
+ }
68
+
69
+ /**
70
+ * @param {DataReader} reader data view for the page
71
+ * @param {DataPageHeader} daph data page header
72
+ * @param {SchemaTree[]} schemaPath
73
+ * @returns {any[]} repetition levels and number of bytes read
74
+ */
75
+ function readRepetitionLevels(reader, daph, schemaPath) {
76
+ if (schemaPath.length > 1) {
77
+ const maxRepetitionLevel = getMaxRepetitionLevel(schemaPath)
78
+ if (maxRepetitionLevel) {
79
+ const values = new Array(daph.num_values)
80
+ readRleBitPackedHybrid(reader, bitWidth(maxRepetitionLevel), values)
81
+ return values
82
+ }
83
+ }
84
+ return []
85
+ }
86
+
87
+ /**
88
+ * @param {DataReader} reader data view for the page
89
+ * @param {DataPageHeader} daph data page header
90
+ * @param {SchemaTree[]} schemaPath
91
+ * @returns {{ definitionLevels: number[], numNulls: number }} definition levels
92
+ */
93
+ function readDefinitionLevels(reader, daph, schemaPath) {
94
+ const maxDefinitionLevel = getMaxDefinitionLevel(schemaPath)
95
+ if (!maxDefinitionLevel) return { definitionLevels: [], numNulls: 0 }
96
+
97
+ const definitionLevels = new Array(daph.num_values)
98
+ readRleBitPackedHybrid(reader, bitWidth(maxDefinitionLevel), definitionLevels)
99
+
100
+ // count nulls
101
+ let numNulls = daph.num_values
102
+ for (const def of definitionLevels) {
103
+ if (def === maxDefinitionLevel) numNulls--
104
+ }
105
+ if (numNulls === 0) definitionLevels.length = 0
106
+
107
+ return { definitionLevels, numNulls }
108
+ }
109
+
110
+ /**
111
+ * @param {Uint8Array} compressedBytes
112
+ * @param {number} uncompressed_page_size
113
+ * @param {CompressionCodec} codec
114
+ * @param {Compressors | undefined} compressors
115
+ * @returns {Uint8Array}
116
+ */
117
+ export function decompressPage(compressedBytes, uncompressed_page_size, codec, compressors) {
118
+ /** @type {Uint8Array} */
119
+ let page
120
+ const customDecompressor = compressors?.[codec]
121
+ if (codec === 'UNCOMPRESSED') {
122
+ page = compressedBytes
123
+ } else if (customDecompressor) {
124
+ page = customDecompressor(compressedBytes, uncompressed_page_size)
125
+ } else if (codec === 'SNAPPY') {
126
+ page = new Uint8Array(uncompressed_page_size)
127
+ snappyUncompress(compressedBytes, page)
128
+ } else {
129
+ throw new Error(`parquet unsupported compression codec: ${codec}`)
130
+ }
131
+ if (page?.length !== uncompressed_page_size) {
132
+ throw new Error(`parquet decompressed page length ${page?.length} does not match header ${uncompressed_page_size}`)
133
+ }
134
+ return page
135
+ }
136
+
137
+
138
+ /**
139
+ * Read a data page from the given Uint8Array.
140
+ *
141
+ * @param {Uint8Array} compressedBytes raw page data
142
+ * @param {PageHeader} ph page header
143
+ * @param {ColumnDecoder} columnDecoder
144
+ * @returns {DataPage} definition levels, repetition levels, and array of values
145
+ */
146
+ export function readDataPageV2(compressedBytes, ph, columnDecoder) {
147
+ const view = new DataView(compressedBytes.buffer, compressedBytes.byteOffset, compressedBytes.byteLength)
148
+ const reader = { view, offset: 0 }
149
+ const { type, element, schemaPath, codec, compressors } = columnDecoder
150
+ const daph2 = ph.data_page_header_v2
151
+ if (!daph2) throw new Error('parquet data page header v2 is undefined')
152
+
153
+ // repetition levels
154
+ const repetitionLevels = readRepetitionLevelsV2(reader, daph2, schemaPath)
155
+ reader.offset = daph2.repetition_levels_byte_length // readVarInt() => len for boolean v2?
156
+
157
+ // definition levels
158
+ const definitionLevels = readDefinitionLevelsV2(reader, daph2, schemaPath)
159
+ // assert(reader.offset === daph2.repetition_levels_byte_length + daph2.definition_levels_byte_length)
160
+
161
+ const uncompressedPageSize = ph.uncompressed_page_size - daph2.definition_levels_byte_length - daph2.repetition_levels_byte_length
162
+
163
+ let page = compressedBytes.subarray(reader.offset)
164
+ if (daph2.is_compressed !== false) {
165
+ page = decompressPage(page, uncompressedPageSize, codec, compressors)
166
+ }
167
+ const pageView = new DataView(page.buffer, page.byteOffset, page.byteLength)
168
+ const pageReader = { view: pageView, offset: 0 }
169
+
170
+ // read values based on encoding
171
+ /** @type {DecodedArray} */
172
+ let dataPage
173
+ const nValues = daph2.num_values - daph2.num_nulls
174
+ if (daph2.encoding === 'PLAIN') {
175
+ dataPage = readPlain(pageReader, type, nValues, element.type_length)
176
+ } else if (daph2.encoding === 'RLE') {
177
+ // assert(type === 'BOOLEAN')
178
+ dataPage = new Array(nValues)
179
+ readRleBitPackedHybrid(pageReader, 1, dataPage)
180
+ dataPage = dataPage.map(x => !!x)
181
+ } else if (
182
+ daph2.encoding === 'PLAIN_DICTIONARY' ||
183
+ daph2.encoding === 'RLE_DICTIONARY'
184
+ ) {
185
+ const bitWidth = pageView.getUint8(pageReader.offset++)
186
+ dataPage = new Array(nValues)
187
+ readRleBitPackedHybrid(pageReader, bitWidth, dataPage, uncompressedPageSize - 1)
188
+ } else if (daph2.encoding === 'DELTA_BINARY_PACKED') {
189
+ const int32 = type === 'INT32'
190
+ dataPage = int32 ? new Int32Array(nValues) : new BigInt64Array(nValues)
191
+ deltaBinaryUnpack(pageReader, nValues, dataPage)
192
+ } else if (daph2.encoding === 'DELTA_LENGTH_BYTE_ARRAY') {
193
+ dataPage = new Array(nValues)
194
+ deltaLengthByteArray(pageReader, nValues, dataPage)
195
+ } else if (daph2.encoding === 'DELTA_BYTE_ARRAY') {
196
+ dataPage = new Array(nValues)
197
+ deltaByteArray(pageReader, nValues, dataPage)
198
+ } else if (daph2.encoding === 'BYTE_STREAM_SPLIT') {
199
+ dataPage = byteStreamSplit(pageReader, nValues, type, element.type_length)
200
+ } else {
201
+ throw new Error(`parquet unsupported encoding: ${daph2.encoding}`)
202
+ }
203
+
204
+ return { definitionLevels, repetitionLevels, dataPage }
205
+ }
206
+
207
+ /**
208
+ * @param {DataReader} reader
209
+ * @param {DataPageHeaderV2} daph2 data page header v2
210
+ * @param {SchemaTree[]} schemaPath
211
+ * @returns {any[]} repetition levels
212
+ */
213
+ function readRepetitionLevelsV2(reader, daph2, schemaPath) {
214
+ const maxRepetitionLevel = getMaxRepetitionLevel(schemaPath)
215
+ if (!maxRepetitionLevel) return []
216
+
217
+ const values = new Array(daph2.num_values)
218
+ readRleBitPackedHybrid(reader, bitWidth(maxRepetitionLevel), values, daph2.repetition_levels_byte_length)
219
+ return values
220
+ }
221
+
222
+ /**
223
+ * @param {DataReader} reader
224
+ * @param {DataPageHeaderV2} daph2 data page header v2
225
+ * @param {SchemaTree[]} schemaPath
226
+ * @returns {number[] | undefined} definition levels
227
+ */
228
+ function readDefinitionLevelsV2(reader, daph2, schemaPath) {
229
+ const maxDefinitionLevel = getMaxDefinitionLevel(schemaPath)
230
+ if (maxDefinitionLevel) {
231
+ // V2 we know the length
232
+ const values = new Array(daph2.num_values)
233
+ readRleBitPackedHybrid(reader, bitWidth(maxDefinitionLevel), values, daph2.definition_levels_byte_length)
234
+ return values
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Minimum bits needed to store value.
240
+ *
241
+ * @param {number} value
242
+ * @returns {number}
243
+ */
244
+ function bitWidth(value) {
245
+ return 32 - Math.clz32(value)
246
+ }
web/node_modules/hyparquet/src/delta.js ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {DataReader} from '../src/types.js'
3
+ */
4
+
5
+ import { readVarInt, readZigZagBigInt } from './thrift.js'
6
+
7
+ /**
8
+ * @param {DataReader} reader
9
+ * @param {number} count number of values to read
10
+ * @param {Int32Array | BigInt64Array} output
11
+ */
12
+ export function deltaBinaryUnpack(reader, count, output) {
13
+ const int32 = output instanceof Int32Array
14
+ const blockSize = readVarInt(reader)
15
+ const miniblockPerBlock = readVarInt(reader)
16
+ readVarInt(reader) // assert(=== count)
17
+ let value = readZigZagBigInt(reader) // first value
18
+ let outputIndex = 0
19
+ output[outputIndex++] = int32 ? Number(value) : value
20
+
21
+ const valuesPerMiniblock = blockSize / miniblockPerBlock
22
+
23
+ while (outputIndex < count) {
24
+ // new block
25
+ const minDelta = readZigZagBigInt(reader)
26
+ const bitWidths = new Uint8Array(miniblockPerBlock)
27
+ for (let i = 0; i < miniblockPerBlock; i++) {
28
+ bitWidths[i] = reader.view.getUint8(reader.offset++)
29
+ }
30
+
31
+ for (let i = 0; i < miniblockPerBlock && outputIndex < count; i++) {
32
+ // new miniblock
33
+ const bitWidth = BigInt(bitWidths[i])
34
+ if (bitWidth) {
35
+ let bitpackPos = 0n
36
+ let miniblockCount = valuesPerMiniblock
37
+ const mask = (1n << bitWidth) - 1n
38
+ while (miniblockCount && outputIndex < count) {
39
+ let bits = BigInt(reader.view.getUint8(reader.offset)) >> bitpackPos & mask // TODO: don't re-read value every time
40
+ bitpackPos += bitWidth
41
+ while (bitpackPos >= 8) {
42
+ bitpackPos -= 8n
43
+ reader.offset++
44
+ if (bitpackPos) {
45
+ bits |= BigInt(reader.view.getUint8(reader.offset)) << bitWidth - bitpackPos & mask
46
+ }
47
+ }
48
+ const delta = minDelta + bits
49
+ value += delta
50
+ output[outputIndex++] = int32 ? Number(value) : value
51
+ miniblockCount--
52
+ }
53
+ if (miniblockCount) {
54
+ // consume leftover miniblock
55
+ reader.offset += Math.ceil((miniblockCount * Number(bitWidth) + Number(bitpackPos)) / 8)
56
+ }
57
+ } else {
58
+ for (let j = 0; j < valuesPerMiniblock && outputIndex < count; j++) {
59
+ value += minDelta
60
+ output[outputIndex++] = int32 ? Number(value) : value
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * @param {DataReader} reader
69
+ * @param {number} count
70
+ * @param {Uint8Array[]} output
71
+ */
72
+ export function deltaLengthByteArray(reader, count, output) {
73
+ const lengths = new Int32Array(count)
74
+ deltaBinaryUnpack(reader, count, lengths)
75
+ for (let i = 0; i < count; i++) {
76
+ output[i] = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, lengths[i])
77
+ reader.offset += lengths[i]
78
+ }
79
+ }
80
+
81
+ /**
82
+ * @param {DataReader} reader
83
+ * @param {number} count
84
+ * @param {Uint8Array[]} output
85
+ */
86
+ export function deltaByteArray(reader, count, output) {
87
+ const prefixData = new Int32Array(count)
88
+ deltaBinaryUnpack(reader, count, prefixData)
89
+ const suffixData = new Int32Array(count)
90
+ deltaBinaryUnpack(reader, count, suffixData)
91
+
92
+ for (let i = 0; i < count; i++) {
93
+ const suffix = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, suffixData[i])
94
+ if (prefixData[i]) {
95
+ // copy from previous value
96
+ output[i] = new Uint8Array(prefixData[i] + suffixData[i])
97
+ output[i].set(output[i - 1].subarray(0, prefixData[i]))
98
+ output[i].set(suffix, prefixData[i])
99
+ } else {
100
+ output[i] = suffix
101
+ }
102
+ reader.offset += suffixData[i]
103
+ }
104
+ }
web/node_modules/hyparquet/src/encoding.js ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {DataReader, DecodedArray, ParquetType} from '../src/types.js'
3
+ */
4
+
5
+ import { readVarInt } from './thrift.js'
6
+
7
+ /**
8
+ * Read values from a run-length encoded/bit-packed hybrid encoding.
9
+ *
10
+ * If length is zero, then read int32 length at the start.
11
+ *
12
+ * @param {DataReader} reader
13
+ * @param {number} width - bitwidth
14
+ * @param {DecodedArray} output
15
+ * @param {number} [length] - length of the encoded data
16
+ */
17
+ export function readRleBitPackedHybrid(reader, width, output, length) {
18
+ if (length === undefined) {
19
+ length = reader.view.getUint32(reader.offset, true)
20
+ reader.offset += 4
21
+ }
22
+ const startOffset = reader.offset
23
+ let seen = 0
24
+ while (seen < output.length) {
25
+ const header = readVarInt(reader)
26
+ if (header & 1) {
27
+ // bit-packed
28
+ seen = readBitPacked(reader, header, width, output, seen)
29
+ } else {
30
+ // rle
31
+ const count = header >>> 1
32
+ readRle(reader, count, width, output, seen)
33
+ seen += count
34
+ }
35
+ }
36
+ reader.offset = startOffset + length // duckdb writes an empty block
37
+ }
38
+
39
+ /**
40
+ * Run-length encoding: read value with bitWidth and repeat it count times.
41
+ *
42
+ * @param {DataReader} reader
43
+ * @param {number} count
44
+ * @param {number} bitWidth
45
+ * @param {DecodedArray} output
46
+ * @param {number} seen
47
+ */
48
+ function readRle(reader, count, bitWidth, output, seen) {
49
+ const width = bitWidth + 7 >> 3
50
+ let value = 0
51
+ for (let i = 0; i < width; i++) {
52
+ value |= reader.view.getUint8(reader.offset++) << (i << 3)
53
+ }
54
+ // assert(value < 1 << bitWidth)
55
+
56
+ // repeat value count times
57
+ for (let i = 0; i < count; i++) {
58
+ output[seen + i] = value
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Read a bit-packed run of the rle/bitpack hybrid.
64
+ * Supports width > 8 (crossing bytes).
65
+ *
66
+ * @param {DataReader} reader
67
+ * @param {number} header - bit-pack header
68
+ * @param {number} bitWidth
69
+ * @param {DecodedArray} output
70
+ * @param {number} seen
71
+ * @returns {number} total output values so far
72
+ */
73
+ function readBitPacked(reader, header, bitWidth, output, seen) {
74
+ let count = header >> 1 << 3 // values to read
75
+ const mask = (1 << bitWidth) - 1
76
+
77
+ let data = 0
78
+ if (reader.offset < reader.view.byteLength) {
79
+ data = reader.view.getUint8(reader.offset++)
80
+ } else if (mask) {
81
+ // sometimes out-of-bounds reads are masked out
82
+ throw new Error(`parquet bitpack offset ${reader.offset} out of range`)
83
+ }
84
+ let left = 8
85
+ let right = 0
86
+
87
+ // read values
88
+ while (count) {
89
+ // if we have crossed a byte boundary, shift the data
90
+ if (right > 8) {
91
+ right -= 8
92
+ left -= 8
93
+ data >>>= 8
94
+ } else if (left - right < bitWidth) {
95
+ // if we don't have bitWidth number of bits to read, read next byte
96
+ data |= reader.view.getUint8(reader.offset) << left
97
+ reader.offset++
98
+ left += 8
99
+ } else {
100
+ if (seen < output.length) {
101
+ // emit value
102
+ output[seen++] = data >> right & mask
103
+ }
104
+ count--
105
+ right += bitWidth
106
+ }
107
+ }
108
+
109
+ return seen
110
+ }
111
+
112
+ /**
113
+ * @param {DataReader} reader
114
+ * @param {number} count
115
+ * @param {ParquetType} type
116
+ * @param {number | undefined} typeLength
117
+ * @returns {DecodedArray}
118
+ */
119
+ export function byteStreamSplit(reader, count, type, typeLength) {
120
+ const width = byteWidth(type, typeLength)
121
+ const bytes = new Uint8Array(count * width)
122
+ for (let b = 0; b < width; b++) {
123
+ for (let i = 0; i < count; i++) {
124
+ bytes[i * width + b] = reader.view.getUint8(reader.offset++)
125
+ }
126
+ }
127
+ // interpret bytes as typed array
128
+ if (type === 'FLOAT') return new Float32Array(bytes.buffer)
129
+ else if (type === 'DOUBLE') return new Float64Array(bytes.buffer)
130
+ else if (type === 'INT32') return new Int32Array(bytes.buffer)
131
+ else if (type === 'INT64') return new BigInt64Array(bytes.buffer)
132
+ else if (type === 'FIXED_LEN_BYTE_ARRAY') {
133
+ // split into arrays of typeLength
134
+ const split = new Array(count)
135
+ for (let i = 0; i < count; i++) {
136
+ split[i] = bytes.subarray(i * width, (i + 1) * width)
137
+ }
138
+ return split
139
+ }
140
+ throw new Error(`parquet byte_stream_split unsupported type: ${type}`)
141
+ }
142
+
143
+ /**
144
+ * @param {ParquetType} type
145
+ * @param {number | undefined} typeLength
146
+ * @returns {number}
147
+ */
148
+ function byteWidth(type, typeLength) {
149
+ switch (type) {
150
+ case 'INT32':
151
+ case 'FLOAT':
152
+ return 4
153
+ case 'INT64':
154
+ case 'DOUBLE':
155
+ return 8
156
+ case 'FIXED_LEN_BYTE_ARRAY':
157
+ if (!typeLength) throw new Error('parquet byteWidth missing type_length')
158
+ return typeLength
159
+ default:
160
+ throw new Error(`parquet unsupported type: ${type}`)
161
+ }
162
+ }
web/node_modules/hyparquet/src/filter.js ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {BloomFilter, ParquetQueryFilter, RowGroup, SchemaElement} from '../src/types.js'
3
+ */
4
+
5
+ import { hashParquetValue, sbbfContains } from './bloom.js'
6
+ import { equals } from './utils.js'
7
+
8
+ /**
9
+ * Returns an array of top-level column names needed to evaluate the filter.
10
+ *
11
+ * @param {ParquetQueryFilter} [filter]
12
+ * @returns {string[]}
13
+ */
14
+ export function columnsNeededForFilter(filter) {
15
+ if (!filter) return []
16
+ /** @type {string[]} */
17
+ const columns = []
18
+ if ('$and' in filter && Array.isArray(filter.$and)) {
19
+ columns.push(...filter.$and.flatMap(columnsNeededForFilter))
20
+ } else if ('$or' in filter && Array.isArray(filter.$or)) {
21
+ columns.push(...filter.$or.flatMap(columnsNeededForFilter))
22
+ } else if ('$nor' in filter && Array.isArray(filter.$nor)) {
23
+ columns.push(...filter.$nor.flatMap(columnsNeededForFilter))
24
+ } else {
25
+ // Map dot-notation paths to top-level column names
26
+ columns.push(...Object.keys(filter).map(key => key.split('.')[0]))
27
+ }
28
+ return [...new Set(columns)]
29
+ }
30
+
31
+ /**
32
+ * Match a record against a query filter
33
+ *
34
+ * @param {Record<string, any>} record
35
+ * @param {ParquetQueryFilter} filter
36
+ * @param {boolean} [strict]
37
+ * @returns {boolean}
38
+ */
39
+ export function matchFilter(record, filter, strict = true) {
40
+ if ('$and' in filter && Array.isArray(filter.$and)) {
41
+ return filter.$and.every(subQuery => matchFilter(record, subQuery, strict))
42
+ }
43
+ if ('$or' in filter && Array.isArray(filter.$or)) {
44
+ return filter.$or.some(subQuery => matchFilter(record, subQuery, strict))
45
+ }
46
+ if ('$nor' in filter && Array.isArray(filter.$nor)) {
47
+ return !filter.$nor.some(subQuery => matchFilter(record, subQuery, strict))
48
+ }
49
+
50
+ return Object.entries(filter).every(([field, condition]) => {
51
+ const value = resolve(record, field)
52
+
53
+ // implicit $eq for non-object conditions
54
+ if (typeof condition !== 'object' || condition === null || Array.isArray(condition)) {
55
+ return equals(value, condition, strict)
56
+ }
57
+
58
+ return Object.entries(condition || {}).every(([operator, target]) => {
59
+ if (operator === '$gt') return value > target
60
+ if (operator === '$gte') return value >= target
61
+ if (operator === '$lt') return value < target
62
+ if (operator === '$lte') return value <= target
63
+ if (operator === '$eq') return equals(value, target, strict)
64
+ if (operator === '$ne') return !equals(value, target, strict)
65
+ if (operator === '$in') return Array.isArray(target) && target.includes(value)
66
+ if (operator === '$nin') return Array.isArray(target) && !target.includes(value)
67
+ if (operator === '$not') return !matchFilter({ [field]: value }, { [field]: target }, strict)
68
+ return true
69
+ })
70
+ })
71
+ }
72
+
73
+ /**
74
+ * Check if a row group can be skipped based on filter and column statistics,
75
+ * optionally consulting per-column bloom filters for equality predicates that
76
+ * statistics can't decide.
77
+ *
78
+ * @param {object} options
79
+ * @param {RowGroup} options.rowGroup
80
+ * @param {string[]} options.physicalColumns
81
+ * @param {ParquetQueryFilter | undefined} options.filter
82
+ * @param {boolean} [options.strict]
83
+ * @param {Record<string, BloomFilter>} [options.bloomFilters] keyed by top-level column name
84
+ * @param {Record<string, SchemaElement>} [options.schemaElements] keyed by top-level column name, required to use bloomFilters
85
+ * @returns {boolean} true if the row group can be skipped
86
+ */
87
+ export function canSkipRowGroup({ rowGroup, physicalColumns, filter, strict = true, bloomFilters, schemaElements }) {
88
+ if (!filter) return false
89
+
90
+ // Handle logical operators
91
+ if ('$and' in filter && Array.isArray(filter.$and)) {
92
+ // For AND, we can skip if ANY condition allows skipping
93
+ return filter.$and.some(subFilter => canSkipRowGroup({ rowGroup, physicalColumns, filter: subFilter, strict, bloomFilters, schemaElements }))
94
+ }
95
+ if ('$or' in filter && Array.isArray(filter.$or)) {
96
+ // For OR, we can skip only if ALL conditions allow skipping
97
+ return filter.$or.every(subFilter => canSkipRowGroup({ rowGroup, physicalColumns, filter: subFilter, strict, bloomFilters, schemaElements }))
98
+ }
99
+ if ('$nor' in filter && Array.isArray(filter.$nor)) {
100
+ // For NOR, we can skip if none of the conditions allow skipping
101
+ // This is complex, so we'll be conservative and not skip
102
+ return false
103
+ }
104
+
105
+ // Check column filters
106
+ for (const [field, condition] of Object.entries(filter)) {
107
+ // Find the column chunk for this field
108
+ const columnIndex = physicalColumns.indexOf(field)
109
+ if (columnIndex === -1) continue
110
+
111
+ const stats = rowGroup.columns[columnIndex].meta_data?.statistics
112
+ const { min, max, min_value, max_value } = stats || {}
113
+ const minVal = min_value !== undefined ? min_value : min
114
+ const maxVal = max_value !== undefined ? max_value : max
115
+ const haveStats = minVal !== undefined && maxVal !== undefined
116
+
117
+ const bloom = bloomFilters?.[field]
118
+ const element = schemaElements?.[field]
119
+
120
+ // Handle operators
121
+ for (const [operator, target] of Object.entries(condition || {})) {
122
+ // Statistics-based skipping
123
+ if (haveStats) {
124
+ if (operator === '$gt' && maxVal <= target) return true
125
+ if (operator === '$gte' && maxVal < target) return true
126
+ if (operator === '$lt' && minVal >= target) return true
127
+ if (operator === '$lte' && minVal > target) return true
128
+ if (operator === '$eq' && (target < minVal || target > maxVal)) return true
129
+ if (operator === '$ne' && equals(minVal, maxVal, strict) && equals(minVal, target, strict)) return true
130
+ if (operator === '$in' && Array.isArray(target) && target.every(v => v < minVal || v > maxVal)) return true
131
+ if (operator === '$nin' && Array.isArray(target) && equals(minVal, maxVal, strict) && target.includes(minVal)) return true
132
+ }
133
+ // Bloom-filter skipping for equality predicates (proves absence, never presence)
134
+ if (bloom && element) {
135
+ if (operator === '$eq') {
136
+ const hash = hashParquetValue(target, element)
137
+ if (hash !== undefined && !sbbfContains(bloom.blocks, hash)) return true
138
+ }
139
+ if (operator === '$in' && Array.isArray(target) && target.length > 0) {
140
+ let allAbsent = true
141
+ for (const v of target) {
142
+ const h = hashParquetValue(v, element)
143
+ if (h === undefined || sbbfContains(bloom.blocks, h)) {
144
+ allAbsent = false
145
+ break
146
+ }
147
+ }
148
+ if (allAbsent) return true
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ return false
155
+ }
156
+
157
+ /**
158
+ * Resolve a dot-notation path to a value in a nested object.
159
+ *
160
+ * @param {Record<string, any>} record
161
+ * @param {string} path
162
+ * @returns {any}
163
+ */
164
+ function resolve(record, path) {
165
+ let value = record
166
+ for (const part of path.split('.')) {
167
+ value = value?.[part]
168
+ }
169
+ return value
170
+ }
web/node_modules/hyparquet/src/geoparquet.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @param {SchemaElement[]} schema
3
+ * @param {KeyValue[] | undefined} key_value_metadata
4
+ * @returns {void}
5
+ */
6
+ export function markGeoColumns(schema, key_value_metadata) {
7
+ // Prepare the list of GeoParquet columns
8
+ /** @type {Map<string, LogicalType>} */
9
+ const columns = new Map()
10
+ const geo = key_value_metadata?.find(({ key }) => key === 'geo')?.value
11
+ const decodedColumns = (geo && JSON.parse(geo)?.columns) ?? {}
12
+ for (const [name, column] of Object.entries(decodedColumns)) {
13
+ if (column.encoding !== 'WKB') continue
14
+
15
+ const type = column.edges === 'spherical' ? 'GEOGRAPHY' : 'GEOMETRY'
16
+ const id = column.crs?.id ?? column.crs?.ids?.[0]
17
+ const crs = id ? `${id.authority}:${id.code.toString()}` : undefined
18
+ // Note: we can't infer GEOGRAPHY's algorithm from GeoParquet
19
+ columns.set(name, { type, crs })
20
+ }
21
+
22
+ // Mark schema elements with logical type
23
+ // Only look at root-level columns of type BYTE_ARRAY without existing logical_type
24
+ for (let i = 1; i < schema.length; i++) { // skip root
25
+ const { logical_type, name, num_children, type } = schema[i]
26
+ if (num_children) {
27
+ i += num_children
28
+ continue // skip the element and its children
29
+ }
30
+ if (type === 'BYTE_ARRAY' && !logical_type) {
31
+ schema[i].logical_type = columns.get(name)
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * @import {KeyValue, LogicalType, SchemaElement} from '../src/types.js'
38
+ */
web/node_modules/hyparquet/src/index.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export { readColumnIndex, readOffsetIndex } from './indexes.js'
2
+ export { parquetMetadata, parquetMetadataAsync, parquetSchema } from './metadata.js'
3
+ export { parquetRead, parquetReadObjects } from './read.js'
4
+ export { parquetQuery } from './query.js'
5
+ export { snappyUncompress } from './snappy.js'
6
+ export { asyncBufferFromUrl, byteLengthFromUrl, cachedAsyncBuffer, flatten, toJson } from './utils.js'
7
+
8
+ /**
9
+ * Explicitly export types for use in downstream typescript projects through
10
+ * `import { ParquetReadOptions } from 'hyparquet'` for example.
11
+ *
12
+ * @template {any} T
13
+ * @typedef {import('../src/types.d.ts').Awaitable<T>} Awaitable<T>
14
+ */
15
+ /**
16
+ * @typedef {import('../src/types.d.ts').AsyncBuffer} AsyncBuffer
17
+ * @typedef {import('../src/types.d.ts').AsyncRowGroup} AsyncRowGroup
18
+ * @typedef {import('../src/types.d.ts').DataReader} DataReader
19
+ * @typedef {import('../src/types.d.ts').FileMetaData} FileMetaData
20
+ * @typedef {import('../src/types.d.ts').SchemaTree} SchemaTree
21
+ * @typedef {import('../src/types.d.ts').SchemaElement} SchemaElement
22
+ * @typedef {import('../src/types.d.ts').ParquetType} ParquetType
23
+ * @typedef {import('../src/types.d.ts').FieldRepetitionType} FieldRepetitionType
24
+ * @typedef {import('../src/types.d.ts').ConvertedType} ConvertedType
25
+ * @typedef {import('../src/types.d.ts').TimeUnit} TimeUnit
26
+ * @typedef {import('../src/types.d.ts').LogicalType} LogicalType
27
+ * @typedef {import('../src/types.d.ts').RowGroup} RowGroup
28
+ * @typedef {import('../src/types.d.ts').ColumnChunk} ColumnChunk
29
+ * @typedef {import('../src/types.d.ts').ColumnMetaData} ColumnMetaData
30
+ * @typedef {import('../src/types.d.ts').Encoding} Encoding
31
+ * @typedef {import('../src/types.d.ts').CompressionCodec} CompressionCodec
32
+ * @typedef {import('../src/types.d.ts').Compressors} Compressors
33
+ * @typedef {import('../src/types.d.ts').KeyValue} KeyValue
34
+ * @typedef {import('../src/types.d.ts').Statistics} Statistics
35
+ * @typedef {import('../src/types.d.ts').GeospatialStatistics} GeospatialStatistics
36
+ * @typedef {import('../src/types.d.ts').BoundingBox} BoundingBox
37
+ * @typedef {import('../src/types.d.ts').PageType} PageType
38
+ * @typedef {import('../src/types.d.ts').PageHeader} PageHeader
39
+ * @typedef {import('../src/types.d.ts').DataPageHeader} DataPageHeader
40
+ * @typedef {import('../src/types.d.ts').DictionaryPageHeader} DictionaryPageHeader
41
+ * @typedef {import('../src/types.d.ts').DecodedArray} DecodedArray
42
+ * @typedef {import('../src/types.d.ts').OffsetIndex} OffsetIndex
43
+ * @typedef {import('../src/types.d.ts').ColumnIndex} ColumnIndex
44
+ * @typedef {import('../src/types.d.ts').BoundaryOrder} BoundaryOrder
45
+ * @typedef {import('../src/types.d.ts').ColumnData} ColumnData
46
+ * @typedef {import('../src/types.d.ts').SubColumnData} SubColumnData
47
+ * @typedef {import('../src/types.d.ts').ParquetReadOptions} ParquetReadOptions
48
+ * @typedef {import('../src/types.d.ts').MetadataOptions} MetadataOptions
49
+ * @typedef {import('../src/types.d.ts').ParquetParsers} ParquetParsers
50
+ * @typedef {import('../src/types.d.ts').ParquetQueryFilter} ParquetQueryFilter
51
+ */
web/node_modules/hyparquet/src/indexes.js ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {ColumnIndex, DataReader, OffsetIndex, PageLocation, ParquetParsers, SchemaElement} from '../src/types.js'
3
+ */
4
+
5
+ import { BoundaryOrders } from './constants.js'
6
+ import { DEFAULT_PARSERS } from './convert.js'
7
+ import { convertMetadata } from './metadata.js'
8
+ import { deserializeTCompactProtocol } from './thrift.js'
9
+
10
+ /**
11
+ * @param {DataReader} reader
12
+ * @param {SchemaElement} schema
13
+ * @param {ParquetParsers | undefined} parsers
14
+ * @returns {ColumnIndex}
15
+ */
16
+ export function readColumnIndex(reader, schema, parsers = undefined) {
17
+ parsers = { ...DEFAULT_PARSERS, ...parsers }
18
+
19
+ const thrift = deserializeTCompactProtocol(reader)
20
+ return {
21
+ null_pages: thrift.field_1,
22
+ min_values: thrift.field_2.map((/** @type {any} */ m) => convertMetadata(m, schema, parsers)),
23
+ max_values: thrift.field_3.map((/** @type {any} */ m) => convertMetadata(m, schema, parsers)),
24
+ boundary_order: BoundaryOrders[thrift.field_4],
25
+ null_counts: thrift.field_5,
26
+ repetition_level_histograms: thrift.field_6,
27
+ definition_level_histograms: thrift.field_7,
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @param {DataReader} reader
33
+ * @returns {OffsetIndex}
34
+ */
35
+ export function readOffsetIndex(reader) {
36
+ const thrift = deserializeTCompactProtocol(reader)
37
+ return {
38
+ // @ts-ignore
39
+ page_locations: thrift.field_1.map(loc => ({
40
+ offset: loc.field_1,
41
+ compressed_page_size: loc.field_2,
42
+ first_row_index: loc.field_3,
43
+ })),
44
+ unencoded_byte_array_data_bytes: thrift.field_2,
45
+ }
46
+ }
web/node_modules/hyparquet/src/metadata.js ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { CompressionCodecs, ConvertedTypes, EdgeInterpolationAlgorithms, Encodings, FieldRepetitionTypes, PageTypes, ParquetTypes } from './constants.js'
2
+ import { DEFAULT_PARSERS, parseDecimal, parseFloat16 } from './convert.js'
3
+ import { getSchemaPath } from './schema.js'
4
+ import { deserializeTCompactProtocol } from './thrift.js'
5
+ import { markGeoColumns } from './geoparquet.js'
6
+
7
+ /**
8
+ * @import {AsyncBuffer, FileMetaData, KeyValue, LogicalType, MetadataOptions, MinMaxType, ParquetParsers, SchemaElement, SchemaTree, Statistics, TimeUnit} from '../src/types.js'
9
+ */
10
+
11
+ export const defaultInitialFetchSize = 1 << 19 // 512kb
12
+
13
+ const decoder = new TextDecoder()
14
+ function decode(/** @type {Uint8Array} */ value) {
15
+ return value && decoder.decode(value)
16
+ }
17
+
18
+ /**
19
+ * Read parquet metadata from an async buffer.
20
+ *
21
+ * An AsyncBuffer is like an ArrayBuffer, but the slices are loaded
22
+ * asynchronously, possibly over the network.
23
+ *
24
+ * You must provide the byteLength of the buffer, typically from a HEAD request.
25
+ *
26
+ * In theory, you could use suffix-range requests to fetch the end of the file,
27
+ * and save a round trip. But in practice, this doesn't work because chrome
28
+ * deems suffix-range requests as a not-safe-listed header, and will require
29
+ * a pre-flight. So the byteLength is required.
30
+ *
31
+ * To make this efficient, we initially request the last 512kb of the file,
32
+ * which is likely to contain the metadata. If the metadata length exceeds the
33
+ * initial fetch, 512kb, we request the rest of the metadata from the AsyncBuffer.
34
+ *
35
+ * This ensures that we either make one 512kb initial request for the metadata,
36
+ * or a second request for up to the metadata size.
37
+ *
38
+ * @param {AsyncBuffer} asyncBuffer parquet file contents
39
+ * @param {MetadataOptions & { initialFetchSize?: number }} options initial fetch size in bytes (default 512kb)
40
+ * @returns {Promise<FileMetaData>} parquet metadata object
41
+ */
42
+ export async function parquetMetadataAsync(asyncBuffer, { parsers, initialFetchSize = defaultInitialFetchSize, geoparquet = true } = {}) {
43
+ if (!asyncBuffer || !(asyncBuffer.byteLength >= 0)) throw new Error('parquet expected AsyncBuffer')
44
+
45
+ // fetch last bytes (footer) of the file
46
+ const footerOffset = Math.max(0, asyncBuffer.byteLength - initialFetchSize)
47
+ const footerBuffer = await asyncBuffer.slice(footerOffset, asyncBuffer.byteLength)
48
+
49
+ // Check for parquet magic number "PAR1"
50
+ const footerView = new DataView(footerBuffer)
51
+ if (footerView.getUint32(footerBuffer.byteLength - 4, true) !== 0x31524150) {
52
+ throw new Error('parquet file invalid (footer != PAR1)')
53
+ }
54
+
55
+ // Parquet files store metadata at the end of the file
56
+ // Metadata length is 4 bytes before the last PAR1
57
+ const metadataLength = footerView.getUint32(footerBuffer.byteLength - 8, true)
58
+ if (metadataLength > asyncBuffer.byteLength - 8) {
59
+ throw new Error(`parquet metadata length ${metadataLength} exceeds available buffer ${asyncBuffer.byteLength - 8}`)
60
+ }
61
+
62
+ // check if metadata size fits inside the initial fetch
63
+ if (metadataLength + 8 > initialFetchSize) {
64
+ // fetch the rest of the metadata
65
+ const metadataOffset = asyncBuffer.byteLength - metadataLength - 8
66
+ const metadataBuffer = await asyncBuffer.slice(metadataOffset, footerOffset)
67
+ // combine initial fetch with the new slice
68
+ const combinedBuffer = new ArrayBuffer(metadataLength + 8)
69
+ const combinedView = new Uint8Array(combinedBuffer)
70
+ combinedView.set(new Uint8Array(metadataBuffer))
71
+ combinedView.set(new Uint8Array(footerBuffer), footerOffset - metadataOffset)
72
+ return parquetMetadata(combinedBuffer, { parsers, geoparquet })
73
+ } else {
74
+ // parse metadata from the footer
75
+ return parquetMetadata(footerBuffer, { parsers, geoparquet })
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Read parquet metadata from a buffer synchronously.
81
+ *
82
+ * @param {ArrayBuffer} arrayBuffer parquet file footer
83
+ * @param {MetadataOptions} options metadata parsing options
84
+ * @returns {FileMetaData} parquet metadata object
85
+ */
86
+ export function parquetMetadata(arrayBuffer, { parsers, geoparquet = true } = {}) {
87
+ if (!(arrayBuffer instanceof ArrayBuffer)) throw new Error('parquet expected ArrayBuffer')
88
+ const view = new DataView(arrayBuffer)
89
+
90
+ // Use default parsers if not given
91
+ parsers = { ...DEFAULT_PARSERS, ...parsers }
92
+
93
+ // Validate footer magic number "PAR1"
94
+ if (view.byteLength < 8) {
95
+ throw new Error('parquet file is too short')
96
+ }
97
+ if (view.getUint32(view.byteLength - 4, true) !== 0x31524150) {
98
+ throw new Error('parquet file invalid (footer != PAR1)')
99
+ }
100
+
101
+ // Parquet files store metadata at the end of the file
102
+ // Metadata length is 4 bytes before the last PAR1
103
+ const metadataLengthOffset = view.byteLength - 8
104
+ const metadataLength = view.getUint32(metadataLengthOffset, true)
105
+ if (metadataLength > view.byteLength - 8) {
106
+ // {metadata}, metadata_length, PAR1
107
+ throw new Error(`parquet metadata length ${metadataLength} exceeds available buffer ${view.byteLength - 8}`)
108
+ }
109
+
110
+ const metadataOffset = metadataLengthOffset - metadataLength
111
+ const reader = { view, offset: metadataOffset }
112
+ const metadata = deserializeTCompactProtocol(reader)
113
+
114
+ // Parse metadata from thrift data
115
+ const version = metadata.field_1
116
+ /** @type {SchemaElement[]} */
117
+ const schema = metadata.field_2.map((/** @type {any} */ field) => ({
118
+ type: ParquetTypes[field.field_1],
119
+ type_length: field.field_2,
120
+ repetition_type: FieldRepetitionTypes[field.field_3],
121
+ name: decode(field.field_4),
122
+ num_children: field.field_5,
123
+ converted_type: ConvertedTypes[field.field_6],
124
+ scale: field.field_7,
125
+ precision: field.field_8,
126
+ field_id: field.field_9,
127
+ logical_type: logicalType(field.field_10),
128
+ }))
129
+ // schema element per column index
130
+ const columnSchema = schema.filter(e => e.type)
131
+ const num_rows = metadata.field_3
132
+ const row_groups = metadata.field_4.map((/** @type {any} */ rowGroup) => ({
133
+ columns: rowGroup.field_1.map((/** @type {any} */ column, /** @type {number} */ columnIndex) => ({
134
+ file_path: decode(column.field_1),
135
+ file_offset: column.field_2,
136
+ meta_data: column.field_3 && {
137
+ type: ParquetTypes[column.field_3.field_1],
138
+ encodings: column.field_3.field_2?.map((/** @type {number} */ e) => Encodings[e]),
139
+ path_in_schema: column.field_3.field_3.map(decode),
140
+ codec: CompressionCodecs[column.field_3.field_4],
141
+ num_values: column.field_3.field_5,
142
+ total_uncompressed_size: column.field_3.field_6,
143
+ total_compressed_size: column.field_3.field_7,
144
+ key_value_metadata: column.field_3.field_8?.map((/** @type {any} */ kv) => ({
145
+ key: decode(kv.field_1),
146
+ value: decode(kv.field_2),
147
+ })),
148
+ data_page_offset: column.field_3.field_9,
149
+ index_page_offset: column.field_3.field_10,
150
+ dictionary_page_offset: column.field_3.field_11,
151
+ statistics: convertStats(column.field_3.field_12, columnSchema[columnIndex], parsers),
152
+ encoding_stats: column.field_3.field_13?.map((/** @type {any} */ encodingStat) => ({
153
+ page_type: PageTypes[encodingStat.field_1],
154
+ encoding: Encodings[encodingStat.field_2],
155
+ count: encodingStat.field_3,
156
+ })),
157
+ bloom_filter_offset: column.field_3.field_14,
158
+ bloom_filter_length: column.field_3.field_15,
159
+ size_statistics: column.field_3.field_16 && {
160
+ unencoded_byte_array_data_bytes: column.field_3.field_16.field_1,
161
+ repetition_level_histogram: column.field_3.field_16.field_2,
162
+ definition_level_histogram: column.field_3.field_16.field_3,
163
+ },
164
+ geospatial_statistics: column.field_3.field_17 && {
165
+ bbox: column.field_3.field_17.field_1 && {
166
+ xmin: column.field_3.field_17.field_1.field_1,
167
+ xmax: column.field_3.field_17.field_1.field_2,
168
+ ymin: column.field_3.field_17.field_1.field_3,
169
+ ymax: column.field_3.field_17.field_1.field_4,
170
+ zmin: column.field_3.field_17.field_1.field_5,
171
+ zmax: column.field_3.field_17.field_1.field_6,
172
+ mmin: column.field_3.field_17.field_1.field_7,
173
+ mmax: column.field_3.field_17.field_1.field_8,
174
+ },
175
+ geospatial_types: column.field_3.field_17.field_2,
176
+ },
177
+ },
178
+ offset_index_offset: column.field_4,
179
+ offset_index_length: column.field_5,
180
+ column_index_offset: column.field_6,
181
+ column_index_length: column.field_7,
182
+ crypto_metadata: column.field_8,
183
+ encrypted_column_metadata: column.field_9,
184
+ })),
185
+ total_byte_size: rowGroup.field_2,
186
+ num_rows: rowGroup.field_3,
187
+ sorting_columns: rowGroup.field_4?.map((/** @type {any} */ sortingColumn) => ({
188
+ column_idx: sortingColumn.field_1,
189
+ descending: sortingColumn.field_2,
190
+ nulls_first: sortingColumn.field_3,
191
+ })),
192
+ file_offset: rowGroup.field_5,
193
+ total_compressed_size: rowGroup.field_6,
194
+ ordinal: rowGroup.field_7,
195
+ }))
196
+ /** @type {KeyValue[] | undefined} */
197
+ const key_value_metadata = metadata.field_5?.map((/** @type {any} */ kv) => ({
198
+ key: decode(kv.field_1),
199
+ value: decode(kv.field_2),
200
+ }))
201
+ const created_by = decode(metadata.field_6)
202
+
203
+ if (geoparquet) {
204
+ markGeoColumns(schema, key_value_metadata)
205
+ }
206
+
207
+ return {
208
+ version,
209
+ schema,
210
+ num_rows,
211
+ row_groups,
212
+ key_value_metadata,
213
+ created_by,
214
+ metadata_length: metadataLength,
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Return a tree of schema elements from parquet metadata.
220
+ *
221
+ * @param {{schema: SchemaElement[]}} metadata parquet metadata object
222
+ * @returns {SchemaTree} tree of schema elements
223
+ */
224
+ export function parquetSchema({ schema }) {
225
+ return getSchemaPath(schema, [])[0]
226
+ }
227
+
228
+ /**
229
+ * @param {any} logicalType
230
+ * @returns {LogicalType | undefined}
231
+ */
232
+ function logicalType(logicalType) {
233
+ if (logicalType?.field_1) return { type: 'STRING' }
234
+ if (logicalType?.field_2) return { type: 'MAP' }
235
+ if (logicalType?.field_3) return { type: 'LIST' }
236
+ if (logicalType?.field_4) return { type: 'ENUM' }
237
+ if (logicalType?.field_5) return {
238
+ type: 'DECIMAL',
239
+ scale: logicalType.field_5.field_1,
240
+ precision: logicalType.field_5.field_2,
241
+ }
242
+ if (logicalType?.field_6) return { type: 'DATE' }
243
+ if (logicalType?.field_7) return {
244
+ type: 'TIME',
245
+ isAdjustedToUTC: logicalType.field_7.field_1,
246
+ unit: timeUnit(logicalType.field_7.field_2),
247
+ }
248
+ if (logicalType?.field_8) return {
249
+ type: 'TIMESTAMP',
250
+ isAdjustedToUTC: logicalType.field_8.field_1,
251
+ unit: timeUnit(logicalType.field_8.field_2),
252
+ }
253
+ if (logicalType?.field_10) return {
254
+ type: 'INTEGER',
255
+ bitWidth: logicalType.field_10.field_1,
256
+ isSigned: logicalType.field_10.field_2,
257
+ }
258
+ if (logicalType?.field_11) return { type: 'NULL' }
259
+ if (logicalType?.field_12) return { type: 'JSON' }
260
+ if (logicalType?.field_13) return { type: 'BSON' }
261
+ if (logicalType?.field_14) return { type: 'UUID' }
262
+ if (logicalType?.field_15) return { type: 'FLOAT16' }
263
+ if (logicalType?.field_16) return {
264
+ type: 'VARIANT',
265
+ specification_version: logicalType.field_16.field_1,
266
+ }
267
+ if (logicalType?.field_17) return {
268
+ type: 'GEOMETRY',
269
+ crs: decode(logicalType.field_17.field_1),
270
+ }
271
+ if (logicalType?.field_18) return {
272
+ type: 'GEOGRAPHY',
273
+ crs: decode(logicalType.field_18.field_1),
274
+ algorithm: EdgeInterpolationAlgorithms[logicalType.field_18.field_2],
275
+ }
276
+ return logicalType
277
+ }
278
+
279
+ /**
280
+ * @param {any} unit
281
+ * @returns {TimeUnit}
282
+ */
283
+ function timeUnit(unit) {
284
+ if (unit.field_1) return 'MILLIS'
285
+ if (unit.field_2) return 'MICROS'
286
+ if (unit.field_3) return 'NANOS'
287
+ throw new Error('parquet time unit required')
288
+ }
289
+
290
+ /**
291
+ * Convert column statistics based on column type.
292
+ *
293
+ * @param {any} stats
294
+ * @param {SchemaElement} schema
295
+ * @param {ParquetParsers} parsers
296
+ * @returns {Statistics}
297
+ */
298
+ function convertStats(stats, schema, parsers) {
299
+ return stats && {
300
+ max: convertMetadata(stats.field_1, schema, parsers),
301
+ min: convertMetadata(stats.field_2, schema, parsers),
302
+ null_count: stats.field_3,
303
+ distinct_count: stats.field_4,
304
+ max_value: convertMetadata(stats.field_5, schema, parsers),
305
+ min_value: convertMetadata(stats.field_6, schema, parsers),
306
+ is_max_value_exact: stats.field_7,
307
+ is_min_value_exact: stats.field_8,
308
+ }
309
+ }
310
+
311
+ /**
312
+ * @param {Uint8Array | undefined} value
313
+ * @param {SchemaElement} schema
314
+ * @param {ParquetParsers} parsers
315
+ * @returns {MinMaxType | undefined}
316
+ */
317
+ export function convertMetadata(value, schema, parsers) {
318
+ const { type, converted_type, logical_type } = schema
319
+ if (value === undefined) return value
320
+ if (type === 'BOOLEAN') return value[0] === 1
321
+ if (type === 'BYTE_ARRAY') return parsers.stringFromBytes(value)
322
+ const view = new DataView(value.buffer, value.byteOffset, value.byteLength)
323
+ if (type === 'FLOAT' && view.byteLength === 4) return view.getFloat32(0, true)
324
+ if (type === 'DOUBLE' && view.byteLength === 8) return view.getFloat64(0, true)
325
+ if (type === 'INT32' && converted_type === 'DATE') return parsers.dateFromDays(view.getInt32(0, true))
326
+ if (type === 'INT64' && converted_type === 'TIMESTAMP_MILLIS') return parsers.timestampFromMilliseconds(view.getBigInt64(0, true))
327
+ if (type === 'INT64' && converted_type === 'TIMESTAMP_MICROS') return parsers.timestampFromMicroseconds(view.getBigInt64(0, true))
328
+ if (type === 'INT64' && logical_type?.type === 'TIMESTAMP' && logical_type?.unit === 'NANOS') return parsers.timestampFromNanoseconds(view.getBigInt64(0, true))
329
+ if (type === 'INT64' && logical_type?.type === 'TIMESTAMP' && logical_type?.unit === 'MICROS') return parsers.timestampFromMicroseconds(view.getBigInt64(0, true))
330
+ if (type === 'INT64' && logical_type?.type === 'TIMESTAMP') return parsers.timestampFromMilliseconds(view.getBigInt64(0, true))
331
+ if (type === 'INT32' && view.byteLength === 4) return view.getInt32(0, true)
332
+ if (type === 'INT64' && view.byteLength === 8) return view.getBigInt64(0, true)
333
+ if (converted_type === 'DECIMAL') return parseDecimal(value) * 10 ** -(schema.scale || 0)
334
+ if (logical_type?.type === 'FLOAT16') return parseFloat16(value)
335
+ if (logical_type?.type === 'UUID') return parsers.uuidFromBytes(value)
336
+ if (type === 'FIXED_LEN_BYTE_ARRAY') return value
337
+ // assert(false)
338
+ return value
339
+ }
web/node_modules/hyparquet/src/node.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {AsyncBuffer} from '../src/types.js'
3
+ */
4
+
5
+ import { createReadStream, promises as fs } from 'fs'
6
+
7
+ export * from './index.js'
8
+
9
+ /**
10
+ * Construct an AsyncBuffer for a local file using node fs package.
11
+ *
12
+ * @param {string} filename
13
+ * @returns {Promise<AsyncBuffer>}
14
+ */
15
+ export async function asyncBufferFromFile(filename) {
16
+ const { size } = await fs.stat(filename)
17
+ return {
18
+ byteLength: size,
19
+ slice(start, end) {
20
+ // read file slice
21
+ const reader = createReadStream(filename, { start, end })
22
+ return new Promise((resolve, reject) => {
23
+ /** @type {any[]} */
24
+ const chunks = []
25
+ reader.on('data', chunk => chunks.push(chunk))
26
+ reader.on('error', reject)
27
+ reader.on('end', () => {
28
+ const buffer = Buffer.concat(chunks)
29
+ resolve(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength))
30
+ })
31
+ })
32
+ },
33
+ }
34
+ }
web/node_modules/hyparquet/src/plain.js ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Read `count` values of the given type from the reader.view.
3
+ *
4
+ * @param {DataReader} reader - buffer to read data from
5
+ * @param {ParquetType} type - parquet type of the data
6
+ * @param {number} count - number of values to read
7
+ * @param {number | undefined} fixedLength - length of each fixed length byte array
8
+ * @returns {DecodedArray} array of values
9
+ */
10
+ export function readPlain(reader, type, count, fixedLength) {
11
+ if (count === 0) return []
12
+ if (type === 'BOOLEAN') {
13
+ return readPlainBoolean(reader, count)
14
+ } else if (type === 'INT32') {
15
+ return readPlainInt32(reader, count)
16
+ } else if (type === 'INT64') {
17
+ return readPlainInt64(reader, count)
18
+ } else if (type === 'INT96') {
19
+ return readPlainInt96(reader, count)
20
+ } else if (type === 'FLOAT') {
21
+ return readPlainFloat(reader, count)
22
+ } else if (type === 'DOUBLE') {
23
+ return readPlainDouble(reader, count)
24
+ } else if (type === 'BYTE_ARRAY') {
25
+ return readPlainByteArray(reader, count)
26
+ } else if (type === 'FIXED_LEN_BYTE_ARRAY') {
27
+ if (!fixedLength) throw new Error('parquet missing fixed length')
28
+ return readPlainByteArrayFixed(reader, count, fixedLength)
29
+ } else {
30
+ throw new Error(`parquet unhandled type: ${type}`)
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Read `count` boolean values.
36
+ *
37
+ * @param {DataReader} reader
38
+ * @param {number} count
39
+ * @returns {boolean[]}
40
+ */
41
+ function readPlainBoolean(reader, count) {
42
+ const values = new Array(count)
43
+ for (let i = 0; i < count; i++) {
44
+ const byteOffset = reader.offset + (i / 8 | 0)
45
+ const bitOffset = i % 8
46
+ const byte = reader.view.getUint8(byteOffset)
47
+ values[i] = (byte & 1 << bitOffset) !== 0
48
+ }
49
+ reader.offset += Math.ceil(count / 8)
50
+ return values
51
+ }
52
+
53
+ /**
54
+ * Read `count` int32 values.
55
+ *
56
+ * @param {DataReader} reader
57
+ * @param {number} count
58
+ * @returns {Int32Array}
59
+ */
60
+ function readPlainInt32(reader, count) {
61
+ const values = (reader.view.byteOffset + reader.offset) % 4
62
+ ? new Int32Array(align(reader.view.buffer, reader.view.byteOffset + reader.offset, count * 4))
63
+ : new Int32Array(reader.view.buffer, reader.view.byteOffset + reader.offset, count)
64
+ reader.offset += count * 4
65
+ return values
66
+ }
67
+
68
+ /**
69
+ * Read `count` int64 values.
70
+ *
71
+ * @param {DataReader} reader
72
+ * @param {number} count
73
+ * @returns {BigInt64Array}
74
+ */
75
+ function readPlainInt64(reader, count) {
76
+ const values = (reader.view.byteOffset + reader.offset) % 8
77
+ ? new BigInt64Array(align(reader.view.buffer, reader.view.byteOffset + reader.offset, count * 8))
78
+ : new BigInt64Array(reader.view.buffer, reader.view.byteOffset + reader.offset, count)
79
+ reader.offset += count * 8
80
+ return values
81
+ }
82
+
83
+ /**
84
+ * Read `count` int96 values.
85
+ *
86
+ * @param {DataReader} reader
87
+ * @param {number} count
88
+ * @returns {bigint[]}
89
+ */
90
+ function readPlainInt96(reader, count) {
91
+ const values = new Array(count)
92
+ for (let i = 0; i < count; i++) {
93
+ const low = reader.view.getBigInt64(reader.offset + i * 12, true)
94
+ const high = reader.view.getInt32(reader.offset + i * 12 + 8, true)
95
+ values[i] = BigInt(high) << 64n | low
96
+ }
97
+ reader.offset += count * 12
98
+ return values
99
+ }
100
+
101
+ /**
102
+ * Read `count` float values.
103
+ *
104
+ * @param {DataReader} reader
105
+ * @param {number} count
106
+ * @returns {Float32Array}
107
+ */
108
+ function readPlainFloat(reader, count) {
109
+ const values = (reader.view.byteOffset + reader.offset) % 4
110
+ ? new Float32Array(align(reader.view.buffer, reader.view.byteOffset + reader.offset, count * 4))
111
+ : new Float32Array(reader.view.buffer, reader.view.byteOffset + reader.offset, count)
112
+ reader.offset += count * 4
113
+ return values
114
+ }
115
+
116
+ /**
117
+ * Read `count` double values.
118
+ *
119
+ * @param {DataReader} reader
120
+ * @param {number} count
121
+ * @returns {Float64Array}
122
+ */
123
+ function readPlainDouble(reader, count) {
124
+ const values = (reader.view.byteOffset + reader.offset) % 8
125
+ ? new Float64Array(align(reader.view.buffer, reader.view.byteOffset + reader.offset, count * 8))
126
+ : new Float64Array(reader.view.buffer, reader.view.byteOffset + reader.offset, count)
127
+ reader.offset += count * 8
128
+ return values
129
+ }
130
+
131
+ /**
132
+ * Read `count` byte array values.
133
+ *
134
+ * @param {DataReader} reader
135
+ * @param {number} count
136
+ * @returns {Uint8Array[]}
137
+ */
138
+ function readPlainByteArray(reader, count) {
139
+ const values = new Array(count)
140
+ for (let i = 0; i < count; i++) {
141
+ const length = reader.view.getUint32(reader.offset, true)
142
+ reader.offset += 4
143
+ values[i] = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length)
144
+ reader.offset += length
145
+ }
146
+ return values
147
+ }
148
+
149
+ /**
150
+ * Read a fixed length byte array.
151
+ *
152
+ * @param {DataReader} reader
153
+ * @param {number} count
154
+ * @param {number} fixedLength
155
+ * @returns {Uint8Array[]}
156
+ */
157
+ function readPlainByteArrayFixed(reader, count, fixedLength) {
158
+ // assert(reader.view.byteLength - reader.offset >= count * fixedLength)
159
+ const values = new Array(count)
160
+ for (let i = 0; i < count; i++) {
161
+ values[i] = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, fixedLength)
162
+ reader.offset += fixedLength
163
+ }
164
+ return values
165
+ }
166
+
167
+ /**
168
+ * Create a new buffer with the offset and size.
169
+ *
170
+ * @import {DataReader, DecodedArray, ParquetType} from '../src/types.js'
171
+ * @param {ArrayBufferLike} buffer
172
+ * @param {number} offset
173
+ * @param {number} size
174
+ * @returns {ArrayBuffer}
175
+ */
176
+ function align(buffer, offset, size) {
177
+ const aligned = new ArrayBuffer(size)
178
+ new Uint8Array(aligned).set(new Uint8Array(buffer, offset, size))
179
+ return aligned
180
+ }
web/node_modules/hyparquet/src/plan.js ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { bloomEligibleColumns, readBloomFilter } from './bloom.js'
2
+ import { canSkipRowGroup } from './filter.js'
3
+ import { parquetSchema } from './metadata.js'
4
+ import { getPhysicalColumns } from './schema.js'
5
+
6
+ /**
7
+ * @import {AsyncBuffer, BloomFilter, ByteRange, ChunkPlan, FileMetaData, GroupPlan, ParquetQueryFilter, ParquetReadOptions, QueryPlan, SchemaElement} from '../src/types.js'
8
+ */
9
+
10
+ // Combine column chunks if less than 2mb
11
+ const runLimit = 1 << 21 // 2mb
12
+
13
+ /**
14
+ * Plan which byte ranges to read to satisfy a read request.
15
+ * Metadata must be non-null.
16
+ *
17
+ * @param {ParquetReadOptions & { bloomFiltersByGroup?: Record<string, BloomFilter>[], schemaElements?: Record<string, SchemaElement> }} options
18
+ * @returns {QueryPlan}
19
+ */
20
+ export function parquetPlan({ metadata, rowStart = 0, rowEnd = Infinity, columns, filter, filterStrict = true, useOffsetIndex = false, bloomFiltersByGroup, schemaElements }) {
21
+ if (!metadata) throw new Error('parquetPlan requires metadata')
22
+ /** @type {GroupPlan[]} */
23
+ const groups = []
24
+ /** @type {ByteRange[]} */
25
+ const fetches = []
26
+ /** @type {ByteRange[]} */
27
+ const indexes = []
28
+ const physicalColumns = getPhysicalColumns(parquetSchema(metadata))
29
+
30
+ // find which row groups to read
31
+ let groupStart = 0 // first row index of the current group
32
+ let rgIdx = 0
33
+ for (const rowGroup of metadata.row_groups) {
34
+ const groupRows = Number(rowGroup.num_rows)
35
+ const groupEnd = groupStart + groupRows
36
+ const bloomFilters = bloomFiltersByGroup?.[rgIdx]
37
+ // if row group overlaps with row range, add it to the plan
38
+ if (groupRows > 0 && groupEnd > rowStart && groupStart < rowEnd && !canSkipRowGroup({ rowGroup, physicalColumns, filter, strict: filterStrict, bloomFilters, schemaElements })) {
39
+ /** @type {ChunkPlan[]} */
40
+ const chunks = []
41
+ let groupStartByte = Infinity
42
+ let groupEndByte = -Infinity
43
+ // loop through each column chunk
44
+ for (const chunk of rowGroup.columns) {
45
+ const meta = chunk.meta_data
46
+ if (chunk.file_path) throw new Error('parquet file_path not supported')
47
+ if (!meta) throw new Error('parquet column metadata is undefined')
48
+ // add included column chunks to the plan
49
+ if (!columns || columns.includes(meta.path_in_schema[0])) {
50
+ // full column chunk
51
+ const columnOffset = meta.dictionary_page_offset || meta.data_page_offset
52
+ const startByte = Number(columnOffset)
53
+ const endByte = Number(columnOffset + meta.total_compressed_size)
54
+ // update group byte range
55
+ if (startByte < groupStartByte) groupStartByte = startByte
56
+ if (endByte > groupEndByte) groupEndByte = endByte
57
+
58
+ if (useOffsetIndex && chunk.offset_index_offset && chunk.offset_index_length && (rowStart > groupStart || rowEnd < groupEnd)) {
59
+ const offsetIndexStart = Number(chunk.offset_index_offset)
60
+ chunks.push({
61
+ columnMetadata: meta,
62
+ offsetIndex: {
63
+ startByte: offsetIndexStart,
64
+ endByte: offsetIndexStart + chunk.offset_index_length,
65
+ },
66
+ range: { startByte, endByte },
67
+ })
68
+ } else {
69
+ chunks.push({
70
+ columnMetadata: meta,
71
+ range: { startByte, endByte },
72
+ })
73
+ }
74
+
75
+ }
76
+ }
77
+ const selectStart = Math.max(rowStart - groupStart, 0)
78
+ const selectEnd = Math.min(rowEnd - groupStart, groupRows)
79
+ groups.push({ chunks, rowGroup, groupStart, groupRows, selectStart, selectEnd })
80
+
81
+ // combine runs of column chunks
82
+ /** @type {ByteRange | undefined} */
83
+ let run
84
+ for (const chunk of chunks) {
85
+ if ('offsetIndex' in chunk) {
86
+ indexes.push(chunk.offsetIndex)
87
+ } else {
88
+ const { range } = chunk
89
+ if (columns) {
90
+ fetches.push(range)
91
+ } else if (run && range.endByte - run.startByte <= runLimit) {
92
+ // extend range
93
+ run.endByte = range.endByte
94
+ } else {
95
+ // new range
96
+ if (run) fetches.push(run)
97
+ run = { ...range }
98
+ }
99
+ }
100
+ }
101
+ if (run) fetches.push(run)
102
+ }
103
+
104
+ groupStart = groupEnd
105
+ rgIdx++
106
+ }
107
+ if (!isFinite(rowEnd)) rowEnd = groupStart
108
+ fetches.push(...indexes)
109
+
110
+ return { metadata, rowStart, rowEnd, columns, fetches, groups }
111
+ }
112
+
113
+ /**
114
+ * Fetch bloom filters for $eq / $in columns of row groups not already provably
115
+ * skippable by statistics alone. Returns an array indexed by row-group ordinal;
116
+ * each entry maps top-level column name → BloomFilter for any chunk whose
117
+ * bloom filter we were able to parse. Adds one round-trip when at least one
118
+ * bloom filter is fetched; otherwise returns synchronously.
119
+ *
120
+ * @param {object} options
121
+ * @param {AsyncBuffer} options.file
122
+ * @param {FileMetaData} options.metadata
123
+ * @param {ParquetQueryFilter} options.filter
124
+ * @param {boolean} [options.filterStrict]
125
+ * @returns {Promise<Record<string, BloomFilter>[]>}
126
+ */
127
+ export async function prefetchBloomFilters({ file, metadata, filter, filterStrict = true }) {
128
+ const result = metadata.row_groups.map(() => /** @type {Record<string, BloomFilter>} */ ({}))
129
+ const eligibleCols = bloomEligibleColumns(filter)
130
+ if (eligibleCols.size === 0) return result
131
+ const physicalColumns = getPhysicalColumns(parquetSchema(metadata))
132
+
133
+ /** @type {Promise<void>[]} */
134
+ const tasks = []
135
+ metadata.row_groups.forEach((rowGroup, rgIdx) => {
136
+ if (canSkipRowGroup({ rowGroup, physicalColumns, filter, strict: filterStrict })) return
137
+ for (const colName of eligibleCols) {
138
+ const columnIdx = physicalColumns.indexOf(colName)
139
+ if (columnIdx === -1) continue
140
+ const meta = rowGroup.columns[columnIdx]?.meta_data
141
+ if (!meta?.bloom_filter_offset || !meta.bloom_filter_length) continue
142
+ const start = Number(meta.bloom_filter_offset)
143
+ const end = start + meta.bloom_filter_length
144
+ tasks.push((async () => {
145
+ const buffer = await file.slice(start, end)
146
+ const bloom = readBloomFilter({ view: new DataView(buffer), offset: 0 })
147
+ if (bloom) result[rgIdx][colName] = bloom
148
+ })())
149
+ }
150
+ })
151
+
152
+ if (tasks.length) await Promise.all(tasks)
153
+ return result
154
+ }
155
+
156
+ /**
157
+ * Prefetch byte ranges from an AsyncBuffer.
158
+ *
159
+ * @param {AsyncBuffer} file
160
+ * @param {QueryPlan} plan
161
+ * @returns {AsyncBuffer}
162
+ */
163
+ export function prefetchAsyncBuffer(file, { fetches }) {
164
+ // fetch byte ranges from the file
165
+ const promises = fetches.map(({ startByte, endByte }) => file.slice(startByte, endByte))
166
+ return {
167
+ byteLength: file.byteLength,
168
+ slice(start, end = file.byteLength) {
169
+ // find matching slice
170
+ const index = fetches.findIndex(({ startByte, endByte }) => startByte <= start && end <= endByte)
171
+ if (index < 0) {
172
+ // fallback to direct read
173
+ return file.slice(start, end)
174
+ }
175
+ if (fetches[index].startByte !== start || fetches[index].endByte !== end) {
176
+ // slice a subrange of the prefetch
177
+ const startOffset = start - fetches[index].startByte
178
+ const endOffset = end - fetches[index].startByte
179
+ if (promises[index] instanceof Promise) {
180
+ return promises[index].then(buffer => buffer.slice(startOffset, endOffset))
181
+ } else {
182
+ return promises[index].slice(startOffset, endOffset)
183
+ }
184
+ } else {
185
+ return promises[index]
186
+ }
187
+ },
188
+ }
189
+ }
web/node_modules/hyparquet/src/query.js ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {BaseParquetReadOptions} from '../src/types.js'
3
+ */
4
+
5
+ import { parquetMetadataAsync, parquetSchema } from './metadata.js'
6
+ import { parquetReadColumn, parquetReadObjects } from './read.js'
7
+
8
+ /**
9
+ * Wraps parquetRead with orderBy support.
10
+ * This is a parquet-aware query engine that can read a subset of rows and columns.
11
+ * Accepts optional orderBy column name to sort the results.
12
+ * Note that using orderBy may SIGNIFICANTLY increase the query time.
13
+ *
14
+ * @param {BaseParquetReadOptions & { orderBy?: string }} options
15
+ * @returns {Promise<Record<string, any>[]>} resolves when all requested rows and columns are parsed
16
+ */
17
+ export async function parquetQuery(options) {
18
+ if (!options.file || !(options.file.byteLength >= 0)) {
19
+ throw new Error('parquet expected AsyncBuffer')
20
+ }
21
+ options.metadata ??= await parquetMetadataAsync(options.file, options)
22
+
23
+ const { metadata, rowStart = 0, columns, orderBy, filter } = options
24
+ if (rowStart < 0) throw new Error('parquet rowStart must be positive')
25
+ const rowEnd = options.rowEnd ?? Number(metadata.num_rows)
26
+
27
+ // Validate orderBy column exists
28
+ if (orderBy) {
29
+ const allColumns = parquetSchema(options.metadata).children.map(c => c.element.name)
30
+ if (!allColumns.includes(orderBy)) {
31
+ throw new Error(`parquet orderBy column not found: ${orderBy}`)
32
+ }
33
+ }
34
+
35
+ if (filter && !orderBy && rowEnd < metadata.num_rows) {
36
+ // iterate through row groups and filter until we have enough rows
37
+ /** @type {Record<string, any>[]} */
38
+ const filteredRows = []
39
+ let groupStart = 0
40
+ for (const group of metadata.row_groups) {
41
+ const groupEnd = groupStart + Number(group.num_rows)
42
+ // TODO: if expected > group size, start fetching next groups
43
+ const groupData = await parquetReadObjects({
44
+ ...options, rowStart: groupStart, rowEnd: groupEnd,
45
+ })
46
+ filteredRows.push(...groupData)
47
+ if (filteredRows.length >= rowEnd) break
48
+ groupStart = groupEnd
49
+ }
50
+ return filteredRows.slice(rowStart, rowEnd)
51
+ } else if (filter && orderBy) {
52
+ // read all rows with orderBy column included for sorting
53
+ const readColumns = columns && !columns.includes(orderBy)
54
+ ? [...columns, orderBy]
55
+ : columns
56
+
57
+ const results = await parquetReadObjects({
58
+ ...options, rowStart: undefined, rowEnd: undefined, columns: readColumns,
59
+ })
60
+
61
+ // sort by orderBy column
62
+ results.sort((a, b) => compare(a[orderBy], b[orderBy]))
63
+
64
+ // project out orderBy column if not originally requested
65
+ if (readColumns !== columns) {
66
+ for (const row of results) {
67
+ delete row[orderBy]
68
+ }
69
+ }
70
+
71
+ return results.slice(rowStart, rowEnd)
72
+ } else if (filter) {
73
+ // filter without orderBy, read all matching rows
74
+ const results = await parquetReadObjects({
75
+ ...options, rowStart: undefined, rowEnd: undefined,
76
+ })
77
+ return results.slice(rowStart, rowEnd)
78
+ } else if (typeof orderBy === 'string') {
79
+ // sorted but unfiltered: fetch orderBy column first
80
+ const orderColumn = await parquetReadColumn({
81
+ ...options, rowStart: undefined, rowEnd: undefined, columns: [orderBy],
82
+ })
83
+
84
+ // compute row groups to fetch
85
+ const sortedIndices = Array.from(orderColumn, (_, index) => index)
86
+ .sort((a, b) => compare(orderColumn[a], orderColumn[b]))
87
+ .slice(rowStart, rowEnd)
88
+
89
+ const sparseData = await parquetReadRows({ ...options, rows: sortedIndices })
90
+ // warning: the type Record<string, any> & {__index__: number})[] is simplified into Record<string, any>[]
91
+ // when returning. The data contains the __index__ property, but it's not exposed as such.
92
+ const data = sortedIndices.map(index => sparseData[index])
93
+ return data
94
+ } else {
95
+ return await parquetReadObjects(options)
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Reads a list rows from a parquet file, reading only the row groups that contain the rows.
101
+ * Returns a sparse array of rows.
102
+ * @param {BaseParquetReadOptions & { rows: number[] }} options
103
+ * @returns {Promise<(Record<string, any> & {__index__: number})[]>}
104
+ */
105
+ async function parquetReadRows(options) {
106
+ const { file, rows } = options
107
+ options.metadata ??= await parquetMetadataAsync(file, options)
108
+ const { row_groups: rowGroups } = options.metadata
109
+ // Compute row groups to fetch
110
+ const groupIncluded = Array(rowGroups.length).fill(false)
111
+ let groupStart = 0
112
+ const groupEnds = rowGroups.map(group => groupStart += Number(group.num_rows))
113
+ for (const index of rows) {
114
+ const groupIndex = groupEnds.findIndex(end => index < end)
115
+ groupIncluded[groupIndex] = true
116
+ }
117
+
118
+ // Compute row ranges to fetch
119
+ const rowRanges = []
120
+ let rangeStart
121
+ groupStart = 0
122
+ for (let i = 0; i < groupIncluded.length; i++) {
123
+ const groupEnd = groupStart + Number(rowGroups[i].num_rows)
124
+ if (groupIncluded[i]) {
125
+ if (rangeStart === undefined) {
126
+ rangeStart = groupStart
127
+ }
128
+ } else {
129
+ if (rangeStart !== undefined) {
130
+ rowRanges.push([rangeStart, groupEnd])
131
+ rangeStart = undefined
132
+ }
133
+ }
134
+ groupStart = groupEnd
135
+ }
136
+ if (rangeStart !== undefined) {
137
+ rowRanges.push([rangeStart, groupStart])
138
+ }
139
+
140
+ // Fetch by row group and map to rows
141
+ /** @type {(Record<string, any> & {__index__: number})[]} */
142
+ const sparseData = Array(Number(options.metadata.num_rows))
143
+ for (const [rangeStart, rangeEnd] of rowRanges) {
144
+ // TODO: fetch in parallel
145
+ const groupData = await parquetReadObjects({ ...options, rowStart: rangeStart, rowEnd: rangeEnd })
146
+ for (let i = rangeStart; i < rangeEnd; i++) {
147
+ // warning: if the row contains a column named __index__, it will overwrite the index.
148
+ sparseData[i] = { __index__: i, ...groupData[i - rangeStart] }
149
+ }
150
+ }
151
+ return sparseData
152
+ }
153
+
154
+ /**
155
+ * @param {any} a
156
+ * @param {any} b
157
+ * @returns {number}
158
+ */
159
+ function compare(a, b) {
160
+ if (a < b) return -1
161
+ if (a > b) return 1
162
+ return 0 // TODO: null handling
163
+ }
web/node_modules/hyparquet/src/read.js ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {AsyncRowGroup, BaseParquetReadOptions, DecodedArray, ParquetReadOptions, SchemaElement} from '../src/types.js'
3
+ */
4
+
5
+ import { columnsNeededForFilter, matchFilter } from './filter.js'
6
+ import { parquetMetadataAsync, parquetSchema } from './metadata.js'
7
+ import { parquetPlan, prefetchAsyncBuffer, prefetchBloomFilters } from './plan.js'
8
+ import { assembleAsync, asyncGroupToRows, readRowGroup } from './rowgroup.js'
9
+ import { concat } from './utils.js'
10
+
11
+ /**
12
+ * Read parquet data rows from a file-like object.
13
+ * Reads the minimal number of row groups and columns to satisfy the request.
14
+ *
15
+ * Returns a void promise when complete.
16
+ * Errors are thrown on the returned promise.
17
+ * Data is returned in callbacks onComplete, onChunk, onPage, NOT the return promise.
18
+ * See parquetReadObjects for a more convenient API.
19
+ *
20
+ * @param {ParquetReadOptions} options read options
21
+ * @returns {Promise<void>} resolves when all requested rows and columns are parsed, all errors are thrown here
22
+ */
23
+ export async function parquetRead(options) {
24
+ // load metadata if not provided
25
+ options.metadata ??= await parquetMetadataAsync(options.file, options)
26
+
27
+ const { rowStart = 0, rowEnd, columns, onChunk, onComplete, rowFormat, filter, filterStrict = true } = options
28
+
29
+ // Filter requires object format to match column names
30
+ if (filter && rowFormat !== 'object') {
31
+ throw new Error('parquet filter requires rowFormat: "object"')
32
+ }
33
+
34
+ // Include filter columns in the read plan
35
+ const filterColumns = columnsNeededForFilter(filter)
36
+ if (filterColumns.length) {
37
+ const schemaColumns = parquetSchema(options.metadata).children.map(c => c.element.name)
38
+ const missingColumns = filterColumns.filter(c => !schemaColumns.includes(c))
39
+ if (missingColumns.length) {
40
+ throw new Error(`parquet filter columns not found: ${missingColumns.join(', ')}`)
41
+ }
42
+ }
43
+ let readColumns = columns
44
+ let requiresProjection = false
45
+ if (columns && filter) {
46
+ const missingFilterColumns = filterColumns.filter(c => !columns.includes(c))
47
+ if (missingFilterColumns.length) {
48
+ readColumns = [...columns, ...missingFilterColumns]
49
+ requiresProjection = true
50
+ }
51
+ }
52
+
53
+ // read row groups with expanded columns
54
+ let readOptions = readColumns !== columns ? { ...options, columns: readColumns } : options
55
+ readOptions = await withBloomFilters(readOptions)
56
+ const asyncGroups = parquetReadAsync(readOptions)
57
+
58
+ // skip assembly if no onComplete or onChunk, but wait for reading to finish
59
+ if (!onComplete && !onChunk) {
60
+ await awaitAllColumns(asyncGroups)
61
+ return
62
+ }
63
+
64
+ // assemble struct columns
65
+ const schemaTree = parquetSchema(options.metadata)
66
+ const assembled = asyncGroups.map(arg => assembleAsync(arg, schemaTree, options.parsers))
67
+
68
+ // onChunk emit all chunks (don't await). Rejection is surfaced by awaitAllColumns below.
69
+ if (onChunk) {
70
+ for (const asyncGroup of assembled) {
71
+ for (const asyncColumn of asyncGroup.asyncColumns) {
72
+ asyncColumn.data.then(({ data, skipped }) => {
73
+ let rowStart = asyncGroup.groupStart + skipped
74
+ for (const columnData of data) {
75
+ onChunk({
76
+ columnName: asyncColumn.pathInSchema[0],
77
+ columnData,
78
+ rowStart,
79
+ rowEnd: rowStart + columnData.length,
80
+ })
81
+ rowStart += columnData.length
82
+ }
83
+ }, () => {})
84
+ }
85
+ }
86
+ }
87
+
88
+ // onComplete transpose column chunks to rows
89
+ if (onComplete) {
90
+ // wait for all reads to settle so a sibling rejection cannot leak
91
+ await awaitAllColumns(assembled)
92
+ // loosen the types to avoid duplicate code
93
+ /** @type {any[]} */
94
+ const rows = []
95
+ for (const asyncGroup of assembled) {
96
+ // filter to rows in range
97
+ const selectStart = Math.max(rowStart - asyncGroup.groupStart, 0)
98
+ const selectEnd = Math.min((rowEnd ?? Infinity) - asyncGroup.groupStart, asyncGroup.groupRows)
99
+ // transpose column chunks to rows in output
100
+ const groupData = rowFormat === 'object' ?
101
+ await asyncGroupToRows(asyncGroup, selectStart, selectEnd, readColumns, 'object') :
102
+ await asyncGroupToRows(asyncGroup, selectStart, selectEnd, columns, 'array')
103
+
104
+ // Apply filter and projection
105
+ if (filter) {
106
+ // eslint-disable-next-line no-extra-parens
107
+ for (const row of /** @type {Record<string, any>[]} */ (groupData)) {
108
+ if (matchFilter(row, filter, filterStrict)) {
109
+ if (requiresProjection && columns) {
110
+ for (const col of filterColumns) {
111
+ if (!columns.includes(col)) delete row[col]
112
+ }
113
+ }
114
+ rows.push(row)
115
+ }
116
+ }
117
+ } else {
118
+ concat(rows, groupData)
119
+ }
120
+ }
121
+ onComplete(rows)
122
+ } else {
123
+ // wait for all async groups to finish (complete takes care of this)
124
+ await awaitAllColumns(assembled)
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Await every column promise across the given row groups via Promise.allSettled
130
+ * so no rejection escapes as an unhandledRejection. Throws the first rejection.
131
+ *
132
+ * @param {AsyncRowGroup[]} asyncGroups
133
+ * @returns {Promise<void>}
134
+ */
135
+ async function awaitAllColumns(asyncGroups) {
136
+ const all = asyncGroups.flatMap(g => g.asyncColumns.map(c => c.data))
137
+ const results = await Promise.allSettled(all)
138
+ const failed = results.find(r => r.status === 'rejected')
139
+ if (failed) throw failed.reason
140
+ }
141
+
142
+ /**
143
+ * @param {ParquetReadOptions} options read options
144
+ * @returns {AsyncRowGroup[]}
145
+ */
146
+ export function parquetReadAsync(options) {
147
+ if (!options.metadata) throw new Error('parquet requires metadata')
148
+ // TODO: validate options (start, end, columns, etc)
149
+
150
+ // prefetch byte ranges
151
+ const plan = parquetPlan(options)
152
+ options.file = prefetchAsyncBuffer(options.file, plan)
153
+
154
+ // read row groups
155
+ return plan.groups.map(groupPlan => readRowGroup(options, plan, groupPlan))
156
+ }
157
+
158
+ /**
159
+ * Reads a single column from a parquet file.
160
+ *
161
+ * @param {BaseParquetReadOptions} options
162
+ * @returns {Promise<DecodedArray>}
163
+ */
164
+ export async function parquetReadColumn(options) {
165
+ if (options.columns?.length !== 1) {
166
+ throw new Error('parquetReadColumn expected columns: [columnName]')
167
+ }
168
+ options.metadata ??= await parquetMetadataAsync(options.file, options)
169
+ const asyncGroups = parquetReadAsync(await withBloomFilters(options))
170
+
171
+ // assemble struct columns
172
+ const schemaTree = parquetSchema(options.metadata)
173
+ const assembled = asyncGroups.map(arg => assembleAsync(arg, schemaTree, options.parsers))
174
+
175
+ // wait for all reads to settle so a sibling rejection cannot leak
176
+ await awaitAllColumns(assembled)
177
+
178
+ /** @type {DecodedArray} */
179
+ const columnData = []
180
+ for (const rg of assembled) {
181
+ const { data } = await rg.asyncColumns[0].data
182
+ for (const chunk of data) {
183
+ concat(columnData, chunk)
184
+ }
185
+ }
186
+ return columnData
187
+ }
188
+
189
+ /**
190
+ * Conditionally fetch bloom filters and attach them (and the per-column schema
191
+ * elements they require) to options so parquetPlan can use them for row-group
192
+ * pruning. Returns options unchanged when there's no filter or the user has
193
+ * disabled bloom pushdown.
194
+ *
195
+ * @param {BaseParquetReadOptions} options
196
+ * @returns {Promise<BaseParquetReadOptions>}
197
+ */
198
+ async function withBloomFilters(options) {
199
+ if (!options.useBloomFilters) return options
200
+ if (!options.filter || !options.metadata) return options
201
+ const schemaTree = parquetSchema(options.metadata)
202
+ /** @type {Record<string, SchemaElement>} */
203
+ const schemaElements = {}
204
+ for (const child of schemaTree.children) schemaElements[child.element.name] = child.element
205
+ const bloomFiltersByGroup = await prefetchBloomFilters({
206
+ file: options.file,
207
+ metadata: options.metadata,
208
+ filter: options.filter,
209
+ filterStrict: options.filterStrict,
210
+ })
211
+ // eslint-disable-next-line no-extra-parens
212
+ return /** @type {BaseParquetReadOptions} */ ({ ...options, bloomFiltersByGroup, schemaElements })
213
+ }
214
+
215
+ /**
216
+ * This is a helper function to read parquet row data as a promise.
217
+ * It is a wrapper around the more configurable parquetRead function.
218
+ *
219
+ * @param {Omit<ParquetReadOptions, 'onComplete'>} options
220
+ * @returns {Promise<Record<string, any>[]>} resolves when all requested rows and columns are parsed
221
+ */
222
+ export function parquetReadObjects(options) {
223
+ return new Promise((onComplete, reject) => {
224
+ parquetRead({
225
+ ...options,
226
+ rowFormat: 'object', // force object output
227
+ onComplete,
228
+ }).catch(reject)
229
+ })
230
+ }
web/node_modules/hyparquet/src/rowgroup.js ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {AsyncColumn, AsyncRowGroup, DecodedArray, GroupPlan, ParquetParsers, ParquetReadOptions, QueryPlan, SchemaTree} from '../src/types.js'
3
+ */
4
+
5
+ import { assembleNested } from './assemble.js'
6
+ import { readColumn } from './column.js'
7
+ import { DEFAULT_PARSERS } from './convert.js'
8
+ import { readOffsetIndex } from './indexes.js'
9
+ import { getSchemaPath } from './schema.js'
10
+ import { flatten } from './utils.js'
11
+
12
+ /**
13
+ * Read a row group from a file-like object.
14
+ *
15
+ * @param {ParquetReadOptions} options
16
+ * @param {QueryPlan} plan
17
+ * @param {GroupPlan} groupPlan
18
+ * @returns {AsyncRowGroup} resolves to column data
19
+ */
20
+ export function readRowGroup(options, { metadata }, groupPlan) {
21
+ /** @type {AsyncColumn[]} */
22
+ const asyncColumns = []
23
+
24
+ // read column data
25
+ for (const chunk of groupPlan.chunks) {
26
+ const { data_page_offset, dictionary_page_offset, path_in_schema: pathInSchema } = chunk.columnMetadata
27
+ const schemaPath = getSchemaPath(metadata.schema, pathInSchema)
28
+ const columnDecoder = {
29
+ pathInSchema,
30
+ element: schemaPath[schemaPath.length - 1].element,
31
+ schemaPath,
32
+ parsers: { ...DEFAULT_PARSERS, ...options.parsers },
33
+ ...options,
34
+ ...chunk.columnMetadata,
35
+ }
36
+ let { startByte, endByte } = chunk.range
37
+
38
+ // non-offset-index case
39
+ if (!('offsetIndex' in chunk)) {
40
+ asyncColumns.push({
41
+ pathInSchema,
42
+ data: Promise.resolve(options.file.slice(startByte, endByte))
43
+ .then(buffer => {
44
+ const reader = { view: new DataView(buffer), offset: 0 }
45
+ return readColumn(reader, groupPlan, columnDecoder, options.onPage)
46
+ }),
47
+ })
48
+ continue
49
+ }
50
+
51
+ // offset-index case
52
+ asyncColumns.push({
53
+ pathInSchema,
54
+ // fetch offset index
55
+ data: Promise.resolve(options.file.slice(chunk.offsetIndex.startByte, chunk.offsetIndex.endByte))
56
+ .then(async arrayBuffer => {
57
+ // use offset index to read only necessary pages
58
+ const { selectStart, selectEnd } = groupPlan
59
+ const pages = readOffsetIndex({ view: new DataView(arrayBuffer), offset: 0 }).page_locations
60
+ let skipped = -1
61
+ // include dictionary if present, handle polars missing dictionary_page_offset
62
+ const hasDict = dictionary_page_offset || data_page_offset < pages[0].offset
63
+ for (let i = 0; i < pages.length; i++) {
64
+ const page = pages[i]
65
+ const pageStart = Number(page.first_row_index)
66
+ const pageEnd = i + 1 < pages.length
67
+ ? Number(pages[i + 1].first_row_index)
68
+ : groupPlan.groupRows // last page extends to end of row group
69
+ // check if page overlaps with [selectStart, selectEnd)
70
+ if (skipped < 0 && !hasDict && pageEnd > selectStart) {
71
+ startByte = Number(page.offset)
72
+ skipped = pageStart
73
+ }
74
+ if (pageStart < selectEnd) {
75
+ endByte = Number(page.offset) + page.compressed_page_size
76
+ }
77
+ }
78
+ if (skipped < 0) skipped = 0
79
+ const buffer = await options.file.slice(startByte, endByte)
80
+ const reader = { view: new DataView(buffer), offset: 0 }
81
+ // adjust row selection for skipped pages
82
+ const adjustedGroupPlan = skipped ? {
83
+ ...groupPlan,
84
+ groupStart: groupPlan.groupStart + skipped,
85
+ selectStart: groupPlan.selectStart - skipped,
86
+ selectEnd: groupPlan.selectEnd - skipped,
87
+ } : groupPlan
88
+ const { data, skipped: columnSkipped } = readColumn(reader, adjustedGroupPlan, columnDecoder, options.onPage)
89
+ return {
90
+ data,
91
+ skipped: skipped + columnSkipped,
92
+ }
93
+ }),
94
+ })
95
+ }
96
+
97
+ return { groupStart: groupPlan.groupStart, groupRows: groupPlan.groupRows, asyncColumns }
98
+ }
99
+
100
+ /**
101
+ * @overload
102
+ * @param {AsyncRowGroup} asyncGroup
103
+ * @param {number} selectStart
104
+ * @param {number} selectEnd
105
+ * @param {string[] | undefined} columns
106
+ * @param {'object'} rowFormat
107
+ * @returns {Promise<Record<string, any>[]>} resolves to row data
108
+ */
109
+ /**
110
+ * @overload
111
+ * @param {AsyncRowGroup} asyncGroup
112
+ * @param {number} selectStart
113
+ * @param {number} selectEnd
114
+ * @param {string[] | undefined} columns
115
+ * @param {'array'} [rowFormat]
116
+ * @returns {Promise<any[][]>} resolves to row data
117
+ */
118
+ /**
119
+ * @param {AsyncRowGroup} asyncGroup
120
+ * @param {number} selectStart
121
+ * @param {number} selectEnd
122
+ * @param {string[] | undefined} columns
123
+ * @param {'object' | 'array'} [rowFormat]
124
+ * @returns {Promise<Record<string, any>[] | any[][]>} resolves to row data
125
+ */
126
+ export async function asyncGroupToRows({ asyncColumns }, selectStart, selectEnd, columns, rowFormat) {
127
+ // TODO: do it without flatten
128
+ const asyncPages = await Promise.all(asyncColumns.map(column =>
129
+ column.data.then(({ skipped, data }) => ({ skipped, data: flatten(data) }))
130
+ ))
131
+
132
+ // transpose columns into rows
133
+ const selectCount = selectEnd - selectStart
134
+ if (rowFormat === 'object') {
135
+ /** @type {Record<string, any>[]} */
136
+ const groupData = Array(selectCount)
137
+ for (let selectRow = 0; selectRow < selectCount; selectRow++) {
138
+ // return each row as an object
139
+ /** @type {Record<string, any>} */
140
+ const rowData = {}
141
+ for (let i = 0; i < asyncColumns.length; i++) {
142
+ const { data, skipped } = asyncPages[i]
143
+ rowData[asyncColumns[i].pathInSchema[0]] = data[selectStart + selectRow - skipped]
144
+ }
145
+ groupData[selectRow] = rowData
146
+ }
147
+ return groupData
148
+ }
149
+
150
+ // careful mapping of column order for rowFormat: array
151
+ const includedColumnNames = asyncColumns
152
+ .map(child => child.pathInSchema[0])
153
+ .filter(name => !columns || columns.includes(name))
154
+ const columnOrder = columns ?? includedColumnNames
155
+ const columnIndexes = columnOrder.map(name => asyncColumns.findIndex(column => column.pathInSchema[0] === name))
156
+
157
+ /** @type {any[][]} */
158
+ const groupData = Array(selectCount)
159
+ for (let selectRow = 0; selectRow < selectCount; selectRow++) {
160
+ // return each row as an array
161
+ const rowData = Array(asyncColumns.length)
162
+ for (let i = 0; i < columnOrder.length; i++) {
163
+ const colIdx = columnIndexes[i]
164
+ if (colIdx < 0) throw new Error(`parquet column not found: ${columnOrder[i]}`)
165
+ const { data, skipped } = asyncPages[colIdx]
166
+ rowData[i] = data[selectStart + selectRow - skipped]
167
+ }
168
+ groupData[selectRow] = rowData
169
+ }
170
+ return groupData
171
+ }
172
+
173
+ /**
174
+ * Assemble physical columns into top-level columns asynchronously.
175
+ *
176
+ * @param {AsyncRowGroup} asyncRowGroup
177
+ * @param {SchemaTree} schemaTree
178
+ * @param {ParquetParsers} [parsers]
179
+ * @returns {AsyncRowGroup}
180
+ */
181
+ export function assembleAsync(asyncRowGroup, schemaTree, parsers) {
182
+ const { asyncColumns } = asyncRowGroup
183
+ parsers = { ...DEFAULT_PARSERS, ...parsers }
184
+ /** @type {AsyncColumn[]} */
185
+ const assembled = []
186
+ for (const child of schemaTree.children) {
187
+ if (child.children.length) {
188
+ const childColumns = asyncColumns.filter(column => column.pathInSchema[0] === child.element.name)
189
+ if (!childColumns.length) continue
190
+
191
+ assembled.push({
192
+ pathInSchema: child.path,
193
+ data: (async () => {
194
+ // collect subcolumn data — Promise.all observes every rejection so
195
+ // a sibling failure cannot leak as an unhandledRejection
196
+ const resolved = await Promise.all(childColumns.map(c => c.data))
197
+ /** @type {Map<string, DecodedArray>} */
198
+ const subcolumnData = new Map()
199
+ let minLength = Infinity
200
+ for (let i = 0; i < childColumns.length; i++) {
201
+ const flat = flatten(resolved[i].data)
202
+ subcolumnData.set(childColumns[i].pathInSchema.join('.'), flat)
203
+ minLength = Math.min(minLength, flat.length)
204
+ }
205
+ // trim sub-columns to same length (offset index may read different pages per column)
206
+ for (const [key, value] of subcolumnData) {
207
+ if (value.length > minLength) {
208
+ subcolumnData.set(key, value.slice(0, minLength))
209
+ }
210
+ }
211
+ // assemble the column
212
+ assembleNested(subcolumnData, child, parsers)
213
+ const assembled = subcolumnData.get(child.element.name)
214
+ if (!assembled) throw new Error('parquet column data not assembled')
215
+ return { data: [assembled], skipped: 0 }
216
+ })(),
217
+ })
218
+ } else {
219
+ // leaf node, return the column
220
+ const asyncColumn = asyncColumns.find(column => column.pathInSchema[0] === child.element.name)
221
+ if (asyncColumn) assembled.push(asyncColumn)
222
+ }
223
+ }
224
+ return { ...asyncRowGroup, asyncColumns: assembled }
225
+ }
web/node_modules/hyparquet/src/schema.js ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Build a tree from the schema elements.
3
+ *
4
+ * @param {SchemaElement[]} schema
5
+ * @param {number} rootIndex index of the root element
6
+ * @param {string[]} path path to the element
7
+ * @returns {SchemaTree} tree of schema elements
8
+ */
9
+ function schemaTree(schema, rootIndex, path) {
10
+ const element = schema[rootIndex]
11
+ const children = []
12
+ let count = 1
13
+
14
+ // Read the specified number of children
15
+ if (element.num_children) {
16
+ while (children.length < element.num_children) {
17
+ const childElement = schema[rootIndex + count]
18
+ const child = schemaTree(schema, rootIndex + count, [...path, childElement.name])
19
+ count += child.count
20
+ children.push(child)
21
+ }
22
+ }
23
+
24
+ return { count, element, children, path }
25
+ }
26
+
27
+ /**
28
+ * Get schema elements from the root to the given element name.
29
+ *
30
+ * @param {SchemaElement[]} schema
31
+ * @param {string[]} name path to the element
32
+ * @returns {SchemaTree[]} list of schema elements
33
+ */
34
+ export function getSchemaPath(schema, name) {
35
+ let tree = schemaTree(schema, 0, [])
36
+ const path = [tree]
37
+ for (const part of name) {
38
+ const child = tree.children.find(child => child.element.name === part)
39
+ if (!child) throw new Error(`parquet schema element not found: ${name}`)
40
+ path.push(child)
41
+ tree = child
42
+ }
43
+ return path
44
+ }
45
+
46
+ /**
47
+ * Get all physical (leaf) column names.
48
+ *
49
+ * @param {SchemaTree} schemaTree
50
+ * @returns {string[]} list of physical column names
51
+ */
52
+ export function getPhysicalColumns(schemaTree) {
53
+ /** @type {string[]} */
54
+ const columns = []
55
+ /** @param {SchemaTree} node */
56
+ function traverse(node) {
57
+ if (node.children.length) {
58
+ for (const child of node.children) {
59
+ traverse(child)
60
+ }
61
+ } else {
62
+ columns.push(node.path.join('.'))
63
+ }
64
+ }
65
+ traverse(schemaTree)
66
+ return columns
67
+ }
68
+
69
+ /**
70
+ * Get the max repetition level for a given schema path.
71
+ *
72
+ * @param {SchemaTree[]} schemaPath
73
+ * @returns {number} max repetition level
74
+ */
75
+ export function getMaxRepetitionLevel(schemaPath) {
76
+ let maxLevel = 0
77
+ for (const { element } of schemaPath) {
78
+ if (element.repetition_type === 'REPEATED') {
79
+ maxLevel++
80
+ }
81
+ }
82
+ return maxLevel
83
+ }
84
+
85
+ /**
86
+ * Get the max definition level for a given schema path.
87
+ *
88
+ * @param {SchemaTree[]} schemaPath
89
+ * @returns {number} max definition level
90
+ */
91
+ export function getMaxDefinitionLevel(schemaPath) {
92
+ let maxLevel = 0
93
+ for (const { element } of schemaPath.slice(1)) {
94
+ if (element.repetition_type !== 'REQUIRED') {
95
+ maxLevel++
96
+ }
97
+ }
98
+ return maxLevel
99
+ }
100
+
101
+ /**
102
+ * Check if a column is list-like.
103
+ *
104
+ * @param {SchemaTree} schema
105
+ * @returns {boolean} true if list-like
106
+ */
107
+ export function isListLike(schema) {
108
+ if (!schema) return false
109
+ if (schema.element.converted_type !== 'LIST') return false
110
+ if (schema.children.length > 1) return false
111
+
112
+ const firstChild = schema.children[0]
113
+ if (firstChild.children.length > 1) return false
114
+ if (firstChild.element.repetition_type !== 'REPEATED') return false
115
+
116
+ return true
117
+ }
118
+
119
+ /**
120
+ * Check if a column is map-like.
121
+ *
122
+ * @param {SchemaTree} schema
123
+ * @returns {boolean} true if map-like
124
+ */
125
+ export function isMapLike(schema) {
126
+ if (!schema) return false
127
+ if (schema.element.converted_type !== 'MAP') return false
128
+ if (schema.children.length > 1) return false
129
+
130
+ const firstChild = schema.children[0]
131
+ if (firstChild.children.length !== 2) return false
132
+ if (firstChild.element.repetition_type !== 'REPEATED') return false
133
+
134
+ const keyChild = firstChild.children.find(child => child.element.name === 'key')
135
+ if (keyChild?.element.repetition_type === 'REPEATED') return false
136
+
137
+ const valueChild = firstChild.children.find(child => child.element.name === 'value')
138
+ if (valueChild?.element.repetition_type === 'REPEATED') return false
139
+
140
+ return true
141
+ }
142
+
143
+ /**
144
+ * Returns true if a column is non-nested.
145
+ *
146
+ * @param {SchemaTree[]} schemaPath
147
+ * @returns {boolean}
148
+ */
149
+ export function isFlatColumn(schemaPath) {
150
+ if (schemaPath.length !== 2) return false
151
+ const [, column] = schemaPath
152
+ if (column.element.repetition_type === 'REPEATED') return false
153
+ if (column.children.length) return false
154
+ return true
155
+ }
156
+
157
+ /**
158
+ * @import {SchemaElement, SchemaTree} from '../src/types.js'
159
+ */
web/node_modules/hyparquet/src/snappy.js ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * The MIT License (MIT)
3
+ * Copyright (c) 2016 Zhipeng Jia
4
+ * https://github.com/zhipeng-jia/snappyjs
5
+ */
6
+
7
+ const WORD_MASK = [0, 0xff, 0xffff, 0xffffff, 0xffffffff]
8
+
9
+ /**
10
+ * Copy bytes from one array to another
11
+ *
12
+ * @param {Uint8Array} fromArray source array
13
+ * @param {number} fromPos source position
14
+ * @param {Uint8Array} toArray destination array
15
+ * @param {number} toPos destination position
16
+ * @param {number} length number of bytes to copy
17
+ */
18
+ function copyBytes(fromArray, fromPos, toArray, toPos, length) {
19
+ for (let i = 0; i < length; i++) {
20
+ toArray[toPos + i] = fromArray[fromPos + i]
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Decompress snappy data.
26
+ * Accepts an output buffer to avoid allocating a new buffer for each call.
27
+ *
28
+ * @param {Uint8Array} input compressed data
29
+ * @param {Uint8Array} output output buffer
30
+ */
31
+ export function snappyUncompress(input, output) {
32
+ const inputLength = input.byteLength
33
+ const outputLength = output.byteLength
34
+ let pos = 0
35
+ let outPos = 0
36
+
37
+ // skip preamble (contains uncompressed length as varint)
38
+ while (pos < inputLength) {
39
+ const c = input[pos]
40
+ pos++
41
+ if (c < 128) {
42
+ break
43
+ }
44
+ }
45
+ if (outputLength && pos >= inputLength) {
46
+ throw new Error('invalid snappy length header')
47
+ }
48
+
49
+ while (pos < inputLength) {
50
+ const c = input[pos]
51
+ let len = 0
52
+ pos++
53
+
54
+ if (pos >= inputLength) {
55
+ throw new Error('missing eof marker')
56
+ }
57
+
58
+ // There are two types of elements, literals and copies (back references)
59
+ if ((c & 0x3) === 0) {
60
+ // Literals are uncompressed data stored directly in the byte stream
61
+ let len = (c >>> 2) + 1
62
+ // Longer literal length is encoded in multiple bytes
63
+ if (len > 60) {
64
+ if (pos + 3 >= inputLength) {
65
+ throw new Error('snappy error literal pos + 3 >= inputLength')
66
+ }
67
+ const lengthSize = len - 60 // length bytes - 1
68
+ len = input[pos]
69
+ + (input[pos + 1] << 8)
70
+ + (input[pos + 2] << 16)
71
+ + (input[pos + 3] << 24)
72
+ len = (len & WORD_MASK[lengthSize]) + 1
73
+ pos += lengthSize
74
+ }
75
+ if (pos + len > inputLength) {
76
+ throw new Error('snappy error literal exceeds input length')
77
+ }
78
+ copyBytes(input, pos, output, outPos, len)
79
+ pos += len
80
+ outPos += len
81
+ } else {
82
+ // Copy elements
83
+ let offset = 0 // offset back from current position to read
84
+ switch (c & 0x3) {
85
+ case 1:
86
+ // Copy with 1-byte offset
87
+ len = (c >>> 2 & 0x7) + 4
88
+ offset = input[pos] + (c >>> 5 << 8)
89
+ pos++
90
+ break
91
+ case 2:
92
+ // Copy with 2-byte offset
93
+ if (inputLength <= pos + 1) {
94
+ throw new Error('snappy error end of input')
95
+ }
96
+ len = (c >>> 2) + 1
97
+ offset = input[pos] + (input[pos + 1] << 8)
98
+ pos += 2
99
+ break
100
+ case 3:
101
+ // Copy with 4-byte offset
102
+ if (inputLength <= pos + 3) {
103
+ throw new Error('snappy error end of input')
104
+ }
105
+ len = (c >>> 2) + 1
106
+ offset = input[pos]
107
+ + (input[pos + 1] << 8)
108
+ + (input[pos + 2] << 16)
109
+ + (input[pos + 3] << 24)
110
+ pos += 4
111
+ break
112
+ default:
113
+ break
114
+ }
115
+ if (offset === 0 || isNaN(offset)) {
116
+ throw new Error(`invalid offset ${offset} pos ${pos} inputLength ${inputLength}`)
117
+ }
118
+ if (offset > outPos) {
119
+ throw new Error('cannot copy from before start of buffer')
120
+ }
121
+ copyBytes(output, outPos - offset, output, outPos, len)
122
+ outPos += len
123
+ }
124
+ }
125
+
126
+ if (outPos !== outputLength) throw new Error('premature end of input')
127
+ }
web/node_modules/hyparquet/src/thrift.js ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {DataReader, ThriftObject, ThriftType} from '../src/types.js'
3
+ */
4
+
5
+ // TCompactProtocol types
6
+ const STOP = 0
7
+ const TRUE = 1
8
+ const FALSE = 2
9
+ const BYTE = 3
10
+ const I16 = 4
11
+ const I32 = 5
12
+ const I64 = 6
13
+ const DOUBLE = 7
14
+ const BINARY = 8
15
+ const LIST = 9
16
+ const STRUCT = 12
17
+
18
+ /**
19
+ * Parse TCompactProtocol
20
+ *
21
+ * @param {DataReader} reader
22
+ * @returns {{ [key: `field_${number}`]: any }}
23
+ */
24
+ export function deserializeTCompactProtocol(reader) {
25
+ /** @type {ThriftObject} */
26
+ const value = {}
27
+ let fid = 0
28
+
29
+ while (reader.offset < reader.view.byteLength) {
30
+ // Parse each field based on its type and add to the result object
31
+ const byte = reader.view.getUint8(reader.offset++)
32
+ const type = byte & 0x0f
33
+ if (type === STOP) break
34
+ const delta = byte >> 4
35
+ fid = delta ? fid + delta : readZigZag(reader)
36
+ value[`field_${fid}`] = readElement(reader, type)
37
+ }
38
+
39
+ return value
40
+ }
41
+
42
+ /**
43
+ * Read a single element based on its type
44
+ *
45
+ * @param {DataReader} reader
46
+ * @param {number} type
47
+ * @returns {ThriftType}
48
+ */
49
+ function readElement(reader, type) {
50
+ switch (type) {
51
+ case TRUE:
52
+ return true
53
+ case FALSE:
54
+ return false
55
+ case BYTE:
56
+ return reader.view.getInt8(reader.offset++)
57
+ case I16:
58
+ case I32:
59
+ return readZigZag(reader)
60
+ case I64:
61
+ return readZigZagBigInt(reader)
62
+ case DOUBLE: {
63
+ const value = reader.view.getFloat64(reader.offset, true)
64
+ reader.offset += 8
65
+ return value
66
+ }
67
+ case BINARY: {
68
+ const stringLength = readVarInt(reader)
69
+ const strBytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, stringLength)
70
+ reader.offset += stringLength
71
+ return strBytes
72
+ }
73
+ case LIST: {
74
+ const byte = reader.view.getUint8(reader.offset++)
75
+ const elemType = byte & 0x0f
76
+ let listSize = byte >> 4
77
+ if (listSize === 15) {
78
+ listSize = readVarInt(reader)
79
+ }
80
+ const boolType = elemType === TRUE || elemType === FALSE
81
+ const values = new Array(listSize)
82
+ for (let i = 0; i < listSize; i++) {
83
+ values[i] = boolType ? readElement(reader, BYTE) === 1 : readElement(reader, elemType)
84
+ }
85
+ return values
86
+ }
87
+ case STRUCT:
88
+ // main function handles struct parsing
89
+ return deserializeTCompactProtocol(reader)
90
+ default:
91
+ // MAP, SET, UUID not used by parquet
92
+ throw new Error(`thrift unhandled type: ${type}`)
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Read varint aka Unsigned LEB128.
98
+ *
99
+ * @param {DataReader} reader
100
+ * @returns {number}
101
+ */
102
+ export function readVarInt(reader) {
103
+ let result = 0
104
+ let shift = 0
105
+ while (true) {
106
+ // Read groups of 7 low bits until high bit is 0
107
+ const byte = reader.view.getUint8(reader.offset++)
108
+ result |= (byte & 0x7f) << shift
109
+ if (!(byte & 0x80)) {
110
+ return result
111
+ }
112
+ shift += 7
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Read a varint as a bigint.
118
+ *
119
+ * @param {DataReader} reader
120
+ * @returns {bigint}
121
+ */
122
+ function readVarBigInt(reader) {
123
+ let result = 0n
124
+ let shift = 0n
125
+ while (true) {
126
+ const byte = reader.view.getUint8(reader.offset++)
127
+ result |= BigInt(byte & 0x7f) << shift
128
+ if (!(byte & 0x80)) {
129
+ return result
130
+ }
131
+ shift += 7n
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Read a zigzag number.
137
+ * Zigzag folds positive and negative numbers into the positive number space.
138
+ *
139
+ * @param {DataReader} reader
140
+ * @returns {number}
141
+ */
142
+ export function readZigZag(reader) {
143
+ const zigzag = readVarInt(reader)
144
+ return zigzag >>> 1 ^ -(zigzag & 1)
145
+ }
146
+
147
+ /**
148
+ * Read a zigzag bigint.
149
+ *
150
+ * @param {DataReader} reader
151
+ * @returns {bigint}
152
+ */
153
+ export function readZigZagBigInt(reader) {
154
+ const zigzag = readVarBigInt(reader)
155
+ return zigzag >> 1n ^ -(zigzag & 1n)
156
+ }
web/node_modules/hyparquet/src/types.d.ts ADDED
@@ -0,0 +1,563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ /**
3
+ * Custom parsers for columns
4
+ */
5
+ export interface ParquetParsers {
6
+ timestampFromMilliseconds(millis: bigint): any
7
+ timestampFromMicroseconds(micros: bigint): any
8
+ timestampFromNanoseconds(nanos: bigint): any
9
+ dateFromDays(days: number): any
10
+ stringFromBytes(bytes: Uint8Array): any
11
+ jsonFromBytes(bytes: Uint8Array): any
12
+ geometryFromBytes(bytes: Uint8Array): any
13
+ geographyFromBytes(bytes: Uint8Array): any
14
+ uuidFromBytes(bytes: Uint8Array): any
15
+ }
16
+
17
+ /**
18
+ * Parquet Metadata options for metadata parsing
19
+ */
20
+ export interface MetadataOptions {
21
+ parsers?: ParquetParsers // custom parsers to decode advanced types
22
+ geoparquet?: boolean // parse geoparquet metadata and set logical type to geometry/geography for geospatial columns (default true)
23
+ }
24
+
25
+ /**
26
+ * Parquet query options for reading data
27
+ */
28
+ export interface BaseParquetReadOptions {
29
+ file: AsyncBuffer // file-like object containing parquet data
30
+ metadata?: FileMetaData // parquet metadata, will be parsed if not provided
31
+ columns?: string[] // columns to read, all columns if undefined
32
+ filter?: ParquetQueryFilter // filter applied to rows (requires rowFormat: 'object', onChunk is not filtered)
33
+ filterStrict?: boolean // if true filtering uses strict equality (default true)
34
+ rowStart?: number // first requested row index (inclusive)
35
+ rowEnd?: number // last requested row index (exclusive)
36
+ onChunk?: (chunk: ColumnData) => void // called when a column chunk is parsed. chunks may contain data outside the requested range.
37
+ onPage?: (chunk: SubColumnData) => void // called when a data page is parsed. pages may contain data outside the requested range.
38
+ compressors?: Compressors // custom decompressors
39
+ utf8?: boolean // decode byte arrays as utf8 strings (default true)
40
+ parsers?: ParquetParsers // custom parsers to decode advanced types
41
+ geoparquet?: boolean // parse geoparquet metadata and set logical type to geometry/geography for geospatial columns (default true)
42
+ useOffsetIndex?: boolean // use offset index to limit column chunk reads when available (default false)
43
+ useBloomFilters?: boolean // fetch bloom filters to enable row-group skipping on $eq/$in predicates (default false)
44
+ }
45
+
46
+ interface ArrayRowFormat {
47
+ rowFormat?: 'array' // format of each row passed to the onComplete function. Can be omitted, as it's the default.
48
+ onComplete?: (rows: any[][]) => void // called when all requested rows and columns are parsed
49
+ }
50
+ interface ObjectRowFormat {
51
+ rowFormat: 'object' // format of each row passed to the onComplete function
52
+ onComplete?: (rows: Record<string, any>[]) => void // called when all requested rows and columns are parsed
53
+ }
54
+ export type ParquetReadOptions = BaseParquetReadOptions & (ArrayRowFormat | ObjectRowFormat)
55
+
56
+ /**
57
+ * Parquet query options for filtering data
58
+ */
59
+ export type ParquetQueryFilter =
60
+ | ParquetQueryColumnsFilter
61
+ | { $and: ParquetQueryFilter[] }
62
+ | { $or: ParquetQueryFilter[] }
63
+ | { $nor: ParquetQueryFilter[] }
64
+ type ParquetQueryColumnsFilter = { [key: string]: ParquetQueryOperator }
65
+ export type ParquetQueryValue = string | number | bigint | boolean | object | null | undefined
66
+ export type ParquetQueryOperator = {
67
+ $gt?: ParquetQueryValue
68
+ $gte?: ParquetQueryValue
69
+ $lt?: ParquetQueryValue
70
+ $lte?: ParquetQueryValue
71
+ $eq?: ParquetQueryValue
72
+ $ne?: ParquetQueryValue
73
+ $in?: ParquetQueryValue[]
74
+ $nin?: ParquetQueryValue[]
75
+ $not?: ParquetQueryOperator
76
+ }
77
+
78
+ /**
79
+ * A run of column data
80
+ */
81
+ export interface ColumnData {
82
+ columnName: string
83
+ columnData: DecodedArray
84
+ rowStart: number
85
+ rowEnd: number // exclusive
86
+ }
87
+ /**
88
+ * A run of sub-column data (pre-assembly)
89
+ */
90
+ export interface SubColumnData {
91
+ pathInSchema: string[]
92
+ columnData: DecodedArray
93
+ rowStart: number
94
+ rowEnd: number // exclusive
95
+ }
96
+
97
+ /**
98
+ * File-like object that can read slices of a file asynchronously.
99
+ */
100
+ export interface AsyncBuffer {
101
+ byteLength: number
102
+ slice(start: number, end?: number): Awaitable<ArrayBuffer>
103
+ }
104
+ export type Awaitable<T> = T | Promise<T>
105
+ export interface ByteRange {
106
+ startByte: number
107
+ endByte: number // exclusive
108
+ }
109
+
110
+ export interface DataReader {
111
+ view: DataView
112
+ offset: number
113
+ }
114
+
115
+ // Parquet file metadata types
116
+ export interface FileMetaData {
117
+ version: number
118
+ schema: SchemaElement[]
119
+ num_rows: bigint
120
+ row_groups: RowGroup[]
121
+ key_value_metadata?: KeyValue[]
122
+ created_by?: string
123
+ // column_orders?: ColumnOrder[]
124
+ // encryption_algorithm?: EncryptionAlgorithm
125
+ // footer_signing_key_metadata?: Uint8Array
126
+ metadata_length: number
127
+ }
128
+
129
+ export interface SchemaTree {
130
+ children: SchemaTree[]
131
+ count: number
132
+ element: SchemaElement
133
+ path: string[]
134
+ }
135
+
136
+ export interface SchemaElement {
137
+ type?: ParquetType
138
+ type_length?: number
139
+ repetition_type?: FieldRepetitionType
140
+ name: string
141
+ num_children?: number
142
+ converted_type?: ConvertedType
143
+ scale?: number
144
+ precision?: number
145
+ field_id?: number
146
+ logical_type?: LogicalType
147
+ }
148
+
149
+ export type ParquetType =
150
+ | 'BOOLEAN'
151
+ | 'INT32'
152
+ | 'INT64'
153
+ | 'INT96' // deprecated
154
+ | 'FLOAT'
155
+ | 'DOUBLE'
156
+ | 'BYTE_ARRAY'
157
+ | 'FIXED_LEN_BYTE_ARRAY'
158
+
159
+ export type FieldRepetitionType =
160
+ | 'REQUIRED'
161
+ | 'OPTIONAL'
162
+ | 'REPEATED'
163
+
164
+ export type ConvertedType =
165
+ | 'UTF8'
166
+ | 'MAP'
167
+ | 'MAP_KEY_VALUE'
168
+ | 'LIST'
169
+ | 'ENUM'
170
+ | 'DECIMAL'
171
+ | 'DATE'
172
+ | 'TIME_MILLIS'
173
+ | 'TIME_MICROS'
174
+ | 'TIMESTAMP_MILLIS'
175
+ | 'TIMESTAMP_MICROS'
176
+ | 'UINT_8'
177
+ | 'UINT_16'
178
+ | 'UINT_32'
179
+ | 'UINT_64'
180
+ | 'INT_8'
181
+ | 'INT_16'
182
+ | 'INT_32'
183
+ | 'INT_64'
184
+ | 'JSON'
185
+ | 'BSON'
186
+ | 'INTERVAL'
187
+
188
+ export type TimeUnit = 'MILLIS' | 'MICROS' | 'NANOS'
189
+
190
+ type EdgeInterpolationAlgorithm = 'SPHERICAL' | 'VINCENTY' | 'THOMAS' | 'ANDOYER' | 'KARNEY'
191
+
192
+ export type LogicalType =
193
+ | { type: 'STRING' }
194
+ | { type: 'MAP' }
195
+ | { type: 'LIST' }
196
+ | { type: 'ENUM' }
197
+ | { type: 'DATE' }
198
+ | { type: 'INTERVAL' }
199
+ | { type: 'NULL' }
200
+ | { type: 'JSON' }
201
+ | { type: 'BSON' }
202
+ | { type: 'UUID' }
203
+ | { type: 'FLOAT16' }
204
+ | { type: 'DECIMAL', precision: number, scale: number }
205
+ | { type: 'TIME', isAdjustedToUTC: boolean, unit: TimeUnit }
206
+ | { type: 'TIMESTAMP', isAdjustedToUTC: boolean, unit: TimeUnit }
207
+ | { type: 'INTEGER', bitWidth: number, isSigned: boolean }
208
+ | { type: 'VARIANT', specification_version?: number }
209
+ | { type: 'GEOMETRY', crs?: string }
210
+ | { type: 'GEOGRAPHY', crs?: string, algorithm?: EdgeInterpolationAlgorithm }
211
+
212
+ export interface RowGroup {
213
+ columns: ColumnChunk[]
214
+ total_byte_size: bigint
215
+ num_rows: bigint
216
+ sorting_columns?: SortingColumn[]
217
+ file_offset?: bigint
218
+ total_compressed_size?: bigint
219
+ ordinal?: number
220
+ }
221
+
222
+ export interface ColumnChunk {
223
+ file_path?: string
224
+ file_offset: bigint
225
+ meta_data?: ColumnMetaData
226
+ offset_index_offset?: bigint
227
+ offset_index_length?: number
228
+ column_index_offset?: bigint
229
+ column_index_length?: number
230
+ crypto_metadata?: ColumnCryptoMetaData
231
+ encrypted_column_metadata?: Uint8Array
232
+ }
233
+
234
+ export interface ColumnMetaData {
235
+ type: ParquetType
236
+ encodings: Encoding[]
237
+ path_in_schema: string[]
238
+ codec: CompressionCodec
239
+ num_values: bigint
240
+ total_uncompressed_size: bigint
241
+ total_compressed_size: bigint
242
+ key_value_metadata?: KeyValue[]
243
+ data_page_offset: bigint
244
+ index_page_offset?: bigint
245
+ dictionary_page_offset?: bigint
246
+ statistics?: Statistics
247
+ encoding_stats?: PageEncodingStats[]
248
+ bloom_filter_offset?: bigint
249
+ bloom_filter_length?: number
250
+ size_statistics?: SizeStatistics
251
+ geospatial_statistics?: GeospatialStatistics
252
+ }
253
+
254
+ type ColumnCryptoMetaData = Record<string, never>
255
+
256
+ export type Encoding =
257
+ | 'PLAIN'
258
+ | 'GROUP_VAR_INT' // deprecated
259
+ | 'PLAIN_DICTIONARY'
260
+ | 'RLE'
261
+ | 'BIT_PACKED' // deprecated
262
+ | 'DELTA_BINARY_PACKED'
263
+ | 'DELTA_LENGTH_BYTE_ARRAY'
264
+ | 'DELTA_BYTE_ARRAY'
265
+ | 'RLE_DICTIONARY'
266
+ | 'BYTE_STREAM_SPLIT'
267
+
268
+ export type CompressionCodec =
269
+ | 'UNCOMPRESSED'
270
+ | 'SNAPPY'
271
+ | 'GZIP'
272
+ | 'LZO'
273
+ | 'BROTLI'
274
+ | 'LZ4'
275
+ | 'ZSTD'
276
+ | 'LZ4_RAW'
277
+
278
+ export type Compressors = {
279
+ [K in CompressionCodec]?: (input: Uint8Array, outputLength: number) => Uint8Array
280
+ }
281
+
282
+ export interface KeyValue {
283
+ key: string
284
+ value?: string
285
+ }
286
+
287
+ export type MinMaxType = bigint | boolean | number | string | Date | Uint8Array
288
+
289
+ export interface Statistics {
290
+ max?: MinMaxType
291
+ min?: MinMaxType
292
+ null_count?: bigint
293
+ distinct_count?: bigint
294
+ max_value?: MinMaxType
295
+ min_value?: MinMaxType
296
+ is_max_value_exact?: boolean
297
+ is_min_value_exact?: boolean
298
+ }
299
+
300
+ interface SizeStatistics {
301
+ unencoded_byte_array_data_bytes?: bigint
302
+ repetition_level_histogram?: bigint[]
303
+ definition_level_histogram?: bigint[]
304
+ }
305
+
306
+ export interface GeospatialStatistics {
307
+ bbox?: BoundingBox
308
+ geospatial_types?: number[]
309
+ }
310
+
311
+ export interface BoundingBox {
312
+ xmin: number
313
+ xmax: number
314
+ ymin: number
315
+ ymax: number
316
+ zmin?: number
317
+ zmax?: number
318
+ mmin?: number
319
+ mmax?: number
320
+ }
321
+
322
+ export interface PageEncodingStats {
323
+ page_type: PageType
324
+ encoding: Encoding
325
+ count: number
326
+ }
327
+
328
+ export interface BloomFilter {
329
+ numBytes: number
330
+ blocks: Uint32Array
331
+ }
332
+
333
+ export type PageType =
334
+ 'DATA_PAGE' |
335
+ 'INDEX_PAGE' |
336
+ 'DICTIONARY_PAGE' |
337
+ 'DATA_PAGE_V2'
338
+
339
+ interface SortingColumn {
340
+ column_idx: number
341
+ descending: boolean
342
+ nulls_first: boolean
343
+ }
344
+
345
+ // Parquet file header types
346
+ export interface PageHeader {
347
+ type: PageType
348
+ uncompressed_page_size: number
349
+ compressed_page_size: number
350
+ crc?: number
351
+ data_page_header?: DataPageHeader
352
+ index_page_header?: IndexPageHeader
353
+ dictionary_page_header?: DictionaryPageHeader
354
+ data_page_header_v2?: DataPageHeaderV2
355
+ }
356
+
357
+ export interface DataPageHeader {
358
+ num_values: number
359
+ encoding: Encoding
360
+ definition_level_encoding: Encoding
361
+ repetition_level_encoding: Encoding
362
+ statistics?: Statistics
363
+ }
364
+
365
+ type IndexPageHeader = Record<string, never>
366
+
367
+ export interface DictionaryPageHeader {
368
+ num_values: number
369
+ encoding: Encoding
370
+ is_sorted?: boolean
371
+ }
372
+
373
+ export interface DataPageHeaderV2 {
374
+ num_values: number
375
+ num_nulls: number
376
+ num_rows: number
377
+ encoding: Encoding
378
+ definition_levels_byte_length: number
379
+ repetition_levels_byte_length: number
380
+ is_compressed?: boolean
381
+ statistics?: Statistics
382
+ }
383
+
384
+ interface DataPage {
385
+ definitionLevels: number[] | undefined
386
+ repetitionLevels: number[]
387
+ dataPage: DecodedArray
388
+ }
389
+
390
+ export type DecodedArray =
391
+ | Uint8Array
392
+ | Uint32Array
393
+ | Int32Array
394
+ | BigInt64Array
395
+ | BigUint64Array
396
+ | Float32Array
397
+ | Float64Array
398
+ | any[]
399
+
400
+ /** Wrapper around decoded page data */
401
+ export interface PageResult {
402
+ skipped: number
403
+ data?: DecodedArray
404
+ }
405
+
406
+ export interface OffsetIndex {
407
+ page_locations: PageLocation[]
408
+ unencoded_byte_array_data_bytes?: bigint[]
409
+ }
410
+
411
+ interface PageLocation {
412
+ offset: bigint
413
+ compressed_page_size: number
414
+ first_row_index: bigint
415
+ }
416
+
417
+ export interface ColumnIndex {
418
+ null_pages: boolean[]
419
+ min_values: Uint8Array[]
420
+ max_values: Uint8Array[]
421
+ boundary_order: BoundaryOrder
422
+ null_counts?: bigint[]
423
+ repetition_level_histograms?: bigint[]
424
+ definition_level_histograms?: bigint[]
425
+ }
426
+
427
+ export type BoundaryOrder = 'UNORDERED' | 'ASCENDING' | 'DESCENDING'
428
+
429
+ export interface VariantMetadata {
430
+ dictionary: string[]
431
+ sorted: boolean
432
+ }
433
+
434
+ export type ThriftObject = { [ key: `field_${number}` ]: ThriftType }
435
+ export type ThriftType = boolean | number | bigint | Uint8Array | ThriftType[] | ThriftObject
436
+
437
+ /**
438
+ * Query plan for which byte ranges to read.
439
+ */
440
+ export interface QueryPlan {
441
+ metadata: FileMetaData
442
+ rowStart: number
443
+ rowEnd?: number
444
+ columns?: string[] // columns to read
445
+ fetches: ByteRange[] // byte ranges to fetch
446
+ groups: GroupPlan[] // byte ranges by row group
447
+ }
448
+ // Plan for one group
449
+ interface GroupPlan {
450
+ chunks: ChunkPlan[]
451
+ rowGroup: RowGroup // row group metadata
452
+ groupStart: number // row index of the first row in the group
453
+ selectStart: number // row index in the group to start reading
454
+ selectEnd: number // row index in the group to stop reading
455
+ groupRows: number // number of rows in the group
456
+ }
457
+ // Plan for one column within a row group
458
+ type ChunkPlan = ChunkFull | ChunkOffsetIndexed
459
+ // full column chunk
460
+ interface ChunkFull {
461
+ columnMetadata: ColumnMetaData
462
+ range: ByteRange
463
+ }
464
+ // column chunk with offset index pending
465
+ interface ChunkOffsetIndexed {
466
+ columnMetadata: ColumnMetaData
467
+ offsetIndex: ByteRange
468
+ range: ByteRange
469
+ }
470
+
471
+ export interface ColumnDecoder {
472
+ pathInSchema: string[]
473
+ type: ParquetType
474
+ element: SchemaElement
475
+ schemaPath: SchemaTree[]
476
+ codec: CompressionCodec
477
+ parsers: ParquetParsers
478
+ compressors?: Compressors
479
+ utf8?: boolean
480
+ }
481
+
482
+ export interface RowGroupSelect {
483
+ groupStart: number // row index of the first row in the group
484
+ selectStart: number // row index in the group to start reading
485
+ selectEnd: number // row index in the group to stop reading
486
+ groupRows: number
487
+ }
488
+
489
+ export interface AsyncRowGroup {
490
+ groupStart: number
491
+ groupRows: number
492
+ asyncColumns: AsyncColumn[]
493
+ }
494
+ export interface AsyncColumn {
495
+ pathInSchema: string[]
496
+ data: Promise<AsyncPages>
497
+ }
498
+ interface AsyncPages {
499
+ skipped: number // rows skipped from groupStart to first row of this column data
500
+ data: DecodedArray[]
501
+ }
502
+
503
+ /**
504
+ * Geometry types based on the GeoJSON specification (RFC 7946)
505
+ */
506
+ export type Geometry =
507
+ | Point
508
+ | MultiPoint
509
+ | LineString
510
+ | MultiLineString
511
+ | Polygon
512
+ | MultiPolygon
513
+ | GeometryCollection
514
+
515
+ /**
516
+ * Position is an array of at least two numbers.
517
+ * The order should be [longitude, latitude] with optional properties (eg- altitude).
518
+ */
519
+ export type Position = number[]
520
+
521
+ export interface Point {
522
+ type: 'Point'
523
+ coordinates: Position
524
+ }
525
+
526
+ export interface MultiPoint {
527
+ type: 'MultiPoint'
528
+ coordinates: Position[]
529
+ }
530
+
531
+ export interface LineString {
532
+ type: 'LineString'
533
+ coordinates: Position[]
534
+ }
535
+
536
+ /**
537
+ * Each element is one LineString.
538
+ */
539
+ export interface MultiLineString {
540
+ type: 'MultiLineString'
541
+ coordinates: Position[][]
542
+ }
543
+
544
+ /**
545
+ * Each element is a linear ring.
546
+ */
547
+ export interface Polygon {
548
+ type: 'Polygon'
549
+ coordinates: Position[][]
550
+ }
551
+
552
+ /**
553
+ * Each element is one Polygon.
554
+ */
555
+ export interface MultiPolygon {
556
+ type: 'MultiPolygon'
557
+ coordinates: Position[][][]
558
+ }
559
+
560
+ export interface GeometryCollection {
561
+ type: 'GeometryCollection'
562
+ geometries: Geometry[]
563
+ }
web/node_modules/hyparquet/src/utils.js ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @import {AsyncBuffer, Awaitable, DecodedArray} from '../src/types.js'
3
+ */
4
+
5
+ import { defaultInitialFetchSize } from './metadata.js'
6
+
7
+ /**
8
+ * Replace bigint, date, etc with legal JSON types.
9
+ *
10
+ * @param {any} obj object to convert
11
+ * @returns {unknown} converted object
12
+ */
13
+ export function toJson(obj) {
14
+ if (obj === undefined) return null
15
+ if (typeof obj === 'bigint') return Number(obj)
16
+ if (Object.is(obj, -0)) return 0
17
+ if (Array.isArray(obj)) return obj.map(toJson)
18
+ if (obj instanceof Uint8Array) return Array.from(obj)
19
+ if (obj instanceof Date) return obj.toISOString()
20
+ if (obj instanceof Object) {
21
+ /** @type {Record<string, unknown>} */
22
+ const newObj = {}
23
+ for (const key of Object.keys(obj)) {
24
+ if (obj[key] === undefined) continue
25
+ newObj[key] = toJson(obj[key])
26
+ }
27
+ return newObj
28
+ }
29
+ return obj
30
+ }
31
+
32
+ /**
33
+ * Concatenate two arrays fast.
34
+ *
35
+ * @param {any[]} aaa
36
+ * @param {DecodedArray} bbb
37
+ */
38
+ export function concat(aaa, bbb) {
39
+ const chunk = 10000
40
+ for (let i = 0; i < bbb.length; i += chunk) {
41
+ aaa.push(...bbb.slice(i, i + chunk))
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Deep equality.
47
+ *
48
+ * @param {any} a
49
+ * @param {any} b
50
+ * @param {boolean} [strict]
51
+ * @returns {boolean}
52
+ */
53
+ export function equals(a, b, strict = true) {
54
+ // eslint-disable-next-line eqeqeq
55
+ if (strict ? a === b : a == b) return true
56
+ if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false
57
+
58
+ if (a instanceof Uint8Array && b instanceof Uint8Array) {
59
+ if (a.length !== b.length) return false
60
+ for (let i = 0; i < a.length; i++) {
61
+ if (a[i] !== b[i]) return false
62
+ }
63
+ return true
64
+ }
65
+ if (Array.isArray(a) && Array.isArray(b)) {
66
+ if (a.length !== b.length) return false
67
+ for (let i = 0; i < a.length; i++) {
68
+ if (!equals(a[i], b[i], strict)) return false
69
+ }
70
+ return true
71
+ }
72
+
73
+ const aKeys = Object.keys(a)
74
+ if (aKeys.length !== Object.keys(b).length) return false
75
+ for (const k of aKeys) {
76
+ if (!equals(a[k], b[k], strict)) return false
77
+ }
78
+ return true
79
+ }
80
+
81
+ /**
82
+ * Get the byte length using fetch with a ranged GET request.
83
+ * Aborts the request if server returns 200 instead of 206.
84
+ *
85
+ * @param {string} url
86
+ * @param {RequestInit} [requestInit] fetch options
87
+ * @param {typeof globalThis.fetch} [fetchFn] fetch function to use
88
+ * @returns {Promise<number>}
89
+ */
90
+ async function byteLengthFromUrlUsingGet(url, requestInit = {}, fetchFn = globalThis.fetch) {
91
+ const controller = new AbortController()
92
+ const headers = new Headers(requestInit.headers)
93
+ headers.set('Range', 'bytes=0-0')
94
+
95
+ const res = await fetchFn(url, {
96
+ ...requestInit,
97
+ headers,
98
+ signal: controller.signal,
99
+ })
100
+
101
+ if (!res.ok) throw new Error(`fetch with range failed ${res.status}`)
102
+
103
+ // Server supports Range requests (206 Partial Content)
104
+ if (res.status === 206) {
105
+ const contentRange = res.headers.get('Content-Range')
106
+ if (!contentRange) throw new Error('missing content-range header')
107
+
108
+ // Parse "bytes 0-0/9446073" to get total length
109
+ const match = contentRange.match(/bytes \d+-\d+\/(\d+)/)
110
+ if (!match) throw new Error(`invalid content-range header: ${contentRange}`)
111
+
112
+ return parseInt(match[1])
113
+ }
114
+
115
+ // Server ignored Range and returned 200 - get Content-Length and abort request
116
+ if (res.status === 200) {
117
+ const contentLength = res.headers.get('Content-Length')
118
+
119
+ // Abort the request to stop any ongoing download
120
+ controller.abort()
121
+
122
+ if (contentLength) return parseInt(contentLength)
123
+ }
124
+
125
+ throw new Error('server does not support range requests and missing content-length')
126
+ }
127
+
128
+ /**
129
+ * Get the byte length of a URL using a HEAD request.
130
+ * If HEAD fails with 403 (e.g., with signed S3 URLs), falls back to a ranged GET request.
131
+ * If HEAD succeeds but Content-Length is missing, falls back to GET with range.
132
+ * If requestInit is provided, it will be passed to fetch.
133
+ *
134
+ * @param {string} url
135
+ * @param {RequestInit} [requestInit] fetch options
136
+ * @param {typeof globalThis.fetch} [customFetch] fetch function to use
137
+ * @returns {Promise<number>}
138
+ */
139
+ export async function byteLengthFromUrl(url, requestInit, customFetch) {
140
+ const fetch = customFetch ?? globalThis.fetch
141
+ const res = await fetch(url, { ...requestInit, method: 'HEAD' })
142
+
143
+ // If HEAD request is forbidden (common with signed S3 URLs), try GET with range
144
+ if (res.status === 403) {
145
+ return byteLengthFromUrlUsingGet(url, requestInit, fetch)
146
+ }
147
+
148
+ if (!res.ok) throw new Error(`fetch head failed ${res.status}`)
149
+ const length = res.headers.get('Content-Length')
150
+ // If Content-Length is missing from HEAD, fallback to GET with range
151
+ if (!length) {
152
+ return byteLengthFromUrlUsingGet(url, requestInit, fetch)
153
+ }
154
+ return parseInt(length)
155
+ }
156
+
157
+ /**
158
+ * Construct an AsyncBuffer for a URL.
159
+ * If byteLength is not provided, will make a HEAD request to get the file size.
160
+ * If fetch is provided, it will be used instead of the global fetch.
161
+ * If requestInit is provided, it will be passed to fetch.
162
+ *
163
+ * @param {object} options
164
+ * @param {string} options.url
165
+ * @param {number} [options.byteLength]
166
+ * @param {typeof globalThis.fetch} [options.fetch] fetch function to use
167
+ * @param {RequestInit} [options.requestInit]
168
+ * @returns {Promise<AsyncBuffer>}
169
+ */
170
+ export async function asyncBufferFromUrl({ url, byteLength, requestInit, fetch: customFetch }) {
171
+ if (!url) throw new Error('missing url')
172
+ const fetch = customFetch ?? globalThis.fetch
173
+ // byte length from HEAD request
174
+ byteLength ??= await byteLengthFromUrl(url, requestInit, fetch)
175
+
176
+ /**
177
+ * A promise for the whole buffer, if range requests are not supported.
178
+ * @type {Promise<ArrayBuffer>|undefined}
179
+ */
180
+ let buffer = undefined
181
+ const init = requestInit || {}
182
+
183
+ return {
184
+ byteLength,
185
+ async slice(start, end) {
186
+ if (buffer) {
187
+ return buffer.then(buffer => buffer.slice(start, end))
188
+ }
189
+
190
+ const headers = new Headers(init.headers)
191
+ const endStr = end === undefined ? '' : end - 1
192
+ headers.set('Range', `bytes=${start}-${endStr}`)
193
+
194
+ const res = await fetch(url, { ...init, headers })
195
+ if (!res.ok || !res.body) throw new Error(`fetch failed ${res.status}`)
196
+
197
+ if (res.status === 200) {
198
+ // Endpoint does not support range requests and returned the whole object
199
+ buffer = res.arrayBuffer()
200
+ return buffer.then(buffer => buffer.slice(start, end))
201
+ } else if (res.status === 206) {
202
+ // The endpoint supports range requests and sent us the requested range
203
+ return res.arrayBuffer()
204
+ } else {
205
+ throw new Error(`fetch received unexpected status code ${res.status}`)
206
+ }
207
+ },
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Returns a cached layer on top of an AsyncBuffer. For caching slices of a file
213
+ * that are read multiple times, possibly over a network.
214
+ *
215
+ * @param {AsyncBuffer} file file-like object to cache
216
+ * @param {{ minSize?: number }} [options]
217
+ * @returns {AsyncBuffer} cached file-like object
218
+ */
219
+ export function cachedAsyncBuffer({ byteLength, slice }, { minSize = defaultInitialFetchSize } = {}) {
220
+ if (byteLength < minSize) {
221
+ // Cache whole file if it's small
222
+ const buffer = slice(0, byteLength)
223
+ return {
224
+ byteLength,
225
+ async slice(start, end) {
226
+ return (await buffer).slice(start, end)
227
+ },
228
+ }
229
+ }
230
+ const cache = new Map()
231
+ return {
232
+ byteLength,
233
+ /**
234
+ * @param {number} start
235
+ * @param {number} [end]
236
+ * @returns {Awaitable<ArrayBuffer>}
237
+ */
238
+ slice(start, end) {
239
+ const key = cacheKey(start, end, byteLength)
240
+ const cached = cache.get(key)
241
+ if (cached) return cached
242
+ // cache miss, read from file
243
+ const promise = slice(start, end)
244
+ cache.set(key, promise)
245
+ return promise
246
+ },
247
+ }
248
+ }
249
+
250
+
251
+ /**
252
+ * Returns canonical cache key for a byte range 'start,end'.
253
+ * Normalize int-range and suffix-range requests to the same key.
254
+ *
255
+ * @param {number} start start byte of range
256
+ * @param {number} [end] end byte of range, or undefined for suffix range
257
+ * @param {number} [size] size of file, or undefined for suffix range
258
+ * @returns {string}
259
+ */
260
+ function cacheKey(start, end, size) {
261
+ if (start < 0) {
262
+ if (end !== undefined) throw new Error(`invalid suffix range [${start}, ${end}]`)
263
+ if (size === undefined) return `${start},`
264
+ return `${size + start},${size}`
265
+ } else if (end !== undefined) {
266
+ if (start > end) throw new Error(`invalid empty range [${start}, ${end}]`)
267
+ return `${start},${end}`
268
+ } else if (size === undefined) {
269
+ return `${start},`
270
+ } else {
271
+ return `${start},${size}`
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Flatten a list of lists into a single list.
277
+ *
278
+ * @param {DecodedArray[]} [chunks]
279
+ * @returns {DecodedArray}
280
+ */
281
+ export function flatten(chunks) {
282
+ if (!chunks) return []
283
+ if (chunks.length === 1) return chunks[0]
284
+ /** @type {any[]} */
285
+ const output = []
286
+ for (const chunk of chunks) {
287
+ concat(output, chunk)
288
+ }
289
+ return output
290
+ }
web/node_modules/hyparquet/src/variant.js ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DEFAULT_PARSERS } from './convert.js'
2
+
3
+ /**
4
+ * @import {DataReader, ParquetParsers, VariantMetadata} from '../src/types.js'
5
+ */
6
+
7
+ const decoder = new TextDecoder()
8
+ /** @type {WeakMap<object, Map<string, VariantMetadata>>} */
9
+ const metadataCache = new WeakMap()
10
+
11
+ /**
12
+ * Recursively decode variant structs into native values.
13
+ *
14
+ * @param {any} value
15
+ * @param {ParquetParsers} [parsers]
16
+ * @returns {any}
17
+ */
18
+ export function decodeVariantColumn(value, parsers = DEFAULT_PARSERS) {
19
+ if (Array.isArray(value)) {
20
+ return value.map(entry => decodeVariantColumn(entry, parsers))
21
+ }
22
+ if (typeof value !== 'object') return value
23
+
24
+ if ('metadata' in value) {
25
+ const metadata = parseVariantMetadata(value.metadata)
26
+
27
+ // Decode shredded fields from typed_value
28
+ const shreddedFields = value.typed_value && decodeTypedValue(value.typed_value, metadata, parsers)
29
+
30
+ // Decode binary value (may contain additional fields for partially shredded objects)
31
+ const binaryValue = value.value && readVariant(makeReader(value.value), metadata, parsers)
32
+
33
+ // Merge shredded and binary values for partially shredded objects
34
+ if (shreddedFields && binaryValue) {
35
+ return { ...binaryValue, ...shreddedFields }
36
+ }
37
+ return shreddedFields ?? binaryValue
38
+ }
39
+
40
+ return value
41
+ }
42
+
43
+ /**
44
+ * Decode a shredded variant typed_value field.
45
+ *
46
+ * @param {any} typedValue
47
+ * @param {VariantMetadata} metadata
48
+ * @param {ParquetParsers} parsers
49
+ * @returns {any}
50
+ */
51
+ function decodeTypedValue(typedValue, metadata, parsers) {
52
+ if (typedValue instanceof Date) return typedValue
53
+
54
+ // Handle {typed_value, value} wrapper - unwrap and recurse
55
+ if (typedValue && typeof typedValue === 'object' && !Array.isArray(typedValue) && !(typedValue instanceof Uint8Array)) {
56
+ if ('typed_value' in typedValue && typedValue.typed_value !== null && typedValue.typed_value !== undefined) {
57
+ return decodeTypedValue(typedValue.typed_value, metadata, parsers)
58
+ }
59
+ if ('value' in typedValue && typedValue.value instanceof Uint8Array) {
60
+ return readVariant(makeReader(typedValue.value), metadata, parsers)
61
+ }
62
+ if ('typed_value' in typedValue || 'value' in typedValue) {
63
+ // both null/missing → field is null
64
+ return null
65
+ }
66
+ // Shredded object: each field value gets decoded
67
+ /** @type {Record<string, any>} */
68
+ const result = {}
69
+ for (const [key, field] of Object.entries(typedValue)) {
70
+ if (!metadata.dictionary.includes(key)) continue
71
+ result[key] = decodeTypedValue(field, metadata, parsers)
72
+ }
73
+ return result
74
+ }
75
+
76
+ // Uint8Array: decode as binary variant
77
+ if (typedValue instanceof Uint8Array) {
78
+ return readVariant(makeReader(typedValue), metadata, parsers)
79
+ }
80
+
81
+ // Arrays
82
+ if (Array.isArray(typedValue)) {
83
+ return typedValue.map(element => decodeTypedValue(element, metadata, parsers))
84
+ }
85
+
86
+ return typedValue
87
+ }
88
+
89
+ /**
90
+ * @param {Uint8Array} bytes
91
+ * @returns {DataReader}
92
+ */
93
+ function makeReader(bytes) {
94
+ return { view: new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), offset: 0 }
95
+ }
96
+
97
+ /**
98
+ * Parse and cache variant metadata dictionary.
99
+ *
100
+ * @param {Uint8Array} bytes
101
+ * @returns {VariantMetadata}
102
+ */
103
+ function parseVariantMetadata(bytes) {
104
+ let bufferCache = metadataCache.get(bytes.buffer)
105
+ if (!bufferCache) {
106
+ bufferCache = new Map()
107
+ metadataCache.set(bytes.buffer, bufferCache)
108
+ }
109
+ const key = `${bytes.byteOffset}:${bytes.byteLength}`
110
+ const cached = bufferCache.get(key)
111
+ if (cached) return cached
112
+
113
+ const reader = makeReader(bytes)
114
+ const header = reader.view.getUint8(reader.offset++)
115
+ const version = header & 0x0f
116
+ if (version !== 1) throw new Error(`parquet unsupported variant metadata version: ${version}`)
117
+ const sorted = (header >> 4 & 0x1) === 1
118
+ const offsetSize = (header >> 6 & 0x3) + 1
119
+
120
+ const dictionarySize = readUnsigned(reader, offsetSize)
121
+
122
+ const offsets = new Array(dictionarySize + 1)
123
+ for (let i = 0; i < offsets.length; i++) {
124
+ offsets[i] = readUnsigned(reader, offsetSize)
125
+ }
126
+
127
+ const base = reader.offset
128
+ const dictionary = new Array(dictionarySize)
129
+ for (let i = 0; i < dictionarySize; i++) {
130
+ const start = offsets[i]
131
+ const end = offsets[i + 1]
132
+ const strBytes = new Uint8Array(bytes.buffer, bytes.byteOffset + base + start, end - start)
133
+ dictionary[i] = decoder.decode(strBytes)
134
+ }
135
+
136
+ const metadata = { dictionary, sorted }
137
+ bufferCache.set(key, metadata)
138
+ return metadata
139
+ }
140
+
141
+ /**
142
+ * @param {DataReader} reader
143
+ * @param {number} byteWidth
144
+ * @returns {number}
145
+ */
146
+ function readUnsigned(reader, byteWidth) {
147
+ let value = 0
148
+ for (let i = 0; i < byteWidth; i++) {
149
+ value |= reader.view.getUint8(reader.offset + i) << i * 8
150
+ }
151
+ reader.offset += byteWidth
152
+ return value
153
+ }
154
+
155
+ /**
156
+ * @param {DataReader} reader
157
+ * @param {VariantMetadata} metadata
158
+ * @param {ParquetParsers} parsers
159
+ * @returns {any}
160
+ */
161
+ function readVariant(reader, metadata, parsers) {
162
+ const typeByte = reader.view.getUint8(reader.offset++)
163
+ const basicType = typeByte & 0x3
164
+ const header = typeByte >> 2
165
+ if (basicType === 0) return readVariantPrimitive(reader, header, parsers)
166
+ if (basicType === 2) return readVariantObject(reader, header, metadata, parsers)
167
+ if (basicType === 3) return readVariantArray(reader, header, metadata, parsers)
168
+ // else short string
169
+ const bytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, header)
170
+ reader.offset += header
171
+ return decoder.decode(bytes)
172
+ }
173
+
174
+ /**
175
+ * @param {DataReader} reader
176
+ * @param {number} typeId
177
+ * @param {ParquetParsers} parsers
178
+ * @returns {any}
179
+ */
180
+ function readVariantPrimitive(reader, typeId, parsers) {
181
+ switch (typeId) {
182
+ case 0: return null
183
+ case 1: return true
184
+ case 2: return false
185
+ case 3: {
186
+ const value = reader.view.getInt8(reader.offset)
187
+ reader.offset += 1
188
+ return value
189
+ }
190
+ case 4: {
191
+ const value = reader.view.getInt16(reader.offset, true)
192
+ reader.offset += 2
193
+ return value
194
+ }
195
+ case 5: {
196
+ const value = reader.view.getInt32(reader.offset, true)
197
+ reader.offset += 4
198
+ return value
199
+ }
200
+ case 6: {
201
+ const value = reader.view.getBigInt64(reader.offset, true)
202
+ reader.offset += 8
203
+ return value
204
+ }
205
+ case 7: {
206
+ const value = reader.view.getFloat64(reader.offset, true)
207
+ reader.offset += 8
208
+ return value
209
+ }
210
+ case 8:
211
+ return readVariantDecimal(reader, 4)
212
+ case 9:
213
+ return readVariantDecimal(reader, 8)
214
+ case 10:
215
+ return readVariantDecimal(reader, 16)
216
+ case 11: {
217
+ const value = reader.view.getInt32(reader.offset, true)
218
+ reader.offset += 4
219
+ return parsers.dateFromDays(value)
220
+ }
221
+ case 12: // timestamp_micros (utc)
222
+ case 13: { // timestamp_micros_ntz (no timezone)
223
+ const value = reader.view.getBigInt64(reader.offset, true)
224
+ reader.offset += 8
225
+ return parsers.timestampFromMicroseconds(value)
226
+ }
227
+ case 14: {
228
+ const value = reader.view.getFloat32(reader.offset, true)
229
+ reader.offset += 4
230
+ return value
231
+ }
232
+ case 15:
233
+ return readVariantBinary(reader)
234
+ case 16: {
235
+ const bytes = readVariantBinary(reader)
236
+ return decoder.decode(bytes)
237
+ }
238
+ case 17: {
239
+ // time: microseconds since midnight
240
+ const value = reader.view.getBigInt64(reader.offset, true)
241
+ reader.offset += 8
242
+ return value
243
+ }
244
+ case 18: // timestamp_nanos (utc)
245
+ case 19: { // timestamp_nanos_ntz (no timezone)
246
+ const value = reader.view.getBigInt64(reader.offset, true)
247
+ reader.offset += 8
248
+ return parsers.timestampFromNanoseconds(value)
249
+ }
250
+ case 20: {
251
+ const bytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, 16)
252
+ reader.offset += 16
253
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('')
254
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
255
+ }
256
+ default:
257
+ throw new Error(`parquet unsupported variant primitive type: ${typeId}`)
258
+ }
259
+ }
260
+
261
+ /**
262
+ * @param {DataReader} reader
263
+ * @param {number} header
264
+ * @param {VariantMetadata} metadata
265
+ * @param {ParquetParsers} parsers
266
+ * @returns {Record<string, any>}
267
+ */
268
+ function readVariantObject(reader, header, metadata, parsers) {
269
+ const offsetWidth = (header & 0x3) + 1
270
+ const idWidth = (header >> 2 & 0x3) + 1
271
+ const isLarge = header >> 4 & 0x1
272
+ const numElements = isLarge ? readUnsigned(reader, 4) : reader.view.getUint8(reader.offset++)
273
+
274
+ /** @type {number[]} */
275
+ const fieldIds = new Array(numElements)
276
+ for (let i = 0; i < numElements; i++) {
277
+ fieldIds[i] = readUnsigned(reader, idWidth)
278
+ }
279
+
280
+ const offsets = new Array(numElements + 1)
281
+ for (let i = 0; i < offsets.length; i++) {
282
+ offsets[i] = readUnsigned(reader, offsetWidth)
283
+ }
284
+
285
+ /** @type {Record<string, any>} */
286
+ const out = {}
287
+ for (let i = 0; i < numElements; i++) {
288
+ const key = metadata.dictionary[fieldIds[i]]
289
+ // Read value at the given offset
290
+ const valueReader = {
291
+ view: reader.view,
292
+ offset: reader.offset + offsets[i],
293
+ }
294
+ out[key] = readVariant(valueReader, metadata, parsers)
295
+ }
296
+ reader.offset += offsets[offsets.length - 1]
297
+ return out
298
+ }
299
+
300
+ /**
301
+ * @param {DataReader} reader
302
+ * @param {number} header
303
+ * @param {VariantMetadata} metadata
304
+ * @param {ParquetParsers} parsers
305
+ * @returns {any[]}
306
+ */
307
+ function readVariantArray(reader, header, metadata, parsers) {
308
+ const fieldOffsetSize = header & 0x3
309
+ const isLarge = header >> 2 & 0x1
310
+ const offsetWidth = fieldOffsetSize + 1
311
+ const numElements = readUnsigned(reader, isLarge ? 4 : 1)
312
+
313
+ const offsets = new Array(numElements + 1)
314
+ for (let i = 0; i < offsets.length; i++) {
315
+ offsets[i] = readUnsigned(reader, offsetWidth)
316
+ }
317
+
318
+ const valuesStart = reader.offset
319
+ const result = new Array(numElements)
320
+ for (let i = 0; i < numElements; i++) {
321
+ const valueReader = {
322
+ view: reader.view,
323
+ offset: valuesStart + offsets[i],
324
+ }
325
+ result[i] = readVariant(valueReader, metadata, parsers)
326
+ }
327
+ reader.offset = valuesStart + offsets[offsets.length - 1]
328
+ return result
329
+ }
330
+
331
+ /**
332
+ * @param {DataReader} reader
333
+ * @param {number} width
334
+ * @returns {number}
335
+ */
336
+ function readVariantDecimal(reader, width) {
337
+ const scale = reader.view.getUint8(reader.offset)
338
+ reader.offset += 1
339
+ let unscaled
340
+ if (width === 4) {
341
+ unscaled = BigInt(reader.view.getInt32(reader.offset, true))
342
+ reader.offset += 4
343
+ } else if (width === 8) {
344
+ unscaled = reader.view.getBigInt64(reader.offset, true)
345
+ reader.offset += 8
346
+ } else {
347
+ const low = reader.view.getBigUint64(reader.offset, true)
348
+ const high = reader.view.getBigInt64(reader.offset + 8, true)
349
+ unscaled = high << 64n | low
350
+ reader.offset += 16
351
+ }
352
+
353
+ return Number(unscaled) * 10 ** -scale
354
+ }
355
+
356
+ /**
357
+ * @param {DataReader} reader
358
+ * @returns {Uint8Array}
359
+ */
360
+ function readVariantBinary(reader) {
361
+ const length = reader.view.getUint32(reader.offset, true)
362
+ reader.offset += 4
363
+ const bytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length)
364
+ reader.offset += length
365
+ return bytes
366
+ }
web/node_modules/hyparquet/src/wkb.js ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WKB (Well-Known Binary) decoder for geometry objects.
3
+ *
4
+ * @param {DataReader} reader
5
+ * @returns {Geometry} geometry object
6
+ */
7
+ export function wkbToGeojson(reader) {
8
+ const flags = getFlags(reader)
9
+
10
+ if (flags.type === 1) { // Point
11
+ return { type: 'Point', coordinates: readPosition(reader, flags) }
12
+ } else if (flags.type === 2) { // LineString
13
+ return { type: 'LineString', coordinates: readLine(reader, flags) }
14
+ } else if (flags.type === 3) { // Polygon
15
+ return { type: 'Polygon', coordinates: readPolygon(reader, flags) }
16
+ } else if (flags.type === 4) { // MultiPoint
17
+ const points = []
18
+ for (let i = 0; i < flags.count; i++) {
19
+ points.push(readPosition(reader, getFlags(reader)))
20
+ }
21
+ return { type: 'MultiPoint', coordinates: points }
22
+ } else if (flags.type === 5) { // MultiLineString
23
+ const lines = []
24
+ for (let i = 0; i < flags.count; i++) {
25
+ lines.push(readLine(reader, getFlags(reader)))
26
+ }
27
+ return { type: 'MultiLineString', coordinates: lines }
28
+ } else if (flags.type === 6) { // MultiPolygon
29
+ const polygons = []
30
+ for (let i = 0; i < flags.count; i++) {
31
+ polygons.push(readPolygon(reader, getFlags(reader)))
32
+ }
33
+ return { type: 'MultiPolygon', coordinates: polygons }
34
+ } else if (flags.type === 7) { // GeometryCollection
35
+ const geometries = []
36
+ for (let i = 0; i < flags.count; i++) {
37
+ geometries.push(wkbToGeojson(reader))
38
+ }
39
+ return { type: 'GeometryCollection', geometries }
40
+ } else {
41
+ throw new Error(`Unsupported geometry type: ${flags.type}`)
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Extract ISO WKB flags and base geometry type.
47
+ *
48
+ * @param {DataReader} reader
49
+ * @returns {WkbFlags}
50
+ */
51
+ function getFlags(reader) {
52
+ const { view } = reader
53
+ const littleEndian = view.getUint8(reader.offset++) === 1
54
+ const rawType = view.getUint32(reader.offset, littleEndian)
55
+ reader.offset += 4
56
+
57
+ const type = rawType % 1000
58
+ const flags = Math.floor(rawType / 1000)
59
+
60
+ let count = 0
61
+ if (type > 1 && type <= 7) {
62
+ count = view.getUint32(reader.offset, littleEndian)
63
+ reader.offset += 4
64
+ }
65
+
66
+ // XY, XYZ, XYM, XYZM
67
+ let dim = 2
68
+ if (flags) dim++
69
+ if (flags === 3) dim++
70
+
71
+ return { littleEndian, type, dim, count }
72
+ }
73
+
74
+ /**
75
+ * @param {DataReader} reader
76
+ * @param {WkbFlags} flags
77
+ * @returns {number[]}
78
+ */
79
+ function readPosition(reader, flags) {
80
+ const points = []
81
+ for (let i = 0; i < flags.dim; i++) {
82
+ const coord = reader.view.getFloat64(reader.offset, flags.littleEndian)
83
+ reader.offset += 8
84
+ points.push(coord)
85
+ }
86
+ return points
87
+ }
88
+
89
+ /**
90
+ * @param {DataReader} reader
91
+ * @param {WkbFlags} flags
92
+ * @returns {number[][]}
93
+ */
94
+ function readLine(reader, flags) {
95
+ const points = []
96
+ for (let i = 0; i < flags.count; i++) {
97
+ points.push(readPosition(reader, flags))
98
+ }
99
+ return points
100
+ }
101
+
102
+ /**
103
+ * @param {DataReader} reader
104
+ * @param {WkbFlags} flags
105
+ * @returns {number[][][]}
106
+ */
107
+ function readPolygon(reader, flags) {
108
+ const { view } = reader
109
+ const rings = []
110
+ for (let r = 0; r < flags.count; r++) {
111
+ const count = view.getUint32(reader.offset, flags.littleEndian)
112
+ reader.offset += 4
113
+ rings.push(readLine(reader, { ...flags, count }))
114
+ }
115
+ return rings
116
+ }
117
+
118
+ /**
119
+ * @typedef {object} WkbFlags
120
+ * @property {boolean} littleEndian
121
+ * @property {number} type
122
+ * @property {number} dim
123
+ * @property {number} count
124
+ */
125
+
126
+ /**
127
+ * @import {DataReader, Geometry} from '../src/types.js'
128
+ */
web/node_modules/hyparquet/src/xxhash.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // XxHash64 (https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md)
2
+ // Used by parquet bloom filters: hashes the PLAIN encoding of column values with seed 0.
3
+
4
+ const MASK = 0xffffffffffffffffn
5
+ const PRIME1 = 0x9e3779b185ebca87n
6
+ const PRIME2 = 0xc2b2ae3d27d4eb4fn
7
+ const PRIME3 = 0x165667b19e3779f9n
8
+ const PRIME4 = 0x85ebca77c2b2ae63n
9
+ const PRIME5 = 0x27d4eb2f165667c5n
10
+
11
+ /**
12
+ * @param {bigint} x
13
+ * @param {bigint} r rotation amount in bits (1..63)
14
+ * @returns {bigint}
15
+ */
16
+ function rotl64(x, r) {
17
+ return (x << r | x >> 64n - r) & MASK
18
+ }
19
+
20
+ /**
21
+ * @param {bigint} acc
22
+ * @param {bigint} val
23
+ * @returns {bigint}
24
+ */
25
+ function round(acc, val) {
26
+ acc = acc + val * PRIME2 & MASK
27
+ acc = rotl64(acc, 31n)
28
+ return acc * PRIME1 & MASK
29
+ }
30
+
31
+ /**
32
+ * @param {bigint} acc
33
+ * @param {bigint} val
34
+ * @returns {bigint}
35
+ */
36
+ function mergeRound(acc, val) {
37
+ acc ^= round(0n, val)
38
+ return acc * PRIME1 + PRIME4 & MASK
39
+ }
40
+
41
+ /**
42
+ * Compute the 64-bit xxHash of a byte buffer.
43
+ *
44
+ * @param {Uint8Array} input
45
+ * @param {bigint} [seed]
46
+ * @returns {bigint} 64-bit hash
47
+ */
48
+ export function xxhash64(input, seed = 0n) {
49
+ const view = new DataView(input.buffer, input.byteOffset, input.byteLength)
50
+ const len = input.byteLength
51
+ let offset = 0
52
+ let h64
53
+
54
+ if (len >= 32) {
55
+ let v1 = seed + PRIME1 + PRIME2 & MASK
56
+ let v2 = seed + PRIME2 & MASK
57
+ let v3 = seed
58
+ let v4 = seed - PRIME1 & MASK
59
+
60
+ while (offset + 32 <= len) {
61
+ v1 = round(v1, view.getBigUint64(offset, true)); offset += 8
62
+ v2 = round(v2, view.getBigUint64(offset, true)); offset += 8
63
+ v3 = round(v3, view.getBigUint64(offset, true)); offset += 8
64
+ v4 = round(v4, view.getBigUint64(offset, true)); offset += 8
65
+ }
66
+
67
+ h64 = rotl64(v1, 1n) + rotl64(v2, 7n) + rotl64(v3, 12n) + rotl64(v4, 18n) & MASK
68
+ h64 = mergeRound(h64, v1)
69
+ h64 = mergeRound(h64, v2)
70
+ h64 = mergeRound(h64, v3)
71
+ h64 = mergeRound(h64, v4)
72
+ } else {
73
+ h64 = seed + PRIME5 & MASK
74
+ }
75
+
76
+ h64 = h64 + BigInt(len) & MASK
77
+
78
+ while (offset + 8 <= len) {
79
+ h64 ^= round(0n, view.getBigUint64(offset, true))
80
+ h64 = rotl64(h64, 27n) * PRIME1 + PRIME4 & MASK
81
+ offset += 8
82
+ }
83
+
84
+ if (offset + 4 <= len) {
85
+ h64 ^= BigInt(view.getUint32(offset, true)) * PRIME1 & MASK
86
+ h64 = rotl64(h64, 23n) * PRIME2 + PRIME3 & MASK
87
+ offset += 4
88
+ }
89
+
90
+ while (offset < len) {
91
+ h64 ^= BigInt(view.getUint8(offset)) * PRIME5 & MASK
92
+ h64 = rotl64(h64, 11n) * PRIME1 & MASK
93
+ offset += 1
94
+ }
95
+
96
+ h64 ^= h64 >> 33n
97
+ h64 = h64 * PRIME2 & MASK
98
+ h64 ^= h64 >> 29n
99
+ h64 = h64 * PRIME3 & MASK
100
+ h64 ^= h64 >> 32n
101
+ return h64
102
+ }
web/node_modules/hyparquet/types/assemble.d.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Reconstructs a complex nested structure from flat arrays of values and
3
+ * definition and repetition levels, according to Dremel encoding.
4
+ *
5
+ * @param {any[]} output
6
+ * @param {number[] | undefined} definitionLevels
7
+ * @param {number[]} repetitionLevels
8
+ * @param {DecodedArray} values
9
+ * @param {SchemaTree[]} schemaPath
10
+ * @returns {DecodedArray}
11
+ */
12
+ export function assembleLists(output: any[], definitionLevels: number[] | undefined, repetitionLevels: number[], values: DecodedArray, schemaPath: SchemaTree[]): DecodedArray;
13
+ /**
14
+ * Assemble a nested structure from subcolumn data.
15
+ *
16
+ * @param {Map<string, DecodedArray>} subcolumnData
17
+ * @param {SchemaTree} schema top-level schema element
18
+ * @param {ParquetParsers} parsers
19
+ * @param {number} [depth] depth of nested structure
20
+ */
21
+ export function assembleNested(subcolumnData: Map<string, DecodedArray>, schema: SchemaTree, parsers: ParquetParsers, depth?: number): void;
22
+ import type { DecodedArray } from '../src/types.js';
23
+ import type { SchemaTree } from '../src/types.js';
24
+ import type { ParquetParsers } from '../src/types.js';
25
+ //# sourceMappingURL=assemble.d.ts.map
web/node_modules/hyparquet/types/assemble.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../src/assemble.js"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,sCAPW,GAAG,EAAE,oBACL,MAAM,EAAE,GAAG,SAAS,oBACpB,MAAM,EAAE,UACR,YAAY,cACZ,UAAU,EAAE,GACV,YAAY,CA2FxB;AAED;;;;;;;GAOG;AACH,8CALW,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,UACzB,UAAU,WACV,cAAc,UACd,MAAM,QA2EhB;kCAG0D,iBAAiB;gCAAjB,iBAAiB;oCAAjB,iBAAiB"}
web/node_modules/hyparquet/types/bloom.d.ts ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Insert a hash into a Split Block Bloom Filter.
3
+ *
4
+ * @param {Uint32Array} blocks bloom filter words (8 * numBlocks long)
5
+ * @param {bigint} hash 64-bit xxhash of the parquet-plain-encoded value
6
+ */
7
+ export function sbbfInsert(blocks: Uint32Array, hash: bigint): void;
8
+ /**
9
+ * Test whether a hash might be present in a Split Block Bloom Filter.
10
+ * False positives are possible; false negatives are not.
11
+ *
12
+ * @param {Uint32Array} blocks bloom filter words (8 * numBlocks long)
13
+ * @param {bigint} hash 64-bit xxhash of the parquet-plain-encoded value
14
+ * @returns {boolean}
15
+ */
16
+ export function sbbfContains(blocks: Uint32Array, hash: bigint): boolean;
17
+ /**
18
+ * Parse a Split Block Bloom Filter from a reader positioned at the BloomFilterHeader.
19
+ * Returns undefined when the header advertises an unsupported algorithm, hash, or
20
+ * compression — callers should treat that as "cannot use this bloom filter."
21
+ *
22
+ * @param {DataReader} reader
23
+ * @returns {BloomFilter | undefined}
24
+ */
25
+ export function readBloomFilter(reader: DataReader): BloomFilter | undefined;
26
+ /**
27
+ * Hash a JS filter value as its parquet PLAIN-encoded bytes, suitable for a
28
+ * bloom filter lookup. Returns undefined when the column's parser is lossy or
29
+ * ambiguous (DATE, TIMESTAMP_*, DECIMAL, JSON, BSON, INT96, FLOAT16, UUID,
30
+ * GEOMETRY, GEOGRAPHY, INTERVAL) or when the JS value type doesn't match the
31
+ * column. Callers must treat undefined as "bloom filter cannot help."
32
+ *
33
+ * @param {any} value
34
+ * @param {SchemaElement} element
35
+ * @returns {bigint | undefined}
36
+ */
37
+ export function hashParquetValue(value: any, element: SchemaElement): bigint | undefined;
38
+ /**
39
+ * Top-level column names that appear in $eq or $in predicates within a filter.
40
+ * These are the only columns where a bloom filter can prove a value's absence
41
+ * and let us skip a row group; any other operator can't be helped by a bloom.
42
+ *
43
+ * @param {ParquetQueryFilter | undefined} filter
44
+ * @returns {Set<string>}
45
+ */
46
+ export function bloomEligibleColumns(filter: ParquetQueryFilter | undefined): Set<string>;
47
+ import type { DataReader } from '../src/types.js';
48
+ import type { BloomFilter } from '../src/types.js';
49
+ import type { SchemaElement } from '../src/types.js';
50
+ import type { ParquetQueryFilter } from '../src/types.js';
51
+ //# sourceMappingURL=bloom.d.ts.map
web/node_modules/hyparquet/types/bloom.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"bloom.d.ts","sourceRoot":"","sources":["../src/bloom.js"],"names":[],"mappings":"AA6CA;;;;;GAKG;AACH,mCAHW,WAAW,QACX,MAAM,QAQhB;AAED;;;;;;;GAOG;AACH,qCAJW,WAAW,QACX,MAAM,GACJ,OAAO,CASnB;AAED;;;;;;;GAOG;AACH,wCAHW,UAAU,GACR,WAAW,GAAG,SAAS,CAsBnC;AAED;;;;;;;;;;GAUG;AACH,wCAJW,GAAG,WACH,aAAa,GACX,MAAM,GAAG,SAAS,CAkE9B;AAED;;;;;;;GAOG;AACH,6CAHW,kBAAkB,GAAG,SAAS,GAC5B,GAAG,CAAC,MAAM,CAAC,CAOvB;gCA3L4E,iBAAiB;iCAAjB,iBAAiB;mCAAjB,iBAAiB;wCAAjB,iBAAiB"}
web/node_modules/hyparquet/types/column.d.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Parse column data from a buffer.
3
+ *
4
+ * @param {DataReader} reader
5
+ * @param {RowGroupSelect} rowGroupSelect row group selection
6
+ * @param {ColumnDecoder} columnDecoder column decoder params
7
+ * @param {(chunk: SubColumnData) => void} [onPage] callback for each page
8
+ * @returns {{ data: DecodedArray[], skipped: number }}
9
+ */
10
+ export function readColumn(reader: DataReader, { groupStart, selectStart, selectEnd }: RowGroupSelect, columnDecoder: ColumnDecoder, onPage?: (chunk: SubColumnData) => void): {
11
+ data: DecodedArray[];
12
+ skipped: number;
13
+ };
14
+ /**
15
+ * Read a page (data or dictionary) from a buffer.
16
+ *
17
+ * @param {DataReader} reader
18
+ * @param {PageHeader} header
19
+ * @param {ColumnDecoder} columnDecoder
20
+ * @param {DecodedArray | undefined} dictionary
21
+ * @param {DecodedArray | undefined} previousChunk
22
+ * @param {number} pageStart skip this many rows in the page
23
+ * @returns {PageResult}
24
+ */
25
+ export function readPage(reader: DataReader, header: PageHeader, columnDecoder: ColumnDecoder, dictionary: DecodedArray | undefined, previousChunk: DecodedArray | undefined, pageStart: number): PageResult;
26
+ import type { DataReader } from '../src/types.js';
27
+ import type { RowGroupSelect } from '../src/types.js';
28
+ import type { ColumnDecoder } from '../src/types.js';
29
+ import type { SubColumnData } from '../src/types.js';
30
+ import type { DecodedArray } from '../src/types.js';
31
+ import type { PageHeader } from '../src/types.js';
32
+ import type { PageResult } from '../src/types.js';
33
+ //# sourceMappingURL=column.d.ts.map
web/node_modules/hyparquet/types/column.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"column.d.ts","sourceRoot":"","sources":["../src/column.js"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AACH,mCANW,UAAU,0CACV,cAAc,iBACd,aAAa,WACb,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAC5B;IAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAsDrD;AAED;;;;;;;;;;GAUG;AACH,iCARW,UAAU,UACV,UAAU,iBACV,aAAa,cACb,YAAY,GAAG,SAAS,iBACxB,YAAY,GAAG,SAAS,aACxB,MAAM,GACJ,UAAU,CA4DtB;gCA/IgH,iBAAiB;oCAAjB,iBAAiB;mCAAjB,iBAAiB;mCAAjB,iBAAiB;kCAAjB,iBAAiB;gCAAjB,iBAAiB;gCAAjB,iBAAiB"}
web/node_modules/hyparquet/types/constants.d.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('../src/types.d.ts').ParquetType[]} */
2
+ export const ParquetTypes: import("../src/types.d.ts").ParquetType[];
3
+ /** @type {import('../src/types.d.ts').Encoding[]} */
4
+ export const Encodings: import("../src/types.d.ts").Encoding[];
5
+ /** @type {import('../src/types.d.ts').FieldRepetitionType[]} */
6
+ export const FieldRepetitionTypes: import("../src/types.d.ts").FieldRepetitionType[];
7
+ /** @type {import('../src/types.d.ts').ConvertedType[]} */
8
+ export const ConvertedTypes: import("../src/types.d.ts").ConvertedType[];
9
+ /** @type {import('../src/types.d.ts').CompressionCodec[]} */
10
+ export const CompressionCodecs: import("../src/types.d.ts").CompressionCodec[];
11
+ /** @type {import('../src/types.d.ts').PageType[]} */
12
+ export const PageTypes: import("../src/types.d.ts").PageType[];
13
+ /** @type {import('../src/types.d.ts').BoundaryOrder[]} */
14
+ export const BoundaryOrders: import("../src/types.d.ts").BoundaryOrder[];
15
+ /** @type {import('../src/types.d.ts').EdgeInterpolationAlgorithm[]} */
16
+ export const EdgeInterpolationAlgorithms: import("../src/types.d.ts").EdgeInterpolationAlgorithm[];
17
+ //# sourceMappingURL=constants.d.ts.map
web/node_modules/hyparquet/types/constants.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.js"],"names":[],"mappings":"AACA,wDAAwD;AACxD,2BADW,OAAO,mBAAmB,EAAE,WAAW,EAAE,CAUnD;AAED,qDAAqD;AACrD,wBADW,OAAO,mBAAmB,EAAE,QAAQ,EAAE,CAYhD;AAED,gEAAgE;AAChE,mCADW,OAAO,mBAAmB,EAAE,mBAAmB,EAAE,CAK3D;AAED,0DAA0D;AAC1D,6BADW,OAAO,mBAAmB,EAAE,aAAa,EAAE,CAwBrD;AAED,6DAA6D;AAC7D,gCADW,OAAO,mBAAmB,EAAE,gBAAgB,EAAE,CAUxD;AAED,qDAAqD;AACrD,wBADW,OAAO,mBAAmB,EAAE,QAAQ,EAAE,CAMhD;AAED,0DAA0D;AAC1D,6BADW,OAAO,mBAAmB,EAAE,aAAa,EAAE,CAKrD;AAED,uEAAuE;AACvE,0CADW,OAAO,mBAAmB,EAAE,0BAA0B,EAAE,CAOlE"}
web/node_modules/hyparquet/types/convert.d.ts ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Convert known types from primitive to rich, and dereference dictionary.
3
+ *
4
+ * @param {DecodedArray} data series of primitive types
5
+ * @param {DecodedArray | undefined} dictionary
6
+ * @param {Encoding} encoding
7
+ * @param {ColumnDecoder} columnDecoder
8
+ * @returns {DecodedArray} series of rich types
9
+ */
10
+ export function convertWithDictionary(data: DecodedArray, dictionary: DecodedArray | undefined, encoding: Encoding, columnDecoder: ColumnDecoder): DecodedArray;
11
+ /**
12
+ * Convert known types from primitive to rich.
13
+ *
14
+ * @param {DecodedArray} data series of primitive types
15
+ * @param {ColumnDecoder} columnDecoder
16
+ * @returns {DecodedArray} series of rich types
17
+ */
18
+ export function convert(data: DecodedArray, columnDecoder: ColumnDecoder): DecodedArray;
19
+ /**
20
+ * @param {Uint8Array} bytes
21
+ * @returns {number}
22
+ */
23
+ export function parseDecimal(bytes: Uint8Array): number;
24
+ /**
25
+ * @param {Uint8Array | undefined} bytes
26
+ * @returns {number | undefined}
27
+ */
28
+ export function parseFloat16(bytes: Uint8Array | undefined): number | undefined;
29
+ /**
30
+ * Default type parsers when no custom ones are given
31
+ * @type ParquetParsers
32
+ */
33
+ export const DEFAULT_PARSERS: ParquetParsers;
34
+ import type { DecodedArray } from '../src/types.js';
35
+ import type { Encoding } from '../src/types.js';
36
+ import type { ColumnDecoder } from '../src/types.js';
37
+ import type { ParquetParsers } from '../src/types.js';
38
+ //# sourceMappingURL=convert.d.ts.map
web/node_modules/hyparquet/types/convert.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.js"],"names":[],"mappings":"AA4CA;;;;;;;;GAQG;AACH,4CANW,YAAY,cACZ,YAAY,GAAG,SAAS,YACxB,QAAQ,iBACR,aAAa,GACX,YAAY,CAgBxB;AAED;;;;;;GAMG;AACH,8BAJW,YAAY,iBACZ,aAAa,GACX,YAAY,CAwFxB;AAED;;;GAGG;AACH,oCAHW,UAAU,GACR,MAAM,CAiBlB;AAaD;;;GAGG;AACH,oCAHW,UAAU,GAAG,SAAS,GACpB,MAAM,GAAG,SAAS,CAW9B;AAzMD;;;GAGG;AACH,8BAFS,cAAc,CAgCtB;kCAvCuE,iBAAiB;8BAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB"}
web/node_modules/hyparquet/types/datapage.d.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Read a data page from uncompressed reader.
3
+ *
4
+ * @param {Uint8Array} bytes raw page data (should already be decompressed)
5
+ * @param {DataPageHeader} daph data page header
6
+ * @param {ColumnDecoder} columnDecoder
7
+ * @returns {DataPage} definition levels, repetition levels, and array of values
8
+ */
9
+ export function readDataPage(bytes: Uint8Array, daph: DataPageHeader, { type, element, schemaPath }: ColumnDecoder): DataPage;
10
+ /**
11
+ * @param {Uint8Array} compressedBytes
12
+ * @param {number} uncompressed_page_size
13
+ * @param {CompressionCodec} codec
14
+ * @param {Compressors | undefined} compressors
15
+ * @returns {Uint8Array}
16
+ */
17
+ export function decompressPage(compressedBytes: Uint8Array, uncompressed_page_size: number, codec: CompressionCodec, compressors: Compressors | undefined): Uint8Array;
18
+ /**
19
+ * Read a data page from the given Uint8Array.
20
+ *
21
+ * @param {Uint8Array} compressedBytes raw page data
22
+ * @param {PageHeader} ph page header
23
+ * @param {ColumnDecoder} columnDecoder
24
+ * @returns {DataPage} definition levels, repetition levels, and array of values
25
+ */
26
+ export function readDataPageV2(compressedBytes: Uint8Array, ph: PageHeader, columnDecoder: ColumnDecoder): DataPage;
27
+ import type { DataPageHeader } from '../src/types.js';
28
+ import type { ColumnDecoder } from '../src/types.js';
29
+ import type { DataPage } from '../src/types.js';
30
+ import type { CompressionCodec } from '../src/types.js';
31
+ import type { Compressors } from '../src/types.js';
32
+ import type { PageHeader } from '../src/types.js';
33
+ //# sourceMappingURL=datapage.d.ts.map
web/node_modules/hyparquet/types/datapage.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"datapage.d.ts","sourceRoot":"","sources":["../src/datapage.js"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AACH,oCALW,UAAU,QACV,cAAc,iCACd,aAAa,GACX,QAAQ,CAkDpB;AA2CD;;;;;;GAMG;AACH,gDANW,UAAU,0BACV,MAAM,SACN,gBAAgB,eAChB,WAAW,GAAG,SAAS,GACrB,UAAU,CAoBtB;AAGD;;;;;;;GAOG;AACH,gDALW,UAAU,MACV,UAAU,iBACV,aAAa,GACX,QAAQ,CA6DpB;oCA3M4J,iBAAiB;mCAAjB,iBAAiB;8BAAjB,iBAAiB;sCAAjB,iBAAiB;iCAAjB,iBAAiB;gCAAjB,iBAAiB"}
web/node_modules/hyparquet/types/delta.d.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @param {DataReader} reader
3
+ * @param {number} count number of values to read
4
+ * @param {Int32Array | BigInt64Array} output
5
+ */
6
+ export function deltaBinaryUnpack(reader: DataReader, count: number, output: Int32Array | BigInt64Array): void;
7
+ /**
8
+ * @param {DataReader} reader
9
+ * @param {number} count
10
+ * @param {Uint8Array[]} output
11
+ */
12
+ export function deltaLengthByteArray(reader: DataReader, count: number, output: Uint8Array[]): void;
13
+ /**
14
+ * @param {DataReader} reader
15
+ * @param {number} count
16
+ * @param {Uint8Array[]} output
17
+ */
18
+ export function deltaByteArray(reader: DataReader, count: number, output: Uint8Array[]): void;
19
+ import type { DataReader } from '../src/types.js';
20
+ //# sourceMappingURL=delta.d.ts.map
web/node_modules/hyparquet/types/delta.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../src/delta.js"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,0CAJW,UAAU,SACV,MAAM,UACN,UAAU,GAAG,aAAa,QAuDpC;AAED;;;;GAIG;AACH,6CAJW,UAAU,SACV,MAAM,UACN,UAAU,EAAE,QAStB;AAED;;;;GAIG;AACH,uCAJW,UAAU,SACV,MAAM,UACN,UAAU,EAAE,QAoBtB;gCAtG4B,iBAAiB"}
web/node_modules/hyparquet/types/encoding.d.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Read values from a run-length encoded/bit-packed hybrid encoding.
3
+ *
4
+ * If length is zero, then read int32 length at the start.
5
+ *
6
+ * @param {DataReader} reader
7
+ * @param {number} width - bitwidth
8
+ * @param {DecodedArray} output
9
+ * @param {number} [length] - length of the encoded data
10
+ */
11
+ export function readRleBitPackedHybrid(reader: DataReader, width: number, output: DecodedArray, length?: number): void;
12
+ /**
13
+ * @param {DataReader} reader
14
+ * @param {number} count
15
+ * @param {ParquetType} type
16
+ * @param {number | undefined} typeLength
17
+ * @returns {DecodedArray}
18
+ */
19
+ export function byteStreamSplit(reader: DataReader, count: number, type: ParquetType, typeLength: number | undefined): DecodedArray;
20
+ import type { DataReader } from '../src/types.js';
21
+ import type { DecodedArray } from '../src/types.js';
22
+ import type { ParquetType } from '../src/types.js';
23
+ //# sourceMappingURL=encoding.d.ts.map
web/node_modules/hyparquet/types/encoding.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../src/encoding.js"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,+CALW,UAAU,SACV,MAAM,UACN,YAAY,WACZ,MAAM,QAsBhB;AA2ED;;;;;;GAMG;AACH,wCANW,UAAU,SACV,MAAM,QACN,WAAW,cACX,MAAM,GAAG,SAAS,GAChB,YAAY,CAwBxB;gCA3IuD,iBAAiB;kCAAjB,iBAAiB;iCAAjB,iBAAiB"}
web/node_modules/hyparquet/types/filter.d.ts ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Returns an array of top-level column names needed to evaluate the filter.
3
+ *
4
+ * @param {ParquetQueryFilter} [filter]
5
+ * @returns {string[]}
6
+ */
7
+ export function columnsNeededForFilter(filter?: ParquetQueryFilter): string[];
8
+ /**
9
+ * Match a record against a query filter
10
+ *
11
+ * @param {Record<string, any>} record
12
+ * @param {ParquetQueryFilter} filter
13
+ * @param {boolean} [strict]
14
+ * @returns {boolean}
15
+ */
16
+ export function matchFilter(record: Record<string, any>, filter: ParquetQueryFilter, strict?: boolean): boolean;
17
+ /**
18
+ * Check if a row group can be skipped based on filter and column statistics,
19
+ * optionally consulting per-column bloom filters for equality predicates that
20
+ * statistics can't decide.
21
+ *
22
+ * @param {object} options
23
+ * @param {RowGroup} options.rowGroup
24
+ * @param {string[]} options.physicalColumns
25
+ * @param {ParquetQueryFilter | undefined} options.filter
26
+ * @param {boolean} [options.strict]
27
+ * @param {Record<string, BloomFilter>} [options.bloomFilters] keyed by top-level column name
28
+ * @param {Record<string, SchemaElement>} [options.schemaElements] keyed by top-level column name, required to use bloomFilters
29
+ * @returns {boolean} true if the row group can be skipped
30
+ */
31
+ export function canSkipRowGroup({ rowGroup, physicalColumns, filter, strict, bloomFilters, schemaElements }: {
32
+ rowGroup: RowGroup;
33
+ physicalColumns: string[];
34
+ filter: ParquetQueryFilter | undefined;
35
+ strict?: boolean | undefined;
36
+ bloomFilters?: Record<string, BloomFilter> | undefined;
37
+ schemaElements?: Record<string, SchemaElement> | undefined;
38
+ }): boolean;
39
+ import type { ParquetQueryFilter } from '../src/types.js';
40
+ import type { RowGroup } from '../src/types.js';
41
+ import type { BloomFilter } from '../src/types.js';
42
+ import type { SchemaElement } from '../src/types.js';
43
+ //# sourceMappingURL=filter.d.ts.map
web/node_modules/hyparquet/types/filter.d.ts.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../src/filter.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,gDAHW,kBAAkB,GAChB,MAAM,EAAE,CAiBpB;AAED;;;;;;;GAOG;AACH,oCALW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,UACnB,kBAAkB,WAClB,OAAO,GACL,OAAO,CAkCnB;AAED;;;;;;;;;;;;;GAaG;AACH,6GARG;IAA0B,QAAQ,EAA1B,QAAQ;IACU,eAAe,EAAjC,MAAM,EAAE;IACgC,MAAM,EAA9C,kBAAkB,GAAG,SAAS;IACZ,MAAM;IACc,YAAY;IACV,cAAc;CAC9D,GAAU,OAAO,CAsEnB;wCAzJ0E,iBAAiB;8BAAjB,iBAAiB;iCAAjB,iBAAiB;mCAAjB,iBAAiB"}