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.
- package/README.md +18 -5
- package/dist/bin.js +816 -23
- package/dist/{chunk-BFLGC6Y5.js → chunk-2XZJK7EF.js} +398 -71
- package/dist/{chunk-TFD5RFKB.js → chunk-4VJQ56HF.js} +424 -4
- package/dist/{chunk-RZL45ZSN.js → chunk-6XXXYVXT.js} +4048 -3341
- package/dist/chunk-ZVHJD6R3.js +1175 -0
- package/dist/cloudflare.d.ts +20 -3
- package/dist/cloudflare.js +105 -5
- package/dist/{geo-backend-TSQJWAAB.js → geo-backend-MY6MET3L.js} +1 -1
- package/dist/index.d.ts +502 -226
- package/dist/index.js +4 -3
- package/dist/node.d.ts +28 -5
- package/dist/node.js +220 -25
- package/dist/window-backend-DIMZN7EZ.js +905 -0
- package/package.json +10 -8
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Lake,
|
|
2
|
-
import {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
688
|
-
|
|
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
|
-
|
|
691
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
|
925
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
1171
|
-
validateManifestSourcedPath(manifest.path,
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
|
1423
|
+
return normalized;
|
|
1182
1424
|
}
|
|
1183
|
-
function
|
|
1425
|
+
function normalizeManifestSourcedPath(path, tableLocation) {
|
|
1184
1426
|
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//iu.test(path) || path.startsWith("/")) {
|
|
1185
|
-
|
|
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: ${
|
|
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: ${
|
|
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
|
|
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
|
|
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 };
|