lakeql 0.1.7 → 0.1.9

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.
@@ -1,5 +1,6 @@
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';
1
+ import { Lake, vectorValue, scalarVectorValue, batchFromColumns, withObjectStoreReadControls, throwIfAborted, readControlSignal, readParquetObjectBatches, readIcebergParquetDeletes } from './chunk-6XXXYVXT.js';
2
+ import { stableStringify } from './chunk-ZVHJD6R3.js';
3
+ import { LakeqlError, jsonSafeValue, matches } from './chunk-4VJQ56HF.js';
3
4
 
4
5
  // ../core/dist/in-memory.js
5
6
  var InMemoryRowsScanner = class {
@@ -157,6 +158,92 @@ function projectPhysicalColumns(row, columns) {
157
158
  return out;
158
159
  }
159
160
 
161
+ // ../core/dist/object-store-json-cache.js
162
+ var ObjectStoreJsonCache = class {
163
+ store;
164
+ prefix;
165
+ ttlMs;
166
+ now;
167
+ constructor(options) {
168
+ this.store = options.store;
169
+ this.prefix = normalizeCachePrefix(options.prefix);
170
+ this.ttlMs = options.ttlMs;
171
+ this.now = options.now ?? Date.now;
172
+ }
173
+ async get(key) {
174
+ const path = this.pathFor(key);
175
+ const bytes = await this.store.get(path);
176
+ if (bytes === null)
177
+ return void 0;
178
+ let parsed;
179
+ try {
180
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
181
+ } catch (cause) {
182
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `Invalid JSON cache entry at ${path}`, {
183
+ path,
184
+ cause
185
+ });
186
+ }
187
+ if (!isStoredJsonCacheEntry(parsed)) {
188
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `Invalid JSON cache entry at ${path}`, {
189
+ path
190
+ });
191
+ }
192
+ if (parsed.expiresAt !== void 0 && parsed.expiresAt <= this.now()) {
193
+ await this.store.delete(path);
194
+ return void 0;
195
+ }
196
+ const entry = { value: parsed.value };
197
+ if (parsed.expiresAt !== void 0)
198
+ entry.expiresAt = parsed.expiresAt;
199
+ return entry;
200
+ }
201
+ async set(key, entry) {
202
+ if (entry.value === void 0) {
203
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "JSON cache value is not JSON serializable", {
204
+ key
205
+ });
206
+ }
207
+ const stored = { value: entry.value };
208
+ if (entry.expiresAt !== void 0) {
209
+ stored.expiresAt = entry.expiresAt;
210
+ } else if (this.ttlMs !== void 0) {
211
+ stored.expiresAt = this.now() + this.ttlMs;
212
+ }
213
+ let json;
214
+ try {
215
+ json = JSON.stringify(stored);
216
+ } catch (cause) {
217
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "JSON cache value is not JSON serializable", {
218
+ key,
219
+ cause
220
+ });
221
+ }
222
+ await this.store.put(this.pathFor(key), new TextEncoder().encode(json), {
223
+ contentType: "application/json"
224
+ });
225
+ }
226
+ async delete(key) {
227
+ await this.store.delete(this.pathFor(key));
228
+ }
229
+ pathFor(key) {
230
+ return `${this.prefix}/${encodeURIComponent(key)}.json`;
231
+ }
232
+ };
233
+ function objectStoreJsonCache(options) {
234
+ return new ObjectStoreJsonCache(options);
235
+ }
236
+ function normalizeCachePrefix(prefix) {
237
+ const trimmed = prefix.replace(/^\/+|\/+$/g, "");
238
+ if (trimmed === "") {
239
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "JSON cache prefix must be non-empty");
240
+ }
241
+ return trimmed;
242
+ }
243
+ function isStoredJsonCacheEntry(value) {
244
+ return typeof value === "object" && value !== null && "value" in value;
245
+ }
246
+
160
247
  // ../core/dist/vector-join.js
