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