lakeql 0.1.0 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1694 @@
1
+ import { Lake, stableStringify, vectorValue, scalarVectorValue, batchFromColumns, withObjectStoreReadControls, throwIfAborted, readControlSignal, readParquetObjectBatches, readIcebergParquetDeletes } from './chunk-RZL45ZSN.js';
2
+ import { LakeqlError, jsonSafeValue, matches } from './chunk-TFD5RFKB.js';
3
+
4
+ // ../core/dist/in-memory.js
5
+ var InMemoryRowsScanner = class {
6
+ tables;
7
+ constructor(tables, options = {}) {
8
+ this.tables = normalizeInMemoryTables(tables, options);
9
+ }
10
+ async *scan(path, options) {
11
+ const table = this.table(path);
12
+ const batchSize = options.batchSize;
13
+ for (let start = 0; start < table.rows.length; start += batchSize) {
14
+ const out = [];
15
+ for (const row of table.rows.slice(start, start + batchSize)) {
16
+ out.push(projectPhysicalColumns(row, options.columns));
17
+ }
18
+ yield out;
19
+ }
20
+ }
21
+ async planTask(path) {
22
+ return { rowGroupRanges: [{ start: 0, end: this.table(path).rows.length }] };
23
+ }
24
+ tableInfo(path) {
25
+ const table = this.table(path);
26
+ return {
27
+ path: table.path,
28
+ size: table.size,
29
+ etag: table.etag,
30
+ lastModified: table.lastModified
31
+ };
32
+ }
33
+ listTables(prefix, options = {}) {
34
+ const out = [];
35
+ for (const table of [...this.tables.values()].sort((left, right) => left.path.localeCompare(right.path))) {
36
+ if (!table.path.startsWith(prefix))
37
+ continue;
38
+ out.push({
39
+ path: table.path,
40
+ size: table.size,
41
+ etag: table.etag,
42
+ lastModified: table.lastModified
43
+ });
44
+ if (options.limit !== void 0 && out.length >= options.limit)
45
+ break;
46
+ }
47
+ return out;
48
+ }
49
+ table(path) {
50
+ const table = this.tables.get(path);
51
+ if (table === void 0) {
52
+ throw new LakeqlError("LAKEQL_UNKNOWN_TABLE", `No in-memory table named ${path}`, { path });
53
+ }
54
+ return table;
55
+ }
56
+ };
57
+ var InMemoryRowsStore = class {
58
+ scanner;
59
+ constructor(scanner) {
60
+ this.scanner = scanner;
61
+ }
62
+ async get(path) {
63
+ this.scanner.tableInfo(path);
64
+ return null;
65
+ }
66
+ async getRange(path) {
67
+ this.scanner.tableInfo(path);
68
+ return new Uint8Array();
69
+ }
70
+ async put() {
71
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "In-memory row stores are read-only");
72
+ }
73
+ async delete() {
74
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "In-memory row stores are read-only");
75
+ }
76
+ async *list(prefix, options) {
77
+ yield* this.scanner.listTables(prefix, options);
78
+ }
79
+ async head(path) {
80
+ try {
81
+ const table = this.scanner.tableInfo(path);
82
+ const head = {
83
+ size: table.size,
84
+ contentType: "application/vnd.lakeql.rows+json"
85
+ };
86
+ if (table.etag !== void 0)
87
+ head.etag = table.etag;
88
+ if (table.lastModified !== void 0)
89
+ head.lastModified = table.lastModified;
90
+ return head;
91
+ } catch (cause) {
92
+ if (cause instanceof LakeqlError && cause.code === "LAKEQL_UNKNOWN_TABLE")
93
+ return null;
94
+ throw cause;
95
+ }
96
+ }
97
+ };
98
+ function inMemoryRowsScanner(tables, options = {}) {
99
+ return new InMemoryRowsScanner(tables, options);
100
+ }
101
+ function createInMemoryLake(tables, options = {}) {
102
+ const { maxRows, maxBytes, ...lakeOptions } = options;
103
+ const tableOptions = {};
104
+ if (maxRows !== void 0)
105
+ tableOptions.maxRows = maxRows;
106
+ if (maxBytes !== void 0)
107
+ tableOptions.maxBytes = maxBytes;
108
+ const scanner = inMemoryRowsScanner(tables, tableOptions);
109
+ return new Lake({
110
+ ...lakeOptions,
111
+ store: new InMemoryRowsStore(scanner),
112
+ scanner
113
+ });
114
+ }
115
+ function normalizeInMemoryTables(tables, options) {
116
+ const normalized = /* @__PURE__ */ new Map();
117
+ let totalRows = 0;
118
+ let totalBytes = 0;
119
+ for (const [path, rows] of Object.entries(tables)) {
120
+ if (path.trim() === "") {
121
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "In-memory table names must be non-empty");
122
+ }
123
+ const copiedRows = rows.map((row) => ({ ...row }));
124
+ const size = estimateRowsBytes(copiedRows);
125
+ totalRows += copiedRows.length;
126
+ totalBytes += size;
127
+ enforceInMemoryTableBudget(options, totalRows, totalBytes);
128
+ normalized.set(path, {
129
+ path,
130
+ rows: copiedRows,
131
+ size,
132
+ etag: `memory-${stableStringify([path, copiedRows.length, size])}`,
133
+ lastModified: /* @__PURE__ */ new Date(0)
134
+ });
135
+ }
136
+ return normalized;
137
+ }
138
+ function enforceInMemoryTableBudget(options, rows, bytes) {
139
+ if (options.maxRows !== void 0 && rows > options.maxRows) {
140
+ throw new LakeqlError("LAKEQL_BUDGET_EXCEEDED", `In-memory ingest exceeded row budget (${rows} > ${options.maxRows})`, { metric: "ingest rows", limit: options.maxRows, actual: rows });
141
+ }
142
+ if (options.maxBytes !== void 0 && bytes > options.maxBytes) {
143
+ throw new LakeqlError("LAKEQL_BUDGET_EXCEEDED", `In-memory ingest exceeded byte budget (${bytes} > ${options.maxBytes})`, { metric: "ingest bytes", limit: options.maxBytes, actual: bytes });
144
+ }
145
+ }
146
+ function estimateRowsBytes(rows) {
147
+ return new TextEncoder().encode(stableStringify(jsonSafeValue(rows))).byteLength;
148
+ }
149
+ function projectPhysicalColumns(row, columns) {
150
+ if (columns === void 0)
151
+ return { ...row };
152
+ const out = {};
153
+ for (const column of columns) {
154
+ if (column in row)
155
+ out[column] = row[column];
156
+ }
157
+ return out;
158
+ }
159
+
160
+ // ../core/dist/vector-join.js
161
+ function vectorHashJoin(left, right, options) {
162
+ const normalized = validateVectorJoinOptions(left, right, options);
163
+ const rightIndices = selectedIndices(right.rowCount, options.rightSelection);
164
+ enforceMaxRightRows(rightIndices.length, options.maxRightRows);
165
+ const index = buildRightIndex(right, rightIndices, normalized.rightKeys);
166
+ const output = createOutputColumns(left, right, normalized);
167
+ for (const leftIndex of selectedIndices(left.rowCount, options.leftSelection)) {
168
+ const matches2 = index.get(joinKey(left, leftIndex, normalized.leftKeys));
169
+ if (normalized.type === "semi") {
170
+ if (matches2 !== void 0 && matches2.length > 0)
171
+ appendLeftOnly(output, left, leftIndex);
172
+ continue;
173
+ }
174
+ if (normalized.type === "anti") {
175
+ if (matches2 === void 0 || matches2.length === 0)
176
+ appendLeftOnly(output, left, leftIndex);
177
+ continue;
178
+ }
179
+ if (matches2 === void 0 || matches2.length === 0) {
180
+ if (normalized.type === "left")
181
+ appendLeftWithNullRight(output, left, right, leftIndex, normalized);
182
+ continue;
183
+ }
184
+ for (const rightIndex of matches2)
185
+ appendJoined(output, left, right, leftIndex, rightIndex, normalized);
186
+ }
187
+ return batchFromOutput(output);
188
+ }
189
+ function validateVectorJoinOptions(left, right, options) {
190
+ const leftKeys = normalizeJoinKeys(options.leftKey, "leftKey");
191
+ const rightKeys = normalizeJoinKeys(options.rightKey, "rightKey");
192
+ if (leftKeys.length !== rightKeys.length) {
193
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Vector join key counts must match", {
194
+ leftKey: options.leftKey,
195
+ rightKey: options.rightKey
196
+ });
197
+ }
198
+ if (!Number.isInteger(options.maxRightRows) || options.maxRightRows < 1) {
199
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Vector join maxRightRows must be a positive integer");
200
+ }
201
+ const type = options.type ?? "inner";
202
+ if (type !== "inner" && type !== "left" && type !== "semi" && type !== "anti") {
203
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Vector join type is not supported", { type });
204
+ }
205
+ for (const key of leftKeys)
206
+ assertColumn(left, key);
207
+ for (const key of rightKeys)
208
+ assertColumn(right, key);
209
+ const rightPrefix = options.rightPrefix ?? "right.";
210
+ return {
211
+ leftKeys,
212
+ rightKeys,
213
+ type,
214
+ rightPrefix,
215
+ outputRightColumns: type === "semi" || type === "anti" ? [] : outputRightColumns(left, right, leftKeys, rightKeys, rightPrefix)
216
+ };
217
+ }
218
+ function buildRightIndex(right, rightIndices, rightKeys) {
219
+ const index = /* @__PURE__ */ new Map();
220
+ for (const rightIndex of rightIndices) {
221
+ const key = joinKey(right, rightIndex, rightKeys);
222
+ const bucket = index.get(key);
223
+ if (bucket === void 0)
224
+ index.set(key, [rightIndex]);
225
+ else
226
+ bucket.push(rightIndex);
227
+ }
228
+ return index;
229
+ }
230
+ function createOutputColumns(left, _right, options) {
231
+ const output = {};
232
+ for (const column of Object.keys(left.columns))
233
+ output[column] = [];
234
+ for (const { output: column } of options.outputRightColumns)
235
+ output[column] = [];
236
+ return output;
237
+ }
238
+ function appendJoined(output, left, right, leftIndex, rightIndex, options) {
239
+ appendLeft(output, left, leftIndex);
240
+ appendRight(output, right, rightIndex, options);
241
+ }
242
+ function appendLeftOnly(output, left, leftIndex) {
243
+ appendLeft(output, left, leftIndex);
244
+ }
245
+ function appendLeftWithNullRight(output, left, right, leftIndex, options) {
246
+ appendLeft(output, left, leftIndex);
247
+ for (const { output: column } of options.outputRightColumns)
248
+ output[column]?.push(null);
249
+ }
250
+ function appendLeft(output, left, leftIndex) {
251
+ for (const [column, vector] of Object.entries(left.columns)) {
252
+ output[column]?.push(vectorValue(vector, leftIndex));
253
+ }
254
+ }
255
+ function appendRight(output, right, rightIndex, options) {
256
+ for (const { input, output: column } of options.outputRightColumns) {
257
+ const vector = right.columns[input];
258
+ if (vector === void 0)
259
+ throw new LakeqlError("LAKEQL_UNKNOWN_COLUMN", `Unknown column ${input}`, { column: input });
260
+ output[column]?.push(vectorValue(vector, rightIndex));
261
+ }
262
+ }
263
+ function outputRightColumns(left, right, leftKeys, rightKeys, rightPrefix) {
264
+ return Object.keys(right.columns).flatMap((column) => {
265
+ if (rightKeys.includes(column) && leftKeys.includes(column))
266
+ return [];
267
+ return [{ input: column, output: column in left.columns ? `${rightPrefix}${column}` : column }];
268
+ });
269
+ }
270
+ function joinKey(batch, index, keys) {
271
+ const values = keys.map((key) => scalarJoinValue(batch, index, key));
272
+ return stableStringify(values.length === 1 ? values[0] : values);
273
+ }
274
+ function scalarJoinValue(batch, index, column) {
275
+ const vector = batch.columns[column];
276
+ if (vector === void 0)
277
+ throw new LakeqlError("LAKEQL_UNKNOWN_COLUMN", `Unknown join key ${column}`, { column });
278
+ return scalarVectorValue(vector, index);
279
+ }
280
+ function normalizeJoinKeys(key, label) {
281
+ const keys = Array.isArray(key) ? key : [key];
282
+ if (keys.length === 0 || keys.some((column) => typeof column !== "string" || column.length === 0)) {
283
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", `Vector join ${label} must contain column names`, {
284
+ [label]: key
285
+ });
286
+ }
287
+ return keys;
288
+ }
289
+ function assertColumn(batch, column) {
290
+ if (batch.columns[column] === void 0) {
291
+ throw new LakeqlError("LAKEQL_UNKNOWN_COLUMN", `Unknown join key ${column}`, { column });
292
+ }
293
+ }
294
+ function selectedIndices(rowCount, selection) {
295
+ const indices = [];
296
+ for (let index = 0; index < rowCount; index += 1) {
297
+ if (selection !== void 0 && selection[index] !== 1)
298
+ continue;
299
+ indices.push(index);
300
+ }
301
+ return indices;
302
+ }
303
+ function enforceMaxRightRows(actual, limit) {
304
+ if (actual <= limit)
305
+ return;
306
+ throw new LakeqlError("LAKEQL_BUDGET_EXCEEDED", `Vector join exceeded maxRightRows (${actual} > ${limit})`, { metric: "maxRightRows", limit, actual });
307
+ }
308
+ function batchFromOutput(output) {
309
+ return batchFromColumns(output);
310
+ }
311
+
312
+ // ../iceberg/dist/index.js
313
+ var PACKAGE = "lakeql-iceberg";
314
+ var IcebergTable = class {
315
+ store;
316
+ metadataPath;
317
+ metadata;
318
+ constructor(store, metadataPath, metadata) {
319
+ this.store = store;
320
+ this.metadataPath = metadataPath;
321
+ this.metadata = metadata;
322
+ }
323
+ snapshot(options = {}) {
324
+ if (options.snapshotId !== void 0)
325
+ return this.snapshotById(options.snapshotId);
326
+ if (options.ref !== void 0) {
327
+ const ref = this.metadata.refs?.[options.ref];
328
+ if (!ref) {
329
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `Unknown Iceberg ref ${options.ref}`, {
330
+ ref: options.ref
331
+ });
332
+ }
333
+ return this.snapshotById(ref["snapshot-id"]);
334
+ }
335
+ if (options.asOfTimestampMs !== void 0) {
336
+ const snapshot = [...this.metadata.snapshots].filter((candidate) => candidate["timestamp-ms"] <= options.asOfTimestampMs).sort((a, b) => b["timestamp-ms"] - a["timestamp-ms"])[0];
337
+ if (!snapshot) {
338
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "No Iceberg snapshot at requested timestamp", {
339
+ asOfTimestampMs: options.asOfTimestampMs
340
+ });
341
+ }
342
+ return snapshot;
343
+ }
344
+ return this.snapshotById(this.metadata["current-snapshot-id"]);
345
+ }
346
+ schema(schemaId) {
347
+ const schema = this.metadata.schemas.find((candidate) => candidate["schema-id"] === schemaId);
348
+ if (!schema) {
349
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `Unknown Iceberg schema ${schemaId}`, {
350
+ schemaId
351
+ });
352
+ }
353
+ return schema.fields;
354
+ }
355
+ projectRow(row, options = {}) {
356
+ const snapshot = this.snapshot(options);
357
+ const fields = this.schema(snapshot["schema-id"]);
358
+ projectedIds(fields, options.select);
359
+ const selected = options.select === void 0 ? void 0 : new Set(options.select);
360
+ const out = {};
361
+ for (const field of fields) {
362
+ if (selected !== void 0 && !selected.has(field.name))
363
+ continue;
364
+ const sourceName = field.name in row ? field.name : field.sourceId !== void 0 ? this.fieldNameById(field.sourceId) : field.name;
365
+ out[field.name] = sourceName !== void 0 && sourceName in row ? row[sourceName] : null;
366
+ }
367
+ return out;
368
+ }
369
+ planFiles(options = {}) {
370
+ const snapshot = this.snapshot(options);
371
+ const fields = this.schema(snapshot["schema-id"]);
372
+ const projectedFieldIds = projectedIds(fields, options.select);
373
+ const readMode = options.readMode ?? "strict";
374
+ const files = [];
375
+ let manifestsSkipped = 0;
376
+ let filesSkipped = 0;
377
+ let deleteFilesPlanned = 0;
378
+ let deleteFilesIgnored = 0;
379
+ const manifests = snapshotManifests(snapshot);
380
+ for (const manifest of manifests) {
381
+ const manifestMayMatch = manifest.files.some((file) => partitionMayMatch(options.where, file.partition ?? {}));
382
+ if (!manifestMayMatch) {
383
+ manifestsSkipped += 1;
384
+ filesSkipped += manifest.files.length;
385
+ continue;
386
+ }
387
+ for (const file of manifest.files) {
388
+ if (!partitionMayMatch(options.where, file.partition ?? {})) {
389
+ filesSkipped += 1;
390
+ continue;
391
+ }
392
+ const supportedDeleteFiles = supportedIcebergDeleteFiles(file.deleteFiles);
393
+ const unsupportedDeleteFiles = unsupportedIcebergDeleteFiles(file.deleteFiles);
394
+ if (unsupportedDeleteFiles.length > 0 && readMode === "strict") {
395
+ throw new LakeqlError("LAKEQL_UNSUPPORTED_DELETE_FILES", "Snapshot contains delete files unsupported by strict Iceberg planning", {
396
+ path: file.path,
397
+ deleteFiles: file.deleteFiles,
398
+ supportedDeleteFiles,
399
+ unsupportedDeleteFiles
400
+ });
401
+ }
402
+ const planned = {
403
+ path: file.path,
404
+ sequenceNumber: file.sequenceNumber,
405
+ partition: file.partition ?? {},
406
+ recordCount: file.recordCount,
407
+ projectedFieldIds,
408
+ snapshotId: snapshot["snapshot-id"]
409
+ };
410
+ if (file.fileSizeInBytes !== void 0)
411
+ planned.fileSizeInBytes = file.fileSizeInBytes;
412
+ if ((readMode === "strict" || readMode === "ignore-unsupported-deletes") && supportedDeleteFiles.length > 0) {
413
+ planned.deleteFiles = supportedDeleteFiles;
414
+ deleteFilesPlanned += supportedDeleteFiles.length;
415
+ }
416
+ if (readMode === "ignore-unsupported-deletes") {
417
+ deleteFilesIgnored += unsupportedDeleteFiles.length;
418
+ } else if (readMode === "ignore-deletes") {
419
+ deleteFilesIgnored += (file.deleteFiles ?? []).length;
420
+ }
421
+ files.push(planned);
422
+ }
423
+ }
424
+ files.sort((a, b) => a.sequenceNumber - b.sequenceNumber || a.path.localeCompare(b.path));
425
+ return {
426
+ snapshotId: snapshot["snapshot-id"],
427
+ schemaId: snapshot["schema-id"],
428
+ manifestsRead: manifests.length - manifestsSkipped,
429
+ manifestsSkipped,
430
+ filesPlanned: files.length,
431
+ filesSkipped,
432
+ deleteFilesPlanned,
433
+ deleteFilesIgnored,
434
+ files
435
+ };
436
+ }
437
+ async appendFiles(options) {
438
+ if (options.files.length === 0) {
439
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Iceberg append requires at least one file");
440
+ }
441
+ if (this.metadata["format-version"] !== 2) {
442
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Iceberg append requires format-version 2 metadata", { formatVersion: this.metadata["format-version"] });
443
+ }
444
+ const currentSnapshot = this.snapshot();
445
+ const nextSnapshotId = options.nextSnapshotId ?? randomSnapshotId(this.metadata.snapshots.map(snapshotIdOf));
446
+ validateNewSnapshotId(nextSnapshotId, this.metadata.snapshots);
447
+ const nextSequenceNumber = maxSequenceNumber(this.metadata) + 1;
448
+ const manifestPath = appendManifestPath(this.metadataPath, options.jobId, nextSnapshotId);
449
+ const manifest = {
450
+ path: manifestPath,
451
+ files: options.files.map((file, index) => ({
452
+ path: file.path,
453
+ sequenceNumber: nextSequenceNumber + index,
454
+ partition: sortStringRecord(file.partition ?? {}),
455
+ recordCount: file.recordCount,
456
+ fileSizeInBytes: file.fileSizeInBytes
457
+ }))
458
+ };
459
+ const nextSnapshot = {
460
+ "snapshot-id": nextSnapshotId,
461
+ "timestamp-ms": options.nowMs ?? Date.now(),
462
+ "schema-id": currentSnapshot["schema-id"],
463
+ manifests: [...snapshotManifests(currentSnapshot).map(cloneManifest), manifest]
464
+ };
465
+ const metadata = cloneMetadata(this.metadata);
466
+ metadata["current-snapshot-id"] = nextSnapshotId;
467
+ metadata.snapshots.push(nextSnapshot);
468
+ metadata.refs = {
469
+ ...metadata.refs ?? {},
470
+ main: { type: "branch", "snapshot-id": nextSnapshotId }
471
+ };
472
+ const nextMetadataPath = nextMetadataPathFor(this.metadataPath, nextSnapshotId);
473
+ const catalog = options.catalog ?? new ObjectStoreIcebergCommitCatalog();
474
+ const commit = await catalog.commitAppend({
475
+ store: this.store,
476
+ currentMetadataPath: this.metadataPath,
477
+ nextMetadataPath,
478
+ expectedSnapshotId: currentSnapshot["snapshot-id"],
479
+ nextSnapshotId,
480
+ manifestPath,
481
+ manifest,
482
+ metadata
483
+ });
484
+ const committed = typeof commit === "boolean" ? commit : commit.committed;
485
+ if (!committed) {
486
+ throw new LakeqlError("LAKEQL_ICEBERG_COMMIT_CONFLICT", "Iceberg append commit conflict", {
487
+ metadataPath: this.metadataPath,
488
+ expectedSnapshotId: currentSnapshot["snapshot-id"],
489
+ nextSnapshotId
490
+ });
491
+ }
492
+ const committedMetadataPath = typeof commit === "boolean" ? nextMetadataPath : commit.metadataPath ?? nextMetadataPath;
493
+ return {
494
+ snapshotId: nextSnapshotId,
495
+ previousSnapshotId: currentSnapshot["snapshot-id"],
496
+ metadataPath: committedMetadataPath,
497
+ manifestPath,
498
+ files: manifest.files.map((file) => ({
499
+ path: file.path,
500
+ sequenceNumber: file.sequenceNumber,
501
+ partition: file.partition ?? {},
502
+ recordCount: file.recordCount,
503
+ projectedFieldIds: [],
504
+ snapshotId: nextSnapshotId
505
+ }))
506
+ };
507
+ }
508
+ async appendOutputManifest(options) {
509
+ const files = options.manifest.entries.map((entry) => {
510
+ if (entry.iceberg === void 0) {
511
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Output manifest entry is missing Iceberg file metadata", {
512
+ taskId: entry.taskId,
513
+ outputPath: entry.outputPath
514
+ });
515
+ }
516
+ return {
517
+ path: entry.outputPath,
518
+ partition: entry.iceberg.partitionValues,
519
+ recordCount: entry.iceberg.recordCount,
520
+ fileSizeInBytes: entry.iceberg.fileSizeInBytes
521
+ };
522
+ });
523
+ return await this.appendFiles({
524
+ files,
525
+ jobId: options.manifest.jobId,
526
+ ...options.nowMs !== void 0 ? { nowMs: options.nowMs } : {},
527
+ ...options.nextSnapshotId !== void 0 ? { nextSnapshotId: options.nextSnapshotId } : {},
528
+ ...options.catalog !== void 0 ? { catalog: options.catalog } : {}
529
+ });
530
+ }
531
+ snapshotById(snapshotId) {
532
+ const snapshot = this.metadata.snapshots.find((candidate) => candidate["snapshot-id"] === snapshotId);
533
+ if (!snapshot) {
534
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `Unknown Iceberg snapshot ${snapshotId}`, {
535
+ snapshotId
536
+ });
537
+ }
538
+ return snapshot;
539
+ }
540
+ fieldNameById(fieldId) {
541
+ for (const schema of this.metadata.schemas) {
542
+ const field = schema.fields.find((candidate) => candidate.id === fieldId);
543
+ if (field)
544
+ return field.name;
545
+ }
546
+ return void 0;
547
+ }
548
+ };
549
+ function planFiles(table, options = {}) {
550
+ return table.planFiles(options);
551
+ }
552
+ var ObjectStoreIcebergCommitCatalog = class {
553
+ async commitAppend(input) {
554
+ if (!supportsConditionalPut(input.store)) {
555
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Object-store Iceberg append requires conditional put support", { metadataPath: input.currentMetadataPath });
556
+ }
557
+ const currentBytes = await input.store.get(input.currentMetadataPath);
558
+ if (!currentBytes) {
559
+ throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", `No object at ${input.currentMetadataPath}`, {
560
+ path: input.currentMetadataPath
561
+ });
562
+ }
563
+ const current = validateMetadata(JSON.parse(new TextDecoder().decode(currentBytes)));
564
+ if (current["current-snapshot-id"] !== input.expectedSnapshotId) {
565
+ return { committed: false };
566
+ }
567
+ const versionHintPath = metadataVersionHintPath(input.nextMetadataPath);
568
+ const versionHintHead = await input.store.head(versionHintPath);
569
+ await input.store.put(input.manifestPath, new TextEncoder().encode(`${stableStringify(input.manifest)}
570
+ `), { contentType: "application/json" });
571
+ await input.store.put(input.nextMetadataPath, new TextEncoder().encode(`${JSON.stringify(input.metadata, null, 2)}
572
+ `), { contentType: "application/json" });
573
+ const updated = await input.store.conditionalPut(versionHintPath, new TextEncoder().encode(`${input.nextSnapshotId}
574
+ `), { contentType: "text/plain", expectedEtag: versionHintHead?.etag ?? null });
575
+ if (!updated)
576
+ return { committed: false };
577
+ return { committed: true, metadataPath: input.nextMetadataPath };
578
+ }
579
+ };
580
+ var IcebergRestCatalog = class {
581
+ url;
582
+ namespace;
583
+ table;
584
+ prefix;
585
+ token;
586
+ fetchFn;
587
+ constructor(options) {
588
+ this.url = options.url;
589
+ this.namespace = namespaceParts(options.namespace);
590
+ this.table = requiredNonEmptyString(options.table, "table");
591
+ this.prefix = catalogPrefixParts(options.prefix);
592
+ this.token = options.token;
593
+ this.fetchFn = options.fetch ?? fetch;
594
+ }
595
+ async loadTable(store) {
596
+ const response = await this.requestJson(this.tableUrl(), { method: "GET" });
597
+ if (!isRecord(response) || typeof response["metadata-location"] !== "string") {
598
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Invalid Iceberg REST load table response", {
599
+ url: this.tableUrl()
600
+ });
601
+ }
602
+ return new IcebergTable(store, response["metadata-location"], await hydrateMetadataManifests(store, validateMetadata(response.metadata)));
603
+ }
604
+ async listTables() {
605
+ return validateListTablesResponse(await this.requestJson(this.namespaceTablesUrl(), {
606
+ method: "GET"
607
+ }));
608
+ }
609
+ async commitAppend(input) {
610
+ await input.store.put(input.manifestPath, new TextEncoder().encode(`${stableStringify(input.manifest)}
611
+ `), { contentType: "application/json" });
612
+ await input.store.put(input.nextMetadataPath, new TextEncoder().encode(`${JSON.stringify(input.metadata, null, 2)}
613
+ `), { contentType: "application/json" });
614
+ const response = await this.fetchFn(this.tableUrl(), {
615
+ method: "POST",
616
+ headers: this.headers(true),
617
+ body: JSON.stringify({
618
+ identifier: { namespace: this.namespace, name: this.table },
619
+ requirements: [
620
+ {
621
+ type: "assert-ref-snapshot-id",
622
+ ref: "main",
623
+ "snapshot-id": input.expectedSnapshotId
624
+ }
625
+ ],
626
+ updates: [
627
+ {
628
+ action: "add-snapshot",
629
+ snapshot: restAppendSnapshot(input)
630
+ },
631
+ {
632
+ action: "set-snapshot-ref",
633
+ "ref-name": "main",
634
+ type: "branch",
635
+ "snapshot-id": input.nextSnapshotId
636
+ }
637
+ ]
638
+ })
639
+ });
640
+ if (response.status === 409)
641
+ return { committed: false };
642
+ if (!response.ok) {
643
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg REST table commit failed", {
644
+ url: this.tableUrl(),
645
+ status: response.status,
646
+ statusText: response.statusText
647
+ });
648
+ }
649
+ const metadataPath = await commitResponseMetadataPath(response);
650
+ return {
651
+ committed: true,
652
+ ...metadataPath !== void 0 ? { metadataPath } : {}
653
+ };
654
+ }
655
+ async requestJson(url, init) {
656
+ const response = await this.fetchFn(url, {
657
+ ...init,
658
+ headers: this.headers(init.body !== void 0)
659
+ });
660
+ if (!response.ok) {
661
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg REST catalog request failed", {
662
+ url,
663
+ status: response.status,
664
+ statusText: response.statusText
665
+ });
666
+ }
667
+ try {
668
+ return await response.json();
669
+ } catch (cause) {
670
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg REST catalog response is not JSON", {
671
+ url,
672
+ cause
673
+ });
674
+ }
675
+ }
676
+ headers(hasBody) {
677
+ const headers = new Headers({ accept: "application/json" });
678
+ if (hasBody)
679
+ headers.set("content-type", "application/json");
680
+ if (this.token !== void 0)
681
+ headers.set("authorization", `Bearer ${this.token}`);
682
+ return headers;
683
+ }
684
+ tableUrl() {
685
+ return restCatalogUrl(this.url, [...this.namespaceTableSegments(), this.table]);
686
+ }
687
+ namespaceTablesUrl() {
688
+ return restCatalogUrl(this.url, this.namespaceTableSegments());
689
+ }
690
+ namespaceTableSegments() {
691
+ return ["v1", ...this.prefix, "namespaces", this.namespace.join(""), "tables"];
692
+ }
693
+ };
694
+ function icebergRestCatalog(options) {
695
+ return new IcebergRestCatalog(options);
696
+ }
697
+ var IcebergUnsupportedCatalog = class {
698
+ catalog;
699
+ namespace;
700
+ table;
701
+ constructor(catalog, namespace, table) {
702
+ this.catalog = requiredNonEmptyString(catalog, "catalog");
703
+ this.namespace = namespaceParts(namespace);
704
+ this.table = requiredNonEmptyString(table, "table");
705
+ }
706
+ async loadTable(_store) {
707
+ throw this.unsupported("loadTable");
708
+ }
709
+ async listTables() {
710
+ throw this.unsupported("listTables");
711
+ }
712
+ async commitAppend(_input) {
713
+ throw this.unsupported("commitAppend");
714
+ }
715
+ unsupported(operation) {
716
+ return new LakeqlError("LAKEQL_CATALOG_ERROR", `${this.catalog} Iceberg catalog is not implemented`, {
717
+ catalog: this.catalog,
718
+ namespace: this.namespace,
719
+ table: this.table,
720
+ operation
721
+ });
722
+ }
723
+ };
724
+ function icebergGlueCatalog(options) {
725
+ requiredNonEmptyString(options.region, "region");
726
+ return new IcebergUnsupportedCatalog("Glue", options.namespace, options.table);
727
+ }
728
+ function icebergNessieCatalog(options) {
729
+ requiredNonEmptyString(options.url, "url");
730
+ if (options.ref !== void 0)
731
+ requiredNonEmptyString(options.ref, "ref");
732
+ return new IcebergUnsupportedCatalog("Nessie", options.namespace, options.table);
733
+ }
734
+ async function loadIcebergTable(options) {
735
+ const readControls = loadReadControls(options);
736
+ const store = withObjectStoreReadControls(options.store, readControls);
737
+ const bytes = await store.get(options.metadataPath);
738
+ if (!bytes) {
739
+ throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", `No object at ${options.metadataPath}`, {
740
+ path: options.metadataPath
741
+ });
742
+ }
743
+ throwIfAborted(readControls.signal);
744
+ const text = new TextDecoder().decode(bytes);
745
+ try {
746
+ return new IcebergTable(store, options.metadataPath, await hydrateMetadataManifests(store, validateMetadata(JSON.parse(text)), readControls));
747
+ } catch (cause) {
748
+ if (cause instanceof LakeqlError)
749
+ throw cause;
750
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `Invalid Iceberg metadata at ${options.metadataPath}`, {
751
+ path: options.metadataPath,
752
+ cause
753
+ });
754
+ }
755
+ }
756
+ async function loadIcebergTableFromObjectStore(options) {
757
+ const readControls = loadReadControls(options);
758
+ const store = withObjectStoreReadControls(options.store, readControls);
759
+ const tableLocation = trimTrailingSlash(options.tableLocation);
760
+ const metadataPrefix = `${tableLocation}/metadata/`;
761
+ const versionHintPath = `${metadataPrefix}version-hint.text`;
762
+ const hintedVersion = await readVersionHint(store, versionHintPath, readControls);
763
+ const metadataPath = hintedVersion === void 0 ? await latestMetadataPathFromList(store, metadataPrefix, readControls) : `${metadataPrefix}v${hintedVersion}.metadata.json`;
764
+ return await loadIcebergTable({ store, metadataPath, ...readControls });
765
+ }
766
+ async function loadIcebergTableFromRest(options) {
767
+ return await icebergRestCatalog(options).loadTable(withObjectStoreReadControls(options.store, loadReadControls(options)));
768
+ }
769
+ function loadReadControls(options) {
770
+ const controls = {};
771
+ if (options.maxConcurrentReads !== void 0)
772
+ controls.maxConcurrentReads = options.maxConcurrentReads;
773
+ const signal = readControlSignal(options);
774
+ if (signal !== void 0)
775
+ controls.signal = signal;
776
+ return controls;
777
+ }
778
+ function applyIcebergDeletes(options) {
779
+ const rowOffset = options.rowOffset ?? 0;
780
+ const positionDeletes = /* @__PURE__ */ new Set();
781
+ for (const deletion of options.positionDeletes ?? []) {
782
+ if (deletion.path !== options.dataFilePath)
783
+ continue;
784
+ positionDeletes.add(validateDeletePosition(deletion.position, deletion.path));
785
+ }
786
+ for (const deletionVector of options.deletionVectors ?? []) {
787
+ if (deletionVector.path !== options.dataFilePath)
788
+ continue;
789
+ for (const position of deletionVector.positions) {
790
+ positionDeletes.add(validateDeletePosition(position, deletionVector.path));
791
+ }
792
+ }
793
+ const equalityDeleteKeys = /* @__PURE__ */ new Map();
794
+ for (const deletion of options.equalityDeletes ?? []) {
795
+ if (deletion.columns.length === 0) {
796
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Iceberg equality delete requires columns");
797
+ }
798
+ const columnsKey = stableStringify(deletion.columns);
799
+ let keys = equalityDeleteKeys.get(columnsKey);
800
+ if (!keys) {
801
+ keys = /* @__PURE__ */ new Set();
802
+ equalityDeleteKeys.set(columnsKey, keys);
803
+ }
804
+ keys.add(equalityKey(deletion.row, deletion.columns));
805
+ }
806
+ return options.rows.filter((row, index) => {
807
+ if (positionDeletes.has(rowOffset + index))
808
+ return false;
809
+ for (const [columnsKey, keys] of equalityDeleteKeys) {
810
+ const columns = JSON.parse(columnsKey);
811
+ if (keys.has(equalityKey(row, columns)))
812
+ return false;
813
+ }
814
+ return true;
815
+ });
816
+ }
817
+ async function* scanPlannedIcebergRows(options) {
818
+ const files = Array.isArray(options.plan) ? options.plan : options.plan.files;
819
+ const signal = readControlSignal(options);
820
+ for (const file of files) {
821
+ throwIfAborted(signal);
822
+ const deletes = await decodedDeletesForFile(file, options, signal);
823
+ throwIfAborted(signal);
824
+ const data = await options.readDataFile(file);
825
+ throwIfAborted(signal);
826
+ let rowOffset = 0;
827
+ for await (const batch of rowBatches(data)) {
828
+ throwIfAborted(signal);
829
+ const rows = Array.isArray(batch) ? batch : batch.rows;
830
+ const absoluteRowOffset = Array.isArray(batch) ? rowOffset : batch.rowOffset;
831
+ const visibleRows = hasDeletes(deletes) ? applyIcebergDeletes({
832
+ dataFilePath: file.path,
833
+ rows,
834
+ rowOffset: absoluteRowOffset,
835
+ ...deletes
836
+ }) : rows;
837
+ rowOffset = absoluteRowOffset + rows.length;
838
+ if (visibleRows.length > 0)
839
+ yield visibleRows;
840
+ throwIfAborted(signal);
841
+ }
842
+ }
843
+ }
844
+ async function decodedDeletesForFile(file, options, signal) {
845
+ const out = {};
846
+ for (const deleteFile of file.deleteFiles ?? []) {
847
+ throwIfAborted(signal);
848
+ const decoded = await options.readDeleteFile(deleteFile, file);
849
+ throwIfAborted(signal);
850
+ pushDeletes(out, decoded);
851
+ }
852
+ return out;
853
+ }
854
+ function pushDeletes(target, source) {
855
+ if (source.positionDeletes !== void 0) {
856
+ target.positionDeletes = [...target.positionDeletes ?? [], ...source.positionDeletes];
857
+ }
858
+ if (source.equalityDeletes !== void 0) {
859
+ target.equalityDeletes = [...target.equalityDeletes ?? [], ...source.equalityDeletes];
860
+ }
861
+ if (source.deletionVectors !== void 0) {
862
+ target.deletionVectors = [...target.deletionVectors ?? [], ...source.deletionVectors];
863
+ }
864
+ }
865
+ function hasDeletes(deletes) {
866
+ return (deletes.positionDeletes?.length ?? 0) > 0 || (deletes.equalityDeletes?.length ?? 0) > 0 || (deletes.deletionVectors?.length ?? 0) > 0;
867
+ }
868
+ async function* rowBatches(rows) {
869
+ if (isAsyncIterable(rows)) {
870
+ yield* rows;
871
+ } else {
872
+ yield rows;
873
+ }
874
+ }
875
+ function isAsyncIterable(value) {
876
+ return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
877
+ }
878
+ function validateDeletePosition(position, path) {
879
+ if (!Number.isInteger(position) || position < 0) {
880
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Iceberg delete position must be non-negative", {
881
+ path,
882
+ position
883
+ });
884
+ }
885
+ return position;
886
+ }
887
+ function nextMetadataPathFor(metadataPath, snapshotId) {
888
+ const slash = metadataPath.lastIndexOf("/");
889
+ const prefix = slash === -1 ? "" : `${metadataPath.slice(0, slash + 1)}`;
890
+ return `${prefix}v${snapshotId}.metadata.json`;
891
+ }
892
+ function randomSnapshotId(existingIds) {
893
+ const existing = new Set(existingIds);
894
+ for (let attempt = 0; attempt < 100; attempt += 1) {
895
+ const id = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
896
+ if (!existing.has(id))
897
+ return id;
898
+ }
899
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Unable to allocate unique Iceberg snapshot id");
900
+ }
901
+ function validateNewSnapshotId(snapshotId, snapshots) {
902
+ if (!Number.isSafeInteger(snapshotId) || snapshotId <= 0) {
903
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Iceberg snapshot id must be a positive safe integer", { snapshotId });
904
+ }
905
+ if (snapshots.some((snapshot) => snapshotIdOf(snapshot) === snapshotId)) {
906
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Iceberg snapshot id already exists", {
907
+ snapshotId
908
+ });
909
+ }
910
+ }
911
+ function snapshotIdOf(snapshot) {
912
+ return snapshot["snapshot-id"];
913
+ }
914
+ function metadataVersionHintPath(metadataPath) {
915
+ const slash = metadataPath.lastIndexOf("/");
916
+ const prefix = slash === -1 ? "" : `${metadataPath.slice(0, slash + 1)}`;
917
+ return `${prefix}version-hint.text`;
918
+ }
919
+ function supportsConditionalPut(store) {
920
+ return typeof store.conditionalPut === "function";
921
+ }
922
+ async function hydrateMetadataManifests(store, metadata, controls = {}) {
923
+ const hydrated = cloneMetadata(metadata);
924
+ const tablePrefix = tableLocationObjectPrefix(hydrated.location);
925
+ for (const snapshot of hydrated.snapshots) {
926
+ throwIfAborted(controls.signal);
927
+ const manifestReferences = snapshot.manifests ?? (snapshot["manifest-list"] !== void 0 ? await readManifestList(store, validateManifestSourcedPath(snapshot["manifest-list"], tablePrefix)) : []);
928
+ const manifests = await Promise.all(manifestReferences.map(async (manifest) => {
929
+ const manifestPath = validateManifestSourcedPath(manifest.path, tablePrefix);
930
+ if (Array.isArray(manifest.files))
931
+ return validateManifestPaths(manifest, manifestPath, tablePrefix);
932
+ return await readManifest(store, manifestPath, tablePrefix);
933
+ }));
934
+ throwIfAborted(controls.signal);
935
+ snapshot.manifests = mergeDeleteManifests(manifests);
936
+ }
937
+ return hydrated;
938
+ }
939
+ function mergeDeleteManifests(manifests) {
940
+ const deleteFiles = manifests.flatMap((manifest) => manifest.deleteFiles ?? []);
941
+ const dataManifests = manifests.filter((manifest) => manifest.files.length > 0);
942
+ if (deleteFiles.length === 0)
943
+ return dataManifests;
944
+ return dataManifests.map((manifest) => ({
945
+ ...manifest,
946
+ files: manifest.files.map((file) => {
947
+ const applicable = deleteFiles.filter((deleteFile) => deleteFileMayApply(deleteFile, file));
948
+ if (applicable.length === 0)
949
+ return file;
950
+ return {
951
+ ...file,
952
+ deleteFiles: [...file.deleteFiles ?? [], ...applicable.map(publicDeleteFile)]
953
+ };
954
+ })
955
+ }));
956
+ }
957
+ function deleteFileMayApply(deleteFile, file) {
958
+ if (deleteFile.partition === void 0 || Object.keys(deleteFile.partition).length === 0) {
959
+ return true;
960
+ }
961
+ const filePartition = file.partition ?? {};
962
+ return Object.entries(deleteFile.partition).every(([key, value]) => filePartition[key] === value);
963
+ }
964
+ function publicDeleteFile(deleteFile) {
965
+ return { content: deleteFile.content, path: deleteFile.path };
966
+ }
967
+ async function readManifestList(store, path) {
968
+ const bytes = await store.get(path);
969
+ if (!bytes) {
970
+ throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", `No Iceberg manifest list at ${path}`, {
971
+ path
972
+ });
973
+ }
974
+ try {
975
+ return avroObjectContainer(bytes) ? validateAvroManifestList(await decodeAvroObjectContainer(bytes), path) : validateManifestList(JSON.parse(new TextDecoder().decode(bytes)), path);
976
+ } catch (cause) {
977
+ if (cause instanceof LakeqlError)
978
+ throw cause;
979
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `Invalid Iceberg manifest list at ${path}`, {
980
+ path,
981
+ cause
982
+ });
983
+ }
984
+ }
985
+ async function readManifest(store, path, tablePrefix = "") {
986
+ const bytes = await store.get(path);
987
+ if (!bytes) {
988
+ throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", `No Iceberg manifest at ${path}`, { path });
989
+ }
990
+ try {
991
+ const manifest = avroObjectContainer(bytes) ? validateAvroManifest(await decodeAvroObjectContainer(bytes), path) : validateManifest(JSON.parse(new TextDecoder().decode(bytes)), path);
992
+ return validateManifestPaths(manifest, path, tablePrefix);
993
+ } catch (cause) {
994
+ if (cause instanceof LakeqlError)
995
+ throw cause;
996
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `Invalid Iceberg manifest at ${path}`, {
997
+ path,
998
+ cause
999
+ });
1000
+ }
1001
+ }
1002
+ function validateAvroManifestList(records, path) {
1003
+ return records.map((record) => {
1004
+ if (!isRecord(record) || typeof record.manifest_path !== "string") {
1005
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg Avro manifest list entry is invalid", {
1006
+ path
1007
+ });
1008
+ }
1009
+ validateManifestContent(record.content, path);
1010
+ return { path: record.manifest_path };
1011
+ });
1012
+ }
1013
+ function validateAvroManifest(records, path) {
1014
+ const files = [];
1015
+ const deleteFiles = [];
1016
+ for (const record of records) {
1017
+ if (!isRecord(record)) {
1018
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg Avro manifest entry is invalid", {
1019
+ path
1020
+ });
1021
+ }
1022
+ if (typeof record.status === "number" && record.status === 2)
1023
+ continue;
1024
+ const dataFile = record.data_file;
1025
+ if (!isRecord(dataFile)) {
1026
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg Avro manifest entry is missing data_file", {
1027
+ path
1028
+ });
1029
+ }
1030
+ const content = typeof dataFile.content === "number" ? dataFile.content : 0;
1031
+ const recordCount = safeAvroNumber(dataFile.record_count);
1032
+ if (typeof dataFile.file_path !== "string" || recordCount === void 0) {
1033
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg Avro data file has invalid fields", {
1034
+ path
1035
+ });
1036
+ }
1037
+ if (content !== 0) {
1038
+ deleteFiles.push({
1039
+ content: avroDeleteContent(content, dataFile),
1040
+ path: dataFile.file_path,
1041
+ partition: avroPartitionValues(dataFile.partition)
1042
+ });
1043
+ continue;
1044
+ }
1045
+ const sequenceNumber = safeAvroNumber(record.sequence_number) ?? safeAvroNumber(record.file_sequence_number) ?? 0;
1046
+ const file = {
1047
+ path: dataFile.file_path,
1048
+ sequenceNumber,
1049
+ partition: avroPartitionValues(dataFile.partition),
1050
+ recordCount
1051
+ };
1052
+ const fileSizeInBytes = safeAvroNumber(dataFile.file_size_in_bytes);
1053
+ if (fileSizeInBytes !== void 0) {
1054
+ file.fileSizeInBytes = fileSizeInBytes;
1055
+ }
1056
+ files.push(file);
1057
+ }
1058
+ const manifest = { path, files };
1059
+ if (deleteFiles.length > 0)
1060
+ manifest.deleteFiles = deleteFiles;
1061
+ return validateManifestPaths(manifest, path);
1062
+ }
1063
+ function avroDeleteContent(content, dataFile) {
1064
+ if (content === 1 && (String(dataFile.file_format).toLowerCase() === "puffin" || dataFile.content_offset !== void 0 || dataFile.content_size_in_bytes !== void 0)) {
1065
+ return "deletion-vector";
1066
+ }
1067
+ if (content === 1)
1068
+ return "position-delete";
1069
+ if (content === 2)
1070
+ return "equality-delete";
1071
+ return `unsupported-delete-${content}`;
1072
+ }
1073
+ function avroPartitionValues(value) {
1074
+ if (!isRecord(value))
1075
+ return {};
1076
+ const out = {};
1077
+ for (const [key, inner] of Object.entries(value)) {
1078
+ if (inner === null || inner === void 0)
1079
+ continue;
1080
+ out[key] = String(jsonSafeValue(inner));
1081
+ }
1082
+ return sortStringRecord(out);
1083
+ }
1084
+ function safeAvroNumber(value) {
1085
+ if (typeof value === "number" && Number.isSafeInteger(value))
1086
+ return value;
1087
+ if (typeof value === "bigint") {
1088
+ const numberValue = Number(value);
1089
+ if (Number.isSafeInteger(numberValue) && BigInt(numberValue) === value)
1090
+ return numberValue;
1091
+ }
1092
+ return void 0;
1093
+ }
1094
+ function avroObjectContainer(bytes) {
1095
+ return bytes.length >= 4 && bytes[0] === 79 && bytes[1] === 98 && bytes[2] === 106 && bytes[3] === 1;
1096
+ }
1097
+ async function decodeAvroObjectContainer(bytes) {
1098
+ const avro = await loadAvro();
1099
+ const avroBigIntLongType = avro.types.LongType.__with({
1100
+ fromBuffer: (buffer) => buffer.readBigInt64LE(),
1101
+ toBuffer: (value) => {
1102
+ const buffer = Buffer.alloc(8);
1103
+ buffer.writeBigInt64LE(BigInt(value));
1104
+ return buffer;
1105
+ },
1106
+ fromJSON: (value) => BigInt(value),
1107
+ toJSON: (value) => value.toString(),
1108
+ isValid: (value) => typeof value === "bigint" || typeof value === "number" && Number.isSafeInteger(value),
1109
+ compare: (left, right) => {
1110
+ const leftBigInt = BigInt(left);
1111
+ const rightBigInt = BigInt(right);
1112
+ return leftBigInt === rightBigInt ? 0 : leftBigInt < rightBigInt ? -1 : 1;
1113
+ }
1114
+ });
1115
+ const avroLongTypeHook = (schema) => schema === "long" || isRecord(schema) && schema.type === "long" ? avroBigIntLongType : void 0;
1116
+ const decoder = new avro.streams.BlockDecoder({
1117
+ parseHook: (schema) => avro.Type.forSchema(schema, { typeHook: avroLongTypeHook })
1118
+ });
1119
+ const records = [];
1120
+ const done = new Promise((resolve, reject) => {
1121
+ decoder.on("data", (record) => records.push(record));
1122
+ decoder.on("end", () => resolve(records));
1123
+ decoder.on("error", reject);
1124
+ });
1125
+ decoder.end(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength));
1126
+ return done;
1127
+ }
1128
+ async function loadAvro() {
1129
+ const module = await import('avsc');
1130
+ return module.default ?? module;
1131
+ }
1132
+ function validateManifestList(value, path) {
1133
+ const manifests = Array.isArray(value) ? value : isRecord(value) && Array.isArray(value.manifests) ? value.manifests : void 0;
1134
+ if (manifests === void 0) {
1135
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg manifest list has invalid required fields", {
1136
+ path
1137
+ });
1138
+ }
1139
+ return manifests.map((manifest) => validateManifestReference(manifest, path));
1140
+ }
1141
+ function validateManifest(value, path) {
1142
+ if (!isRecord(value) || typeof value.path !== "string" || !Array.isArray(value.files)) {
1143
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg manifest has invalid required fields", {
1144
+ path
1145
+ });
1146
+ }
1147
+ return value;
1148
+ }
1149
+ function validateManifestReference(value, path) {
1150
+ if (!isRecord(value) || typeof value.path !== "string") {
1151
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg manifest list entry is invalid", {
1152
+ path
1153
+ });
1154
+ }
1155
+ validateManifestContent(value.content, path);
1156
+ if (Array.isArray(value.files))
1157
+ return value;
1158
+ return { path: value.path };
1159
+ }
1160
+ function validateManifestContent(content, path) {
1161
+ if (content === void 0 || content === null)
1162
+ return;
1163
+ if (content === 0 || content === 1 || content === "data" || content === "deletes")
1164
+ return;
1165
+ throw new LakeqlError("LAKEQL_UNSUPPORTED_ICEBERG_FEATURE", "Iceberg manifest list contains an unsupported manifest content type", {
1166
+ path,
1167
+ content
1168
+ });
1169
+ }
1170
+ function validateManifestPaths(manifest, path, tablePrefix = "") {
1171
+ validateManifestSourcedPath(manifest.path, tablePrefix);
1172
+ for (const file of manifest.files) {
1173
+ validateManifestSourcedPath(file.path, tablePrefix);
1174
+ for (const deleteFile of file.deleteFiles ?? []) {
1175
+ validateManifestSourcedPath(deleteFile.path, tablePrefix);
1176
+ }
1177
+ }
1178
+ for (const deleteFile of manifest.deleteFiles ?? []) {
1179
+ validateManifestSourcedPath(deleteFile.path, tablePrefix);
1180
+ }
1181
+ return { ...manifest, path };
1182
+ }
1183
+ function validateManifestSourcedPath(path, tablePrefix = "") {
1184
+ if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//iu.test(path) || path.startsWith("/")) {
1185
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path must be relative: ${path}`, {
1186
+ path
1187
+ });
1188
+ }
1189
+ for (const segment of path.split("/")) {
1190
+ let decoded;
1191
+ try {
1192
+ decoded = decodeURIComponent(segment);
1193
+ } catch {
1194
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path is invalid: ${path}`, {
1195
+ path
1196
+ });
1197
+ }
1198
+ if (decoded === "." || decoded === "..") {
1199
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path contains traversal: ${path}`, {
1200
+ path
1201
+ });
1202
+ }
1203
+ }
1204
+ if (tablePrefix !== "" && path !== tablePrefix && !path.startsWith(`${tablePrefix}/`)) {
1205
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path escapes table location: ${path}`, {
1206
+ path,
1207
+ tableLocation: tablePrefix
1208
+ });
1209
+ }
1210
+ return path;
1211
+ }
1212
+ function tableLocationObjectPrefix(location) {
1213
+ const trimmed = trimTrailingSlash(location.trim());
1214
+ if (trimmed === "" || !trimmed.includes("/"))
1215
+ return "";
1216
+ if (/^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed)) {
1217
+ const url = new URL(trimmed);
1218
+ return trimSlashes(decodeURIComponent(url.pathname));
1219
+ }
1220
+ return trimSlashes(trimmed);
1221
+ }
1222
+ function trimSlashes(value) {
1223
+ return value.replace(/^\/+|\/+$/gu, "");
1224
+ }
1225
+ async function readVersionHint(store, path, controls = {}) {
1226
+ const bytes = await store.get(path);
1227
+ if (!bytes)
1228
+ return void 0;
1229
+ throwIfAborted(controls.signal);
1230
+ const text = new TextDecoder().decode(bytes).trim();
1231
+ const version = Number(text);
1232
+ if (!Number.isInteger(version) || version < 0) {
1233
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Invalid Iceberg version hint", {
1234
+ path,
1235
+ versionHint: text
1236
+ });
1237
+ }
1238
+ return version;
1239
+ }
1240
+ async function latestMetadataPathFromList(store, metadataPrefix, controls = {}) {
1241
+ let latest;
1242
+ for await (const object of store.list(metadataPrefix)) {
1243
+ throwIfAborted(controls.signal);
1244
+ const name = object.path.slice(metadataPrefix.length);
1245
+ const match = /^v(\d+)\.metadata\.json$/u.exec(name);
1246
+ if (!match)
1247
+ continue;
1248
+ const version = Number(match[1]);
1249
+ if (!Number.isSafeInteger(version))
1250
+ continue;
1251
+ if (latest === void 0 || version > latest.version)
1252
+ latest = { path: object.path, version };
1253
+ }
1254
+ if (latest === void 0) {
1255
+ throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", "No Iceberg metadata files found", {
1256
+ prefix: metadataPrefix
1257
+ });
1258
+ }
1259
+ return latest.path;
1260
+ }
1261
+ function trimTrailingSlash(value) {
1262
+ return value.endsWith("/") ? value.slice(0, -1) : value;
1263
+ }
1264
+ function restCatalogUrl(baseUrl, segments) {
1265
+ const url = new URL(baseUrl);
1266
+ const basePath = url.pathname.replace(/\/+$/u, "");
1267
+ const encodedSegments = segments.map((segment) => encodeURIComponent(segment));
1268
+ url.pathname = [basePath, ...encodedSegments].filter((segment) => segment.length > 0).join("/");
1269
+ return url.toString();
1270
+ }
1271
+ function namespaceParts(namespace) {
1272
+ const parts = Array.isArray(namespace) ? namespace : namespace.split(".");
1273
+ const out = parts.map((part) => requiredNonEmptyString(part, "namespace"));
1274
+ if (out.length === 0) {
1275
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Iceberg REST namespace is required");
1276
+ }
1277
+ return out;
1278
+ }
1279
+ function catalogPrefixParts(prefix) {
1280
+ if (prefix === void 0 || prefix.trim() === "")
1281
+ return [];
1282
+ return prefix.split("/").filter((part) => part.length > 0).map((part) => requiredNonEmptyString(part, "prefix"));
1283
+ }
1284
+ function requiredNonEmptyString(value, field) {
1285
+ const trimmed = value.trim();
1286
+ if (trimmed.length === 0) {
1287
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg REST ${field} is required`);
1288
+ }
1289
+ return trimmed;
1290
+ }
1291
+ async function commitResponseMetadataPath(response) {
1292
+ if (response.status === 204)
1293
+ return void 0;
1294
+ try {
1295
+ const body = await response.json();
1296
+ if (isRecord(body) && typeof body["metadata-location"] === "string") {
1297
+ return body["metadata-location"];
1298
+ }
1299
+ } catch {
1300
+ return void 0;
1301
+ }
1302
+ return void 0;
1303
+ }
1304
+ function restAppendSnapshot(input) {
1305
+ const snapshot = input.metadata.snapshots.at(-1);
1306
+ if (snapshot === void 0) {
1307
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg append metadata is missing next snapshot");
1308
+ }
1309
+ return {
1310
+ "snapshot-id": snapshot["snapshot-id"],
1311
+ "parent-snapshot-id": input.expectedSnapshotId,
1312
+ "timestamp-ms": snapshot["timestamp-ms"],
1313
+ "schema-id": snapshot["schema-id"],
1314
+ "manifest-list": input.manifestPath,
1315
+ summary: {
1316
+ operation: "append",
1317
+ "added-data-files": String(input.manifest.files.length),
1318
+ "added-records": String(input.manifest.files.reduce((sum, file) => sum + file.recordCount, 0))
1319
+ }
1320
+ };
1321
+ }
1322
+ function equalityKey(row, columns) {
1323
+ return stableStringify(columns.map((column) => {
1324
+ if (!(column in row)) {
1325
+ throw new LakeqlError("LAKEQL_UNKNOWN_COLUMN", `Unknown Iceberg equality delete column ${column}`, {
1326
+ column
1327
+ });
1328
+ }
1329
+ return jsonSafeValue(row[column]);
1330
+ }));
1331
+ }
1332
+ function appendManifestPath(metadataPath, jobId, snapshotId) {
1333
+ const slash = metadataPath.lastIndexOf("/");
1334
+ const prefix = slash === -1 ? "" : `${metadataPath.slice(0, slash + 1)}`;
1335
+ return `${prefix}${jobId ?? "append"}-${snapshotId}.manifest.json`;
1336
+ }
1337
+ function maxSequenceNumber(metadata) {
1338
+ let max = 0;
1339
+ for (const snapshot of metadata.snapshots) {
1340
+ for (const manifest of snapshotManifests(snapshot)) {
1341
+ for (const file of manifest.files)
1342
+ max = Math.max(max, file.sequenceNumber);
1343
+ }
1344
+ }
1345
+ return max;
1346
+ }
1347
+ function supportedIcebergDeleteFiles(deleteFiles) {
1348
+ const supported = [];
1349
+ for (const deleteFile of deleteFiles ?? []) {
1350
+ if (deleteFile.content === "position-delete" || deleteFile.content === "equality-delete") {
1351
+ supported.push({ content: deleteFile.content, path: deleteFile.path });
1352
+ }
1353
+ }
1354
+ return supported;
1355
+ }
1356
+ function unsupportedIcebergDeleteFiles(deleteFiles) {
1357
+ const supported = /* @__PURE__ */ new Set(["position-delete", "equality-delete"]);
1358
+ return (deleteFiles ?? []).filter((deleteFile) => !supported.has(deleteFile.content));
1359
+ }
1360
+ function cloneMetadata(metadata) {
1361
+ const cloned = {
1362
+ "format-version": metadata["format-version"],
1363
+ "table-uuid": metadata["table-uuid"],
1364
+ location: metadata.location,
1365
+ "current-snapshot-id": metadata["current-snapshot-id"],
1366
+ schemas: metadata.schemas.map((schema) => ({
1367
+ "schema-id": schema["schema-id"],
1368
+ fields: schema.fields.map((field) => ({ ...field }))
1369
+ })),
1370
+ snapshots: metadata.snapshots.map((snapshot) => {
1371
+ const cloned2 = {
1372
+ "snapshot-id": snapshot["snapshot-id"],
1373
+ "timestamp-ms": snapshot["timestamp-ms"],
1374
+ "schema-id": snapshot["schema-id"]
1375
+ };
1376
+ if (snapshot["manifest-list"] !== void 0)
1377
+ cloned2["manifest-list"] = snapshot["manifest-list"];
1378
+ if (snapshot.manifests !== void 0) {
1379
+ cloned2.manifests = snapshot.manifests.map(cloneManifestOrReference);
1380
+ }
1381
+ return cloned2;
1382
+ })
1383
+ };
1384
+ if (metadata.refs)
1385
+ cloned.refs = cloneRefs(metadata.refs);
1386
+ if (metadata["partition-specs"]) {
1387
+ cloned["partition-specs"] = metadata["partition-specs"].map((spec) => ({
1388
+ "spec-id": spec["spec-id"],
1389
+ fields: spec.fields.map((field) => ({ ...field }))
1390
+ }));
1391
+ }
1392
+ if (metadata["sort-orders"]) {
1393
+ cloned["sort-orders"] = metadata["sort-orders"].map((order) => ({
1394
+ "order-id": order["order-id"],
1395
+ fields: order.fields.map((field) => field)
1396
+ }));
1397
+ }
1398
+ return cloned;
1399
+ }
1400
+ function cloneRefs(refs) {
1401
+ const out = {};
1402
+ for (const [name, ref] of Object.entries(refs)) {
1403
+ out[name] = { type: ref.type, "snapshot-id": ref["snapshot-id"] };
1404
+ }
1405
+ return out;
1406
+ }
1407
+ function cloneManifest(manifest) {
1408
+ const cloned = {
1409
+ path: manifest.path,
1410
+ files: manifest.files.map((file) => {
1411
+ const cloned2 = {
1412
+ path: file.path,
1413
+ sequenceNumber: file.sequenceNumber,
1414
+ partition: sortStringRecord(file.partition ?? {}),
1415
+ recordCount: file.recordCount
1416
+ };
1417
+ if (file.fileSizeInBytes !== void 0)
1418
+ cloned2.fileSizeInBytes = file.fileSizeInBytes;
1419
+ if (file.deleteFiles !== void 0) {
1420
+ cloned2.deleteFiles = file.deleteFiles.map((deleteFile) => ({ ...deleteFile }));
1421
+ }
1422
+ return cloned2;
1423
+ })
1424
+ };
1425
+ if (manifest.deleteFiles !== void 0) {
1426
+ cloned.deleteFiles = manifest.deleteFiles.map((deleteFile) => ({ ...deleteFile }));
1427
+ }
1428
+ return cloned;
1429
+ }
1430
+ function cloneManifestOrReference(manifest) {
1431
+ if (!Array.isArray(manifest.files)) {
1432
+ return { path: manifest.path };
1433
+ }
1434
+ return cloneManifest(manifest);
1435
+ }
1436
+ function snapshotManifests(snapshot) {
1437
+ return snapshot.manifests ?? [];
1438
+ }
1439
+ function sortStringRecord(record) {
1440
+ const out = {};
1441
+ for (const key of Object.keys(record).sort())
1442
+ out[key] = record[key] ?? "";
1443
+ return out;
1444
+ }
1445
+ function validateListTablesResponse(value) {
1446
+ if (!isRecord(value) || !Array.isArray(value.identifiers)) {
1447
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Invalid Iceberg REST list tables response");
1448
+ }
1449
+ return value.identifiers.map((identifier) => {
1450
+ if (!isRecord(identifier) || !Array.isArray(identifier.namespace) || !identifier.namespace.every((part) => typeof part === "string") || typeof identifier.name !== "string") {
1451
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Invalid Iceberg REST table identifier");
1452
+ }
1453
+ return { namespace: identifier.namespace, name: identifier.name };
1454
+ });
1455
+ }
1456
+ function validateMetadata(value) {
1457
+ if (!isRecord(value))
1458
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg metadata must be an object");
1459
+ if (value["format-version"] !== 1 && value["format-version"] !== 2) {
1460
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Only Iceberg format-version 1 and 2 metadata is supported for reads", {
1461
+ formatVersion: value["format-version"]
1462
+ });
1463
+ }
1464
+ if (!Array.isArray(value.snapshots) || !Array.isArray(value.schemas)) {
1465
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg metadata is missing snapshots or schemas");
1466
+ }
1467
+ if (!isMetadataFile(value)) {
1468
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg metadata has invalid required fields");
1469
+ }
1470
+ rejectUnsupportedMetadataFeatures(value);
1471
+ return value;
1472
+ }
1473
+ function rejectUnsupportedMetadataFeatures(metadata) {
1474
+ rejectAdvertisedFeatureFlags(metadata);
1475
+ rejectUnsupportedPartitionTransforms(metadata["partition-specs"]);
1476
+ rejectUnsupportedSortOrders(metadata["sort-orders"]);
1477
+ }
1478
+ function rejectAdvertisedFeatureFlags(metadata) {
1479
+ for (const key of ["features", "table-features", "format-version-features"]) {
1480
+ const features = metadata[key];
1481
+ if (features === void 0 || Array.isArray(features) && features.length === 0)
1482
+ continue;
1483
+ throw new LakeqlError("LAKEQL_UNSUPPORTED_ICEBERG_FEATURE", "Iceberg metadata advertises unsupported table-format features", {
1484
+ featureProperty: key,
1485
+ features
1486
+ });
1487
+ }
1488
+ }
1489
+ function rejectUnsupportedPartitionTransforms(partitionSpecs) {
1490
+ if (partitionSpecs === void 0)
1491
+ return;
1492
+ if (!Array.isArray(partitionSpecs)) {
1493
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg partition-specs must be an array");
1494
+ }
1495
+ for (const spec of partitionSpecs) {
1496
+ if (!isRecord(spec) || !Array.isArray(spec.fields)) {
1497
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg partition spec is invalid");
1498
+ }
1499
+ for (const field of spec.fields) {
1500
+ if (!isRecord(field) || typeof field.transform !== "string") {
1501
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg partition field is invalid");
1502
+ }
1503
+ if (field.transform !== "identity" && field.transform !== "void") {
1504
+ throw new LakeqlError("LAKEQL_UNSUPPORTED_ICEBERG_FEATURE", "Iceberg partition transform is not supported for strict planning", {
1505
+ specId: spec["spec-id"],
1506
+ fieldName: field.name,
1507
+ transform: field.transform
1508
+ });
1509
+ }
1510
+ }
1511
+ }
1512
+ }
1513
+ function rejectUnsupportedSortOrders(sortOrders) {
1514
+ if (sortOrders === void 0)
1515
+ return;
1516
+ if (!Array.isArray(sortOrders)) {
1517
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg sort-orders must be an array");
1518
+ }
1519
+ for (const order of sortOrders) {
1520
+ if (!isRecord(order) || !Array.isArray(order.fields)) {
1521
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg sort order is invalid");
1522
+ }
1523
+ if (order.fields.length > 0) {
1524
+ throw new LakeqlError("LAKEQL_UNSUPPORTED_ICEBERG_FEATURE", "Iceberg sorted table metadata is not supported for strict planning", {
1525
+ orderId: order["order-id"],
1526
+ fields: order.fields
1527
+ });
1528
+ }
1529
+ }
1530
+ }
1531
+ function isMetadataFile(value) {
1532
+ if (!isRecord(value))
1533
+ return false;
1534
+ return (value["format-version"] === 1 || value["format-version"] === 2) && typeof value["table-uuid"] === "string" && typeof value.location === "string" && typeof value["current-snapshot-id"] === "number" && Array.isArray(value.refs) === false && Array.isArray(value.schemas) && Array.isArray(value.snapshots);
1535
+ }
1536
+ function projectedIds(fields, select) {
1537
+ if (!select)
1538
+ return fields.map((field) => field.id).sort((a, b) => a - b);
1539
+ return select.map((name) => {
1540
+ const field = fields.find((candidate) => candidate.name === name);
1541
+ if (!field) {
1542
+ throw new LakeqlError("LAKEQL_UNKNOWN_COLUMN", `Unknown Iceberg column ${name}`, {
1543
+ column: name
1544
+ });
1545
+ }
1546
+ return field.sourceId ?? field.id;
1547
+ }).sort((a, b) => a - b);
1548
+ }
1549
+ function partitionMayMatch(expr, partition) {
1550
+ if (!expr)
1551
+ return true;
1552
+ const columns = /* @__PURE__ */ new Set();
1553
+ collectColumns(expr, columns);
1554
+ if (columns.size === 0 || [...columns].some((column) => !(column in partition)))
1555
+ return true;
1556
+ try {
1557
+ return matches(expr, partition);
1558
+ } catch (cause) {
1559
+ if (cause instanceof LakeqlError && cause.code === "LAKEQL_TYPE_ERROR")
1560
+ return true;
1561
+ throw cause;
1562
+ }
1563
+ }
1564
+ function collectColumns(expr, columns) {
1565
+ switch (expr.kind) {
1566
+ case "column":
1567
+ columns.add(expr.name);
1568
+ return;
1569
+ case "literal":
1570
+ return;
1571
+ case "compare":
1572
+ collectColumns(expr.left, columns);
1573
+ collectColumns(expr.right, columns);
1574
+ return;
1575
+ case "in":
1576
+ collectColumns(expr.target, columns);
1577
+ for (const value of expr.values)
1578
+ collectColumns(value, columns);
1579
+ return;
1580
+ case "between":
1581
+ collectColumns(expr.target, columns);
1582
+ collectColumns(expr.low, columns);
1583
+ collectColumns(expr.high, columns);
1584
+ return;
1585
+ case "null-check":
1586
+ collectColumns(expr.target, columns);
1587
+ return;
1588
+ case "logical":
1589
+ for (const operand of expr.operands)
1590
+ collectColumns(operand, columns);
1591
+ return;
1592
+ case "not":
1593
+ collectColumns(expr.operand, columns);
1594
+ return;
1595
+ case "like":
1596
+ collectColumns(expr.target, columns);
1597
+ return;
1598
+ case "call":
1599
+ for (const arg of expr.args)
1600
+ collectColumns(arg, columns);
1601
+ return;
1602
+ case "arithmetic":
1603
+ collectColumns(expr.left, columns);
1604
+ collectColumns(expr.right, columns);
1605
+ return;
1606
+ case "case":
1607
+ for (const branch of expr.whens) {
1608
+ collectColumns(branch.when, columns);
1609
+ collectColumns(branch.value, columns);
1610
+ }
1611
+ if (expr.else !== void 0)
1612
+ collectColumns(expr.else, columns);
1613
+ return;
1614
+ }
1615
+ }
1616
+ function isRecord(value) {
1617
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1618
+ }
1619
+
1620
+ // src/engine.ts
1621
+ async function loadTable(options) {
1622
+ if (options.format === "parquet") {
1623
+ return { format: "parquet", store: options.store, path: options.path };
1624
+ }
1625
+ const { format: _format, ...icebergOptions } = options;
1626
+ return {
1627
+ format: "iceberg",
1628
+ store: options.store,
1629
+ table: await loadIcebergTable(icebergOptions)
1630
+ };
1631
+ }
1632
+ function planFiles2(table, options = {}) {
1633
+ if (table.format === "parquet") {
1634
+ return { format: "parquet", store: table.store, files: [{ path: table.path }] };
1635
+ }
1636
+ return {
1637
+ format: "iceberg",
1638
+ store: table.store,
1639
+ table: table.table,
1640
+ plan: planFiles(table.table, options),
1641
+ options
1642
+ };
1643
+ }
1644
+ async function* scanBatches(plan, options = {}) {
1645
+ if (plan.format === "parquet") {
1646
+ for (const file of plan.files) {
1647
+ const readOptions = {};
1648
+ if (options.batchSize !== void 0) readOptions.batchSize = options.batchSize;
1649
+ for await (const batch of readParquetObjectBatches(plan.store, file.path, readOptions)) {
1650
+ yield batch.rows;
1651
+ }
1652
+ }
1653
+ return;
1654
+ }
1655
+ for await (const batch of scanPlannedIcebergRows({
1656
+ plan: plan.plan,
1657
+ ...options,
1658
+ readDataFile: async (file) => projectIcebergParquetBatches(
1659
+ plan.store,
1660
+ plan.table,
1661
+ file.path,
1662
+ file.partition,
1663
+ file.snapshotId,
1664
+ plan.options,
1665
+ options
1666
+ ),
1667
+ readDeleteFile: async (deleteFile) => readIcebergParquetDeletes(plan.store, deleteFile)
1668
+ })) {
1669
+ yield batch;
1670
+ }
1671
+ }
1672
+ async function* scanRows(plan, options = {}) {
1673
+ for await (const batch of scanBatches(plan, options)) {
1674
+ for (const row of batch) {
1675
+ yield row;
1676
+ }
1677
+ }
1678
+ }
1679
+ async function* projectIcebergParquetBatches(store, table, path, partition, snapshotId, planOptions, scanOptions) {
1680
+ const readOptions = {};
1681
+ if (scanOptions.batchSize !== void 0) readOptions.batchSize = scanOptions.batchSize;
1682
+ for await (const batch of readParquetObjectBatches(store, path, readOptions)) {
1683
+ yield {
1684
+ rowOffset: batch.rowOffset,
1685
+ rows: batch.rows.map((row) => {
1686
+ const projectOptions = { snapshotId };
1687
+ if (planOptions.select !== void 0) projectOptions.select = planOptions.select;
1688
+ return table.projectRow({ ...partition, ...row }, projectOptions);
1689
+ })
1690
+ };
1691
+ }
1692
+ }
1693
+
1694
+ export { IcebergRestCatalog, IcebergTable, IcebergUnsupportedCatalog, InMemoryRowsScanner, InMemoryRowsStore, ObjectStoreIcebergCommitCatalog, PACKAGE, applyIcebergDeletes, createInMemoryLake, icebergGlueCatalog, icebergNessieCatalog, icebergRestCatalog, inMemoryRowsScanner, loadIcebergTable, loadIcebergTableFromObjectStore, loadIcebergTableFromRest, loadTable, planFiles2 as planFiles, scanBatches, scanPlannedIcebergRows, scanRows, vectorHashJoin };