161
248
  function vectorHashJoin(left, right, options) {
162
249
  const normalized = validateVectorJoinOptions(left, right, options);
@@ -581,37 +668,61 @@ var IcebergRestCatalog = class {
581
668
  url;
582
669
  namespace;
583
670
  table;
584
- prefix;
671
+ explicitPrefix;
672
+ warehouse;
673
+ accessDelegation;
585
674
  token;
586
675
  fetchFn;
676
+ configPromise;
677
+ prefixPromise;
587
678
  constructor(options) {
588
679
  this.url = options.url;
589
680
  this.namespace = namespaceParts(options.namespace);
590
681
  this.table = requiredNonEmptyString(options.table, "table");
591
- this.prefix = catalogPrefixParts(options.prefix);
682
+ this.explicitPrefix = options.prefix === void 0 ? void 0 : catalogPrefixParts(options.prefix);
683
+ this.warehouse = options.warehouse === void 0 ? void 0 : requiredNonEmptyString(options.warehouse, "warehouse");
684
+ this.accessDelegation = options.accessDelegation?.join(",");
592
685
  this.token = options.token;
593
686
  this.fetchFn = options.fetch ?? fetch;
594
687
  }
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()
688
+ async loadTable(store, options = {}) {
689
+ const response = await this.loadTableResult();
690
+ const readControls = loadReadControls(options);
691
+ const controlledStore = withObjectStoreReadControls(store, readControls);
692
+ return new IcebergTable(controlledStore, response["metadata-location"], await hydrateMetadataManifests(controlledStore, response.metadata, readControls, options.cache));
693
+ }
694
+ async loadTableResult(options = {}) {
695
+ const url = await this.tableUrl(options.snapshots);
696
+ const headers = this.headers(false);
697
+ if (this.accessDelegation !== void 0)
698
+ headers.set("X-Iceberg-Access-Delegation", this.accessDelegation);
699
+ if (options.ifNoneMatch !== void 0)
700
+ headers.set("If-None-Match", options.ifNoneMatch);
701
+ const response = await this.requestJsonResponse(url, { method: "GET", headers });
702
+ return validateRestLoadTableResult(response.body, url, response.headers.get("etag"));
703
+ }
704
+ async loadConfig() {
705
+ if (this.configPromise === void 0) {
706
+ const url = this.configUrl();
707
+ this.configPromise = this.requestJson(url, { method: "GET" }).then((body) => validateRestCatalogConfig(body, url)).catch((cause) => {
708
+ this.configPromise = void 0;
709
+ throw cause;
600
710
  });
601
711
  }
602
- return new IcebergTable(store, response["metadata-location"], await hydrateMetadataManifests(store, validateMetadata(response.metadata)));
712
+ return await this.configPromise;
603
713
  }
604
714
  async listTables() {
605
- return validateListTablesResponse(await this.requestJson(this.namespaceTablesUrl(), {
715
+ return validateListTablesResponse(await this.requestJson(await this.namespaceTablesUrl(), {
606
716
  method: "GET"
607
717
  }));
608
718
  }
609
719
  async commitAppend(input) {
720
+ const tableUrl = await this.tableUrl();
610
721
  await input.store.put(input.manifestPath, new TextEncoder().encode(`${stableStringify(input.manifest)}
611
722
  `), { contentType: "application/json" });
612
723
  await input.store.put(input.nextMetadataPath, new TextEncoder().encode(`${JSON.stringify(input.metadata, null, 2)}
613
724
  `), { contentType: "application/json" });
614
- const response = await this.fetchFn(this.tableUrl(), {
725
+ const response = await this.fetchFn(tableUrl, {
615
726
  method: "POST",
616
727
  headers: this.headers(true),
617
728
  body: JSON.stringify({
@@ -641,7 +752,7 @@ var IcebergRestCatalog = class {
641
752
  return { committed: false };
642
753
  if (!response.ok) {
643
754
  throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg REST table commit failed", {
644
- url: this.tableUrl(),
755
+ url: tableUrl,
645
756
  status: response.status,
646
757
  statusText: response.statusText
647
758
  });
@@ -653,9 +764,12 @@ var IcebergRestCatalog = class {
653
764
  };
654
765
  }
655
766
  async requestJson(url, init) {
767
+ return (await this.requestJsonResponse(url, init)).body;
768
+ }
769
+ async requestJsonResponse(url, init) {
656
770
  const response = await this.fetchFn(url, {
657
771
  ...init,
658
- headers: this.headers(init.body !== void 0)
772
+ headers: init.headers ?? this.headers(init.body !== void 0)
659
773
  });
660
774
  if (!response.ok) {
661
775
  throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg REST catalog request failed", {
@@ -665,7 +779,7 @@ var IcebergRestCatalog = class {
665
779
  });
666
780
  }
667
781
  try {
668
- return await response.json();
782
+ return { body: await response.json(), headers: response.headers };
669
783
  } catch (cause) {
670
784
  throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg REST catalog response is not JSON", {
671
785
  url,
@@ -681,14 +795,50 @@ var IcebergRestCatalog = class {
681
795
  headers.set("authorization", `Bearer ${this.token}`);
682
796
  return headers;
683
797
  }
684
- tableUrl() {
685
- return restCatalogUrl(this.url, [...this.namespaceTableSegments(), this.table]);
798
+ async tableUrl(snapshots) {
799
+ const url = new URL(restCatalogUrl(this.url, [...await this.namespaceTableSegments(), this.table]));
800
+ if (snapshots !== void 0)
801
+ url.searchParams.set("snapshots", snapshots);
802
+ return url.toString();
803
+ }
804
+ async namespaceTablesUrl() {
805
+ return restCatalogUrl(this.url, await this.namespaceTableSegments());
806
+ }
807
+ async namespaceTableSegments() {
808
+ return [
809
+ "v1",
810
+ ...await this.prefixParts(),
811
+ "namespaces",
812
+ this.namespace.join(""),
813
+ "tables"
814
+ ];
815
+ }
816
+ async prefixParts() {
817
+ if (this.explicitPrefix !== void 0)
818
+ return this.explicitPrefix;
819
+ if (this.warehouse === void 0)
820
+ return [];
821
+ if (this.prefixPromise === void 0)
822
+ this.prefixPromise = this.computePrefixParts();
823
+ return await this.prefixPromise;
686
824
  }
687
- namespaceTablesUrl() {
688
- return restCatalogUrl(this.url, this.namespaceTableSegments());
825
+ async computePrefixParts() {
826
+ try {
827
+ const config = await this.loadConfig();
828
+ const serverPrefix = config.overrides.prefix ?? config.defaults.prefix;
829
+ if (serverPrefix !== void 0 && serverPrefix.trim() !== "") {
830
+ return catalogPrefixParts(serverPrefix);
831
+ }
832
+ } catch {
833
+ return [this.warehouse];
834
+ }
835
+ return [this.warehouse];
689
836
  }
690
- namespaceTableSegments() {
691
- return ["v1", ...this.prefix, "namespaces", this.namespace.join(""), "tables"];
837
+ configUrl() {
838
+ const url = new URL(restCatalogUrl(this.url, ["v1", "config"]));
839
+ if (this.warehouse !== void 0)
840
+ url.searchParams.set("warehouse", this.warehouse);
841
+ return url.toString();
692
842
  }
693
843
  };
694
844
  function icebergRestCatalog(options) {
@@ -703,7 +853,7 @@ var IcebergUnsupportedCatalog = class {
703
853
  this.namespace = namespaceParts(namespace);
704
854
  this.table = requiredNonEmptyString(table, "table");
705
855
  }
706
- async loadTable(_store) {
856
+ async loadTable(_store, _options = {}) {
707
857
  throw this.unsupported("loadTable");
708
858
  }
709
859
  async listTables() {
@@ -743,7 +893,7 @@ async function loadIcebergTable(options) {
743
893
  throwIfAborted(readControls.signal);
744
894
  const text = new TextDecoder().decode(bytes);
745
895
  try {
746
- return new IcebergTable(store, options.metadataPath, await hydrateMetadataManifests(store, validateMetadata(JSON.parse(text)), readControls));
896
+ return new IcebergTable(store, options.metadataPath, await hydrateMetadataManifests(store, validateMetadata(JSON.parse(text)), readControls, options.cache));
747
897
  } catch (cause) {
748
898
  if (cause instanceof LakeqlError)
749
899
  throw cause;
@@ -761,10 +911,23 @@ async function loadIcebergTableFromObjectStore(options) {
761
911
  const versionHintPath = `${metadataPrefix}version-hint.text`;
762
912
  const hintedVersion = await readVersionHint(store, versionHintPath, readControls);
763
913
  const metadataPath = hintedVersion === void 0 ? await latestMetadataPathFromList(store, metadataPrefix, readControls) : `${metadataPrefix}v${hintedVersion}.metadata.json`;
764
- return await loadIcebergTable({ store, metadataPath, ...readControls });
914
+ return await loadIcebergTable({
915
+ store,
916
+ metadataPath,
917
+ ...readControls,
918
+ ...options.cache !== void 0 ? { cache: options.cache } : {}
919
+ });
765
920
  }
766
921
  async function loadIcebergTableFromRest(options) {
767
- return await icebergRestCatalog(options).loadTable(withObjectStoreReadControls(options.store, loadReadControls(options)));
922
+ const catalog = icebergRestCatalog(options);
923
+ const response = await catalog.loadTableResult();
924
+ const baseStore = options.store ?? (options.storeFactory === void 0 ? void 0 : await options.storeFactory({ ...response, catalog }));
925
+ if (baseStore === void 0) {
926
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Iceberg REST table loading requires a store or storeFactory");
927
+ }
928
+ const readControls = loadReadControls(options);
929
+ const store = withObjectStoreReadControls(baseStore, readControls);
930
+ return new IcebergTable(store, response["metadata-location"], await hydrateMetadataManifests(store, response.metadata, readControls, options.cache));
768
931
  }
769
932
  function loadReadControls(options) {
770
933
  const controls = {};
@@ -919,23 +1082,81 @@ function metadataVersionHintPath(metadataPath) {
919
1082
  function supportsConditionalPut(store) {
920
1083
  return typeof store.conditionalPut === "function";
921
1084
  }
922
- async function hydrateMetadataManifests(store, metadata, controls = {}) {
1085
+ async function hydrateMetadataManifests(store, metadata, controls = {}, persistentCache) {
923
1086
  const hydrated = cloneMetadata(metadata);
924
- const tablePrefix = tableLocationObjectPrefix(hydrated.location);
925
- for (const snapshot of hydrated.snapshots) {
1087
+ const tableLocation = tableLocationRef(hydrated.location);
1088
+ const cache = {
1089
+ lists: /* @__PURE__ */ new Map(),
1090
+ manifests: /* @__PURE__ */ new Map(),
1091
+ tableCacheKey: icebergTableCacheKey(hydrated)
1092
+ };
1093
+ if (persistentCache !== void 0)
1094
+ cache.persistent = persistentCache;
1095
+ await Promise.all(hydrated.snapshots.map(async (snapshot) => {
926
1096
  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
- }));
1097
+ snapshot.manifests = mergeDeleteManifests(await hydrateSnapshotManifests(store, snapshot, tableLocation, cache, controls));
934
1098
  throwIfAborted(controls.signal);
935
- snapshot.manifests = mergeDeleteManifests(manifests);
936
- }
1099
+ }));
937
1100
  return hydrated;
938
1101
  }
1102
+ async function hydrateSnapshotManifests(store, snapshot, tableLocation, cache, controls) {
1103
+ const manifestReferences = snapshot.manifests ?? (snapshot["manifest-list"] !== void 0 ? await cachedManifestList(store, validateManifestSourcedPath(snapshot["manifest-list"], tableLocation), cache) : []);
1104
+ return await Promise.all(manifestReferences.map(async (manifest) => {
1105
+ throwIfAborted(controls.signal);
1106
+ const manifestPath = validateManifestSourcedPath(manifest.path, tableLocation);
1107
+ if (Array.isArray(manifest.files))
1108
+ return validateManifestPaths(manifest, manifestPath, tableLocation);
1109
+ return await cachedManifest(store, manifestPath, tableLocation, cache);
1110
+ }));
1111
+ }
1112
+ function cachedManifestList(store, path, cache) {
1113
+ const cached = cache.lists.get(path);
1114
+ if (cached !== void 0)
1115
+ return cached;
1116
+ const key = icebergMetadataCacheKey(cache, "manifest-list", path);
1117
+ const promise = readPersistentCache(cache.persistent, key, cloneManifestReferences).then(async (persistent) => {
1118
+ if (persistent !== void 0)
1119
+ return persistent;
1120
+ const manifests = await readManifestList(store, path);
1121
+ await cache.persistent?.set(key, { value: manifests.map(cloneManifestOrReference) });
1122
+ return manifests;
1123
+ }).catch((cause) => {
1124
+ cache.lists.delete(path);
1125
+ throw cause;
1126
+ });
1127
+ cache.lists.set(path, promise);
1128
+ return promise;
1129
+ }
1130
+ function cachedManifest(store, path, tableLocation, cache) {
1131
+ const cached = cache.manifests.get(path);
1132
+ if (cached !== void 0)
1133
+ return cached;
1134
+ const key = icebergMetadataCacheKey(cache, "manifest", path);
1135
+ const promise = readPersistentCache(cache.persistent, key, cloneManifest).then(async (persistent) => {
1136
+ if (persistent !== void 0)
1137
+ return validateManifestPaths(persistent, path, tableLocation);
1138
+ const manifest = await readManifest(store, path, tableLocation);
1139
+ await cache.persistent?.set(key, { value: cloneManifest(manifest) });
1140
+ return manifest;
1141
+ }).catch((cause) => {
1142
+ cache.manifests.delete(path);
1143
+ throw cause;
1144
+ });
1145
+ cache.manifests.set(path, promise);
1146
+ return promise;
1147
+ }
1148
+ async function readPersistentCache(cache, key, clone) {
1149
+ const entry = await cache?.get(key);
1150
+ if (entry === void 0)
1151
+ return void 0;
1152
+ return clone(entry.value);
1153
+ }
1154
+ function icebergTableCacheKey(metadata) {
1155
+ return `${metadata["table-uuid"]}:${metadata.location}`;
1156
+ }
1157
+ function icebergMetadataCacheKey(cache, kind, path) {
1158
+ return `iceberg:${cache.tableCacheKey}:${kind}:${path}`;
1159
+ }
939
1160
  function mergeDeleteManifests(manifests) {
940
1161
  const deleteFiles = manifests.flatMap((manifest) => manifest.deleteFiles ?? []);
941
1162
  const dataManifests = manifests.filter((manifest) => manifest.files.length > 0);
@@ -982,14 +1203,14 @@ async function readManifestList(store, path) {
982
1203
  });
983
1204
  }
984
1205
  }
985
- async function readManifest(store, path, tablePrefix = "") {
1206
+ async function readManifest(store, path, tableLocation = tableLocationRef("")) {
986
1207
  const bytes = await store.get(path);
987
1208
  if (!bytes) {
988
1209
  throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", `No Iceberg manifest at ${path}`, { path });
989
1210
  }
990
1211
  try {
991
1212
  const manifest = avroObjectContainer(bytes) ? validateAvroManifest(await decodeAvroObjectContainer(bytes), path) : validateManifest(JSON.parse(new TextDecoder().decode(bytes)), path);
992
- return validateManifestPaths(manifest, path, tablePrefix);
1213
+ return validateManifestPaths(manifest, path, tableLocation);
993
1214
  } catch (cause) {
994
1215
  if (cause instanceof LakeqlError)
995
1216
  throw cause;
@@ -1058,7 +1279,7 @@ function validateAvroManifest(records, path) {
1058
1279
  const manifest = { path, files };
1059
1280
  if (deleteFiles.length > 0)
1060
1281
  manifest.deleteFiles = deleteFiles;
1061
- return validateManifestPaths(manifest, path);
1282
+ return manifest;
1062
1283
  }
1063
1284
  function avroDeleteContent(content, dataFile) {
1064
1285
  if (content === 1 && (String(dataFile.file_format).toLowerCase() === "puffin" || dataFile.content_offset !== void 0 || dataFile.content_size_in_bytes !== void 0)) {
@@ -1167,57 +1388,95 @@ function validateManifestContent(content, path) {
1167
1388
  content
1168
1389
  });
1169
1390
  }
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);
1391
+ function validateManifestPaths(manifest, path, tableLocation = tableLocationRef("")) {
1392
+ validateManifestSourcedPath(manifest.path, tableLocation);
1393
+ return {
1394
+ ...manifest,
1395
+ path,
1396
+ files: manifest.files.map((file) => ({
1397
+ ...file,
1398
+ path: validateManifestSourcedPath(file.path, tableLocation),
1399
+ ...file.deleteFiles !== void 0 ? {
1400
+ deleteFiles: file.deleteFiles.map((deleteFile) => ({
1401
+ ...deleteFile,
1402
+ path: validateManifestSourcedPath(deleteFile.path, tableLocation)
1403
+ }))
1404
+ } : {}
1405
+ })),
1406
+ ...manifest.deleteFiles !== void 0 ? {
1407
+ deleteFiles: manifest.deleteFiles.map((deleteFile) => ({
1408
+ ...deleteFile,
1409
+ path: validateManifestSourcedPath(deleteFile.path, tableLocation)
1410
+ }))
1411
+ } : {}
1412
+ };
1413
+ }
1414
+ function validateManifestSourcedPath(path, tableLocation = tableLocationRef("")) {
1415
+ const normalized = normalizeManifestSourcedPath(path, tableLocation);
1416
+ validateRelativeObjectPath(normalized, path);
1417
+ if (tableLocation.prefix !== "" && normalized !== tableLocation.prefix && !normalized.startsWith(`${tableLocation.prefix}/`)) {
1418
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path escapes table location: ${path}`, {
1419
+ path,
1420
+ tableLocation: tableLocation.prefix
1421
+ });
1180
1422
  }
1181
- return { ...manifest, path };
1423
+ return normalized;
1182
1424
  }
1183
- function validateManifestSourcedPath(path, tablePrefix = "") {
1425
+ function normalizeManifestSourcedPath(path, tableLocation) {
1184
1426
  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
- });
1427
+ if (tableLocation.uriAuthority === void 0) {
1428
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path must be relative: ${path}`, {
1429
+ path
1430
+ });
1431
+ }
1432
+ let url;
1433
+ try {
1434
+ url = new URL(path);
1435
+ } catch {
1436
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path is invalid: ${path}`, {
1437
+ path
1438
+ });
1439
+ }
1440
+ const authority = `${url.protocol}//${url.host}`;
1441
+ if (authority !== tableLocation.uriAuthority) {
1442
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path escapes table location: ${path}`, {
1443
+ path,
1444
+ tableLocation: tableLocation.uriAuthority
1445
+ });
1446
+ }
1447
+ return trimSlashes(decodeURIComponent(url.pathname));
1188
1448
  }
1449
+ return path;
1450
+ }
1451
+ function validateRelativeObjectPath(path, originalPath) {
1189
1452
  for (const segment of path.split("/")) {
1190
1453
  let decoded;
1191
1454
  try {
1192
1455
  decoded = decodeURIComponent(segment);
1193
1456
  } catch {
1194
- throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path is invalid: ${path}`, {
1195
- path
1457
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path is invalid: ${originalPath}`, {
1458
+ path: originalPath
1196
1459
  });
1197
1460
  }
1198
1461
  if (decoded === "." || decoded === "..") {
1199
- throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path contains traversal: ${path}`, {
1200
- path
1462
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", `Iceberg manifest path contains traversal: ${originalPath}`, {
1463
+ path: originalPath
1201
1464
  });
1202
1465
  }
1203
1466
  }
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
1467
  }
1212
- function tableLocationObjectPrefix(location) {
1468
+ function tableLocationRef(location) {
1213
1469
  const trimmed = trimTrailingSlash(location.trim());
1214
1470
  if (trimmed === "" || !trimmed.includes("/"))
1215
- return "";
1471
+ return { prefix: "" };
1216
1472
  if (/^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed)) {
1217
1473
  const url = new URL(trimmed);
1218
- return trimSlashes(decodeURIComponent(url.pathname));
1474
+ return {
1475
+ prefix: trimSlashes(decodeURIComponent(url.pathname)),
1476
+ uriAuthority: `${url.protocol}//${url.host}`
1477
+ };
1219
1478
  }
1220
- return trimSlashes(trimmed);
1479
+ return { prefix: trimSlashes(trimmed) };
1221
1480
  }
1222
1481
  function trimSlashes(value) {
1223
1482
  return value.replace(/^\/+|\/+$/gu, "");
@@ -1433,6 +1692,9 @@ function cloneManifestOrReference(manifest) {
1433
1692
  }
1434
1693
  return cloneManifest(manifest);
1435
1694
  }
1695
+ function cloneManifestReferences(manifests) {
1696
+ return manifests.map(cloneManifestOrReference);
1697
+ }
1436
1698
  function snapshotManifests(snapshot) {
1437
1699
  return snapshot.manifests ?? [];
1438
1700
  }
@@ -1453,6 +1715,71 @@ function validateListTablesResponse(value) {
1453
1715
  return { namespace: identifier.namespace, name: identifier.name };
1454
1716
  });
1455
1717
  }
1718
+ function validateRestLoadTableResult(value, url, etag) {
1719
+ if (!isRecord(value) || typeof value["metadata-location"] !== "string") {
1720
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Invalid Iceberg REST load table response", {
1721
+ url
1722
+ });
1723
+ }
1724
+ return {
1725
+ "metadata-location": value["metadata-location"],
1726
+ metadata: validateMetadata(value.metadata),
1727
+ config: validateStringRecord(value.config, "Iceberg REST load table config"),
1728
+ "storage-credentials": validateStorageCredentials(value["storage-credentials"]),
1729
+ etag
1730
+ };
1731
+ }
1732
+ function validateRestCatalogConfig(value, url) {
1733
+ if (!isRecord(value)) {
1734
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Invalid Iceberg REST catalog config response", {
1735
+ url
1736
+ });
1737
+ }
1738
+ const config = {
1739
+ defaults: validateStringRecord(value.defaults, "Iceberg REST catalog defaults"),
1740
+ overrides: validateStringRecord(value.overrides, "Iceberg REST catalog overrides")
1741
+ };
1742
+ if (Array.isArray(value.endpoints) && value.endpoints.every((endpoint) => typeof endpoint === "string")) {
1743
+ config.endpoints = value.endpoints;
1744
+ }
1745
+ if (typeof value["idempotency-key-lifetime"] === "string") {
1746
+ config["idempotency-key-lifetime"] = value["idempotency-key-lifetime"];
1747
+ }
1748
+ return config;
1749
+ }
1750
+ function validateStorageCredentials(value) {
1751
+ if (value === void 0)
1752
+ return [];
1753
+ if (!Array.isArray(value)) {
1754
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Invalid Iceberg REST storage credentials");
1755
+ }
1756
+ return value.map((credential) => {
1757
+ if (!isRecord(credential) || typeof credential.prefix !== "string") {
1758
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Invalid Iceberg REST storage credential");
1759
+ }
1760
+ return {
1761
+ prefix: credential.prefix,
1762
+ config: validateStringRecord(credential.config, "Iceberg REST storage credential config")
1763
+ };
1764
+ });
1765
+ }
1766
+ function validateStringRecord(value, label) {
1767
+ if (value === void 0)
1768
+ return {};
1769
+ if (!isRecord(value)) {
1770
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `${label} must be an object`);
1771
+ }
1772
+ const out = {};
1773
+ for (const [key, entry] of Object.entries(value)) {
1774
+ if (typeof entry !== "string") {
1775
+ throw new LakeqlError("LAKEQL_CATALOG_ERROR", `${label} values must be strings`, {
1776
+ key
1777
+ });
1778
+ }
1779
+ out[key] = entry;
1780
+ }
1781
+ return out;
1782
+ }
1456
1783
  function validateMetadata(value) {
1457
1784
  if (!isRecord(value))
1458
1785
  throw new LakeqlError("LAKEQL_CATALOG_ERROR", "Iceberg metadata must be an object");
@@ -1691,4 +2018,4 @@ async function* projectIcebergParquetBatches(store, table, path, partition, snap
1691
2018
  }
1692
2019
  }
1693
2020
 
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 };
2021
+ export { IcebergRestCatalog, IcebergTable, IcebergUnsupportedCatalog, InMemoryRowsScanner, InMemoryRowsStore, ObjectStoreIcebergCommitCatalog, ObjectStoreJsonCache, PACKAGE, applyIcebergDeletes, createInMemoryLake, icebergGlueCatalog, icebergNessieCatalog, icebergRestCatalog, inMemoryRowsScanner, loadIcebergTable, loadIcebergTableFromObjectStore, loadIcebergTableFromRest, loadTable, objectStoreJsonCache, planFiles2 as planFiles, scanBatches, scanPlannedIcebergRows, scanRows, vectorHashJoin };