lakeql 0.1.8 → 0.2.0
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 +57 -61
- package/dist/bin.js +60 -8080
- package/dist/chunk-32PE6IK4.js +20803 -0
- package/dist/{chunk-5K5JMJ2M.js → chunk-3WUY56UD.js} +682 -3
- package/dist/chunk-LWF5FKHB.js +1175 -0
- package/dist/{chunk-TFD5RFKB.js → chunk-WGHR35QU.js} +426 -4
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.js +5 -4
- package/dist/{geo-backend-TSQJWAAB.js → geo-backend-WYCTLPNS.js} +1 -1
- package/dist/index.d.ts +581 -338
- package/dist/index.js +4 -3
- package/dist/node.d.ts +1 -1
- package/dist/node.js +48 -9
- package/dist/window-backend-OECETVJ2.js +905 -0
- package/package.json +9 -9
- package/dist/chunk-MXGEAVHL.js +0 -11381
|
@@ -0,0 +1,1175 @@
|
|
|
1
|
+
import { isIntervalValue, LakeqlError, isTimestampValue, compareTimestampValues, evaluate, matches, jsonSafeValue } from './chunk-WGHR35QU.js';
|
|
2
|
+
|
|
3
|
+
// ../core/dist/expr-utils.js
|
|
4
|
+
function collectExprColumns(expr, columns) {
|
|
5
|
+
if (!expr)
|
|
6
|
+
return;
|
|
7
|
+
switch (expr.kind) {
|
|
8
|
+
case "column":
|
|
9
|
+
columns.add(expr.name);
|
|
10
|
+
return;
|
|
11
|
+
case "literal":
|
|
12
|
+
return;
|
|
13
|
+
case "compare":
|
|
14
|
+
collectExprColumns(expr.left, columns);
|
|
15
|
+
collectExprColumns(expr.right, columns);
|
|
16
|
+
return;
|
|
17
|
+
case "in":
|
|
18
|
+
collectExprColumns(expr.target, columns);
|
|
19
|
+
for (const value of expr.values)
|
|
20
|
+
collectExprColumns(value, columns);
|
|
21
|
+
return;
|
|
22
|
+
case "between":
|
|
23
|
+
collectExprColumns(expr.target, columns);
|
|
24
|
+
collectExprColumns(expr.low, columns);
|
|
25
|
+
collectExprColumns(expr.high, columns);
|
|
26
|
+
return;
|
|
27
|
+
case "null-check":
|
|
28
|
+
collectExprColumns(expr.target, columns);
|
|
29
|
+
return;
|
|
30
|
+
case "logical":
|
|
31
|
+
for (const operand of expr.operands)
|
|
32
|
+
collectExprColumns(operand, columns);
|
|
33
|
+
return;
|
|
34
|
+
case "not":
|
|
35
|
+
collectExprColumns(expr.operand, columns);
|
|
36
|
+
return;
|
|
37
|
+
case "like":
|
|
38
|
+
collectExprColumns(expr.target, columns);
|
|
39
|
+
return;
|
|
40
|
+
case "call":
|
|
41
|
+
for (const arg of expr.args)
|
|
42
|
+
collectExprColumns(arg, columns);
|
|
43
|
+
return;
|
|
44
|
+
case "arithmetic":
|
|
45
|
+
collectExprColumns(expr.left, columns);
|
|
46
|
+
collectExprColumns(expr.right, columns);
|
|
47
|
+
return;
|
|
48
|
+
case "case":
|
|
49
|
+
for (const branch of expr.whens) {
|
|
50
|
+
collectExprColumns(branch.when, columns);
|
|
51
|
+
collectExprColumns(branch.value, columns);
|
|
52
|
+
}
|
|
53
|
+
collectExprColumns(expr.else, columns);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ../core/dist/object-key.js
|
|
59
|
+
var PROTOTYPE_MUTATION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
60
|
+
function isPrototypeMutationKey(key) {
|
|
61
|
+
return PROTOTYPE_MUTATION_KEYS.has(key);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ../core/dist/window-snapshot.js
|
|
65
|
+
function isWindowExprSnapshot(value) {
|
|
66
|
+
if (!isRecord(value))
|
|
67
|
+
return false;
|
|
68
|
+
const fn = value.fn;
|
|
69
|
+
const validFn = typeof fn === "string" || isRecord(fn) && typeof fn.aggregate === "string" && Object.keys(fn).length === 1;
|
|
70
|
+
return validFn && Array.isArray(value.args) && value.args.every(isExprSnapshot) && isRecord(value.over) && Array.isArray(value.over.partitionBy) && value.over.partitionBy.every(isExprSnapshot) && Array.isArray(value.over.orderBy) && value.over.orderBy.every(isWindowOrderTermSnapshot) && (value.over.frame === void 0 || isWindowFrameSnapshot(value.over.frame)) && (value.filter === void 0 || isExprSnapshot(value.filter)) && (value.ignoreNulls === void 0 || typeof value.ignoreNulls === "boolean") && (value.distinct === void 0 || typeof value.distinct === "boolean");
|
|
71
|
+
}
|
|
72
|
+
function isWindowOrderTermSnapshot(value) {
|
|
73
|
+
return isRecord(value) && isExprSnapshot(value.expr) && (value.direction === void 0 || value.direction === "asc" || value.direction === "desc") && (value.nulls === void 0 || value.nulls === "first" || value.nulls === "last");
|
|
74
|
+
}
|
|
75
|
+
function isWindowFrameSnapshot(value) {
|
|
76
|
+
return isRecord(value) && (value.mode === "rows" || value.mode === "range" || value.mode === "groups") && isWindowFrameBoundSnapshot(value.start) && isWindowFrameBoundSnapshot(value.end) && (value.exclude === "no-others" || value.exclude === "current-row" || value.exclude === "group" || value.exclude === "ties");
|
|
77
|
+
}
|
|
78
|
+
function isWindowFrameBoundSnapshot(value) {
|
|
79
|
+
return isRecord(value) && (value.kind === "unbounded-preceding" || value.kind === "preceding" || value.kind === "current-row" || value.kind === "following" || value.kind === "unbounded-following") && (value.offset === void 0 || isExprSnapshot(value.offset));
|
|
80
|
+
}
|
|
81
|
+
function isExprSnapshot(value) {
|
|
82
|
+
if (!isRecord(value) || typeof value.kind !== "string")
|
|
83
|
+
return false;
|
|
84
|
+
switch (value.kind) {
|
|
85
|
+
case "literal":
|
|
86
|
+
return isWindowSnapshotValue(value.value) || isIntervalValue(value.value);
|
|
87
|
+
case "column":
|
|
88
|
+
return typeof value.name === "string";
|
|
89
|
+
case "compare":
|
|
90
|
+
return (value.op === "eq" || value.op === "ne" || value.op === "lt" || value.op === "lte" || value.op === "gt" || value.op === "gte") && isExprSnapshot(value.left) && isExprSnapshot(value.right);
|
|
91
|
+
case "in":
|
|
92
|
+
return typeof value.negated === "boolean" && isExprSnapshot(value.target) && Array.isArray(value.values) && value.values.every(isExprSnapshot);
|
|
93
|
+
case "between":
|
|
94
|
+
return isExprSnapshot(value.target) && isExprSnapshot(value.low) && isExprSnapshot(value.high);
|
|
95
|
+
case "null-check":
|
|
96
|
+
return typeof value.negated === "boolean" && isExprSnapshot(value.target);
|
|
97
|
+
case "logical":
|
|
98
|
+
return (value.op === "and" || value.op === "or") && Array.isArray(value.operands) && value.operands.every(isExprSnapshot);
|
|
99
|
+
case "not":
|
|
100
|
+
return isExprSnapshot(value.operand);
|
|
101
|
+
case "like":
|
|
102
|
+
return typeof value.caseInsensitive === "boolean" && isExprSnapshot(value.target) && typeof value.pattern === "string";
|
|
103
|
+
case "call":
|
|
104
|
+
return typeof value.fn === "string" && Array.isArray(value.args) && value.args.every(isExprSnapshot);
|
|
105
|
+
case "arithmetic":
|
|
106
|
+
return (value.op === "add" || value.op === "sub" || value.op === "mul" || value.op === "div" || value.op === "mod") && isExprSnapshot(value.left) && isExprSnapshot(value.right);
|
|
107
|
+
case "case":
|
|
108
|
+
return Array.isArray(value.whens) && value.whens.every((branch) => isRecord(branch) && isExprSnapshot(branch.when) && isExprSnapshot(branch.value)) && (value.else === void 0 || isExprSnapshot(value.else));
|
|
109
|
+
default:
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function isWindowSnapshotValue(value) {
|
|
114
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint";
|
|
115
|
+
}
|
|
116
|
+
function isRecord(value) {
|
|
117
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ../core/dist/manifest.js
|
|
121
|
+
function createTaskManifest(input) {
|
|
122
|
+
const normalizedTasks = input.tasks.map(normalizeTaskInput);
|
|
123
|
+
const snapshot = input.snapshot ?? snapshotFromTasks(normalizedTasks);
|
|
124
|
+
const tasks = normalizedTasks.map((task, index) => ({
|
|
125
|
+
id: taskId(input.jobId, index, task),
|
|
126
|
+
input: task,
|
|
127
|
+
outputRole: input.outputRole ?? "rows"
|
|
128
|
+
}));
|
|
129
|
+
const planFingerprint = fingerprint({
|
|
130
|
+
version: 1,
|
|
131
|
+
snapshot,
|
|
132
|
+
tasks: tasks.map((task) => task.input)
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
version: 1,
|
|
136
|
+
jobId: input.jobId,
|
|
137
|
+
planFingerprint,
|
|
138
|
+
snapshot,
|
|
139
|
+
tasks
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function createOutputManifest(input) {
|
|
143
|
+
return {
|
|
144
|
+
version: 1,
|
|
145
|
+
jobId: input.jobId,
|
|
146
|
+
planFingerprint: input.planFingerprint,
|
|
147
|
+
entries: input.entries.map(normalizeOutputEntry)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async function createOutputManifestFromCheckpoints(input) {
|
|
151
|
+
const entries = [];
|
|
152
|
+
for await (const checkpoint of input.checkpoints.list(input.jobId)) {
|
|
153
|
+
if (checkpoint.outputs !== void 0) {
|
|
154
|
+
entries.push(...checkpoint.outputs);
|
|
155
|
+
} else if (checkpoint.output !== void 0) {
|
|
156
|
+
entries.push(checkpoint.output);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
entries.sort((left, right) => left.taskId.localeCompare(right.taskId) || left.outputPath.localeCompare(right.outputPath));
|
|
160
|
+
return createOutputManifest({
|
|
161
|
+
jobId: input.jobId,
|
|
162
|
+
planFingerprint: input.planFingerprint,
|
|
163
|
+
entries
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function writeOutputManifest(store, path, manifest) {
|
|
167
|
+
const bytes = new TextEncoder().encode(`${stableStringify(manifest)}
|
|
168
|
+
`);
|
|
169
|
+
await store.put(path, bytes, { contentType: "application/json" });
|
|
170
|
+
const head = await store.head(path);
|
|
171
|
+
const result = {
|
|
172
|
+
path,
|
|
173
|
+
byteSize: head?.size ?? bytes.byteLength
|
|
174
|
+
};
|
|
175
|
+
if (head?.etag !== void 0)
|
|
176
|
+
result.etag = head.etag;
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
async function readOutputManifest(store, path) {
|
|
180
|
+
const bytes = await store.get(path);
|
|
181
|
+
if (!bytes) {
|
|
182
|
+
throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", `No output manifest at ${path}`, { path });
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return parseOutputManifest(JSON.parse(new TextDecoder().decode(bytes)));
|
|
186
|
+
} catch (cause) {
|
|
187
|
+
if (cause instanceof LakeqlError)
|
|
188
|
+
throw cause;
|
|
189
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Output manifest is invalid JSON", {
|
|
190
|
+
path,
|
|
191
|
+
cause
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function createBookmark(init) {
|
|
196
|
+
const position = {
|
|
197
|
+
fileIndex: init.position.fileIndex,
|
|
198
|
+
rowGroup: init.position.rowGroup,
|
|
199
|
+
rowOffset: init.position.rowOffset
|
|
200
|
+
};
|
|
201
|
+
if (init.position.taskId !== void 0)
|
|
202
|
+
position.taskId = init.position.taskId;
|
|
203
|
+
if (init.position.outputManifestCursor !== void 0) {
|
|
204
|
+
position.outputManifestCursor = init.position.outputManifestCursor;
|
|
205
|
+
}
|
|
206
|
+
const bookmark = {
|
|
207
|
+
version: 1,
|
|
208
|
+
planFingerprint: init.planFingerprint,
|
|
209
|
+
snapshot: init.snapshot,
|
|
210
|
+
position
|
|
211
|
+
};
|
|
212
|
+
if (init.query !== void 0)
|
|
213
|
+
bookmark.query = normalizeBookmarkQuery(init.query);
|
|
214
|
+
if (init.writeState !== void 0)
|
|
215
|
+
bookmark.writeState = normalizeBookmarkWriteState(init.writeState);
|
|
216
|
+
if (init.operatorState !== void 0) {
|
|
217
|
+
bookmark.operatorState = normalizeBookmarkOperatorState(init.operatorState);
|
|
218
|
+
}
|
|
219
|
+
return bookmark;
|
|
220
|
+
}
|
|
221
|
+
function assertBookmarkMatches(bookmark, planFingerprint) {
|
|
222
|
+
if (bookmark.planFingerprint !== planFingerprint) {
|
|
223
|
+
throw new LakeqlError("LAKEQL_BOOKMARK_STALE", "Bookmark does not match the current query plan", {
|
|
224
|
+
bookmarkPlanFingerprint: bookmark.planFingerprint,
|
|
225
|
+
planFingerprint
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function signPaginationToken(bookmark, secret) {
|
|
230
|
+
const payload = stableStringify(bookmark);
|
|
231
|
+
const signature = await hmac(payload, secret);
|
|
232
|
+
return `${base64UrlEncode(new TextEncoder().encode(payload))}.${signature}`;
|
|
233
|
+
}
|
|
234
|
+
async function verifyPaginationToken(token, secret) {
|
|
235
|
+
const [payloadPart, signaturePart, extra] = token.split(".");
|
|
236
|
+
if (!payloadPart || !signaturePart || extra !== void 0) {
|
|
237
|
+
throwInvalidBookmark("Pagination token must contain a payload and signature");
|
|
238
|
+
}
|
|
239
|
+
const payloadBytes = base64UrlDecode(payloadPart);
|
|
240
|
+
const payload = new TextDecoder().decode(payloadBytes);
|
|
241
|
+
const expected = await hmac(payload, secret);
|
|
242
|
+
if (!constantTimeEqual(signaturePart, expected)) {
|
|
243
|
+
throwInvalidBookmark("Pagination token signature is invalid");
|
|
244
|
+
}
|
|
245
|
+
const parsed = JSON.parse(payload);
|
|
246
|
+
return parseBookmark(parsed);
|
|
247
|
+
}
|
|
248
|
+
var MemoryCheckpointAdapter = class {
|
|
249
|
+
checkpoints = /* @__PURE__ */ new Map();
|
|
250
|
+
taskJobs = /* @__PURE__ */ new Map();
|
|
251
|
+
async get(taskId2) {
|
|
252
|
+
const checkpoint = this.checkpoints.get(taskId2);
|
|
253
|
+
return checkpoint ? cloneCheckpoint(checkpoint) : void 0;
|
|
254
|
+
}
|
|
255
|
+
async put(checkpoint) {
|
|
256
|
+
this.checkpoints.set(checkpoint.taskId, cloneCheckpoint(checkpoint));
|
|
257
|
+
this.taskJobs.set(checkpoint.taskId, jobIdFromTaskId(checkpoint.taskId));
|
|
258
|
+
}
|
|
259
|
+
async *list(jobId) {
|
|
260
|
+
const checkpoints = [...this.checkpoints.values()].sort((a, b) => a.taskId.localeCompare(b.taskId));
|
|
261
|
+
for (const checkpoint of checkpoints) {
|
|
262
|
+
if (jobId !== void 0 && this.taskJobs.get(checkpoint.taskId) !== jobId)
|
|
263
|
+
continue;
|
|
264
|
+
yield cloneCheckpoint(checkpoint);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
function memoryCheckpointAdapter() {
|
|
269
|
+
return new MemoryCheckpointAdapter();
|
|
270
|
+
}
|
|
271
|
+
async function advanceTaskCheckpoint(checkpoints, input) {
|
|
272
|
+
const checkpoint = transitionTaskCheckpoint(await checkpoints.get(input.taskId), input);
|
|
273
|
+
await checkpoints.put(checkpoint);
|
|
274
|
+
return checkpoint;
|
|
275
|
+
}
|
|
276
|
+
function transitionTaskCheckpoint(existing, input) {
|
|
277
|
+
if (!existing)
|
|
278
|
+
return createCheckpoint(input);
|
|
279
|
+
if (existing.taskId !== input.taskId) {
|
|
280
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Task checkpoint id does not match transition", {
|
|
281
|
+
existingTaskId: existing.taskId,
|
|
282
|
+
taskId: input.taskId
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (existing.state === input.nextState && existing.idempotencyKey === input.idempotencyKey) {
|
|
286
|
+
return cloneCheckpoint(existing);
|
|
287
|
+
}
|
|
288
|
+
const stale = input.staleTimeoutMs !== void 0 && input.nowMs - existing.updatedAtMs > input.staleTimeoutMs;
|
|
289
|
+
if (existing.idempotencyKey !== input.idempotencyKey && !stale) {
|
|
290
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Task transition idempotency key mismatch", {
|
|
291
|
+
taskId: input.taskId,
|
|
292
|
+
existingIdempotencyKey: existing.idempotencyKey,
|
|
293
|
+
idempotencyKey: input.idempotencyKey
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
if (!transitionAllowed(existing.state, input.nextState, stale)) {
|
|
297
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Task state transition is not allowed", {
|
|
298
|
+
taskId: input.taskId,
|
|
299
|
+
from: existing.state,
|
|
300
|
+
to: input.nextState
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
const checkpoint = createCheckpoint(input);
|
|
304
|
+
if (checkpoint.output === void 0 && checkpoint.outputs === void 0 && existing.output !== void 0 && stateRank(input.nextState) > stateRank(existing.state)) {
|
|
305
|
+
checkpoint.output = normalizeOutputEntry(existing.output);
|
|
306
|
+
}
|
|
307
|
+
if (checkpoint.output === void 0 && checkpoint.outputs === void 0 && existing.outputs !== void 0 && stateRank(input.nextState) > stateRank(existing.state)) {
|
|
308
|
+
checkpoint.outputs = existing.outputs.map(normalizeOutputEntry);
|
|
309
|
+
}
|
|
310
|
+
return checkpoint;
|
|
311
|
+
}
|
|
312
|
+
function stableStringify(value) {
|
|
313
|
+
return JSON.stringify(toStableJson(value));
|
|
314
|
+
}
|
|
315
|
+
function fingerprint(value) {
|
|
316
|
+
return `fp_${fnv1a64(stableStringify(value)).toString(16).padStart(16, "0")}`;
|
|
317
|
+
}
|
|
318
|
+
function normalizeTaskInput(task) {
|
|
319
|
+
const normalized = {
|
|
320
|
+
path: task.path,
|
|
321
|
+
rowGroupRanges: [...task.rowGroupRanges].map((range) => ({ start: range.start, end: range.end })).sort((a, b) => a.start - b.start || a.end - b.end),
|
|
322
|
+
partitionValues: sortRecord(task.partitionValues)
|
|
323
|
+
};
|
|
324
|
+
if (task.etag !== void 0)
|
|
325
|
+
normalized.etag = task.etag;
|
|
326
|
+
if (task.size !== void 0)
|
|
327
|
+
normalized.size = task.size;
|
|
328
|
+
if (task.rowGroupCount !== void 0)
|
|
329
|
+
normalized.rowGroupCount = task.rowGroupCount;
|
|
330
|
+
if (task.projectedColumns !== void 0)
|
|
331
|
+
normalized.projectedColumns = [...task.projectedColumns].sort();
|
|
332
|
+
if (task.residualPredicate !== void 0)
|
|
333
|
+
normalized.residualPredicate = task.residualPredicate;
|
|
334
|
+
if (task.window !== void 0) {
|
|
335
|
+
normalized.window = task.window.available === true ? {
|
|
336
|
+
topology: task.window.topology,
|
|
337
|
+
available: true,
|
|
338
|
+
bucketCount: task.window.bucketCount,
|
|
339
|
+
partitionBy: task.window.partitionBy
|
|
340
|
+
} : {
|
|
341
|
+
topology: task.window.topology,
|
|
342
|
+
available: false,
|
|
343
|
+
bucketCount: 1,
|
|
344
|
+
reason: task.window.reason
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return normalized;
|
|
348
|
+
}
|
|
349
|
+
function normalizeOutputEntry(entry) {
|
|
350
|
+
const normalized = {
|
|
351
|
+
taskId: entry.taskId,
|
|
352
|
+
outputPath: entry.outputPath,
|
|
353
|
+
partitionValues: sortRecord(entry.partitionValues),
|
|
354
|
+
rowCount: entry.rowCount,
|
|
355
|
+
byteSize: entry.byteSize
|
|
356
|
+
};
|
|
357
|
+
if (entry.contentHash !== void 0)
|
|
358
|
+
normalized.contentHash = entry.contentHash;
|
|
359
|
+
if (entry.etag !== void 0)
|
|
360
|
+
normalized.etag = entry.etag;
|
|
361
|
+
if (entry.iceberg !== void 0) {
|
|
362
|
+
normalized.iceberg = {
|
|
363
|
+
recordCount: entry.iceberg.recordCount,
|
|
364
|
+
fileSizeInBytes: entry.iceberg.fileSizeInBytes,
|
|
365
|
+
partitionValues: sortRecord(entry.iceberg.partitionValues)
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
return normalized;
|
|
369
|
+
}
|
|
370
|
+
function parseOutputManifest(value) {
|
|
371
|
+
if (!isRecord2(value) || value.version !== 1 || typeof value.jobId !== "string" || value.jobId.length === 0 || typeof value.planFingerprint !== "string" || value.planFingerprint.length === 0 || !Array.isArray(value.entries)) {
|
|
372
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Output manifest has invalid required fields");
|
|
373
|
+
}
|
|
374
|
+
return createOutputManifest({
|
|
375
|
+
jobId: value.jobId,
|
|
376
|
+
planFingerprint: value.planFingerprint,
|
|
377
|
+
entries: value.entries.map(parseOutputEntry)
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function parseOutputEntry(value) {
|
|
381
|
+
if (!isRecord2(value) || typeof value.taskId !== "string" || value.taskId.length === 0 || typeof value.outputPath !== "string" || value.outputPath.length === 0 || !isStringRecord(value.partitionValues) || !isNonNegativeInteger(value.rowCount) || !isNonNegativeInteger(value.byteSize)) {
|
|
382
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Output manifest entry is invalid");
|
|
383
|
+
}
|
|
384
|
+
const entry = {
|
|
385
|
+
taskId: value.taskId,
|
|
386
|
+
outputPath: value.outputPath,
|
|
387
|
+
partitionValues: value.partitionValues,
|
|
388
|
+
rowCount: value.rowCount,
|
|
389
|
+
byteSize: value.byteSize
|
|
390
|
+
};
|
|
391
|
+
if (value.contentHash !== void 0) {
|
|
392
|
+
if (typeof value.contentHash !== "string" || value.contentHash.length === 0) {
|
|
393
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Output manifest content hash is invalid");
|
|
394
|
+
}
|
|
395
|
+
entry.contentHash = value.contentHash;
|
|
396
|
+
}
|
|
397
|
+
if (value.etag !== void 0) {
|
|
398
|
+
if (typeof value.etag !== "string" || value.etag.length === 0) {
|
|
399
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Output manifest etag is invalid");
|
|
400
|
+
}
|
|
401
|
+
entry.etag = value.etag;
|
|
402
|
+
}
|
|
403
|
+
if (value.iceberg !== void 0)
|
|
404
|
+
entry.iceberg = parseOutputIcebergMetadata(value.iceberg);
|
|
405
|
+
return entry;
|
|
406
|
+
}
|
|
407
|
+
function parseOutputIcebergMetadata(value) {
|
|
408
|
+
if (!isRecord2(value) || !isNonNegativeInteger(value.recordCount) || !isNonNegativeInteger(value.fileSizeInBytes) || !isStringRecord(value.partitionValues)) {
|
|
409
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Output manifest Iceberg metadata is invalid");
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
recordCount: value.recordCount,
|
|
413
|
+
fileSizeInBytes: value.fileSizeInBytes,
|
|
414
|
+
partitionValues: value.partitionValues
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function snapshotFromTasks(tasks) {
|
|
418
|
+
return fingerprint(tasks.map((task) => ({
|
|
419
|
+
path: task.path,
|
|
420
|
+
etag: task.etag ?? null,
|
|
421
|
+
size: task.size ?? null
|
|
422
|
+
})));
|
|
423
|
+
}
|
|
424
|
+
function taskId(jobId, index, task) {
|
|
425
|
+
return `${jobId}-task-${String(index).padStart(6, "0")}-${fingerprint(task).slice(3, 11)}`;
|
|
426
|
+
}
|
|
427
|
+
function jobIdFromTaskId(taskId2) {
|
|
428
|
+
const marker = "-task-";
|
|
429
|
+
const index = taskId2.indexOf(marker);
|
|
430
|
+
return index === -1 ? "" : taskId2.slice(0, index);
|
|
431
|
+
}
|
|
432
|
+
function createCheckpoint(input) {
|
|
433
|
+
const checkpoint = {
|
|
434
|
+
taskId: input.taskId,
|
|
435
|
+
state: input.nextState,
|
|
436
|
+
idempotencyKey: input.idempotencyKey,
|
|
437
|
+
updatedAtMs: input.nowMs
|
|
438
|
+
};
|
|
439
|
+
if (input.output !== void 0)
|
|
440
|
+
checkpoint.output = normalizeOutputEntry(input.output);
|
|
441
|
+
if (input.outputs !== void 0) {
|
|
442
|
+
checkpoint.outputs = input.outputs.map(normalizeOutputEntry);
|
|
443
|
+
}
|
|
444
|
+
return checkpoint;
|
|
445
|
+
}
|
|
446
|
+
function transitionAllowed(from, to, stale) {
|
|
447
|
+
if (stale && to === "running")
|
|
448
|
+
return true;
|
|
449
|
+
return stateRank(to) === stateRank(from) + 1;
|
|
450
|
+
}
|
|
451
|
+
function stateRank(state) {
|
|
452
|
+
const order = [
|
|
453
|
+
"planned",
|
|
454
|
+
"running",
|
|
455
|
+
"output-written",
|
|
456
|
+
"manifest-recorded",
|
|
457
|
+
"complete"
|
|
458
|
+
];
|
|
459
|
+
return order.indexOf(state);
|
|
460
|
+
}
|
|
461
|
+
function toStableJson(value) {
|
|
462
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
463
|
+
if (typeof value === "number" && !Number.isFinite(value))
|
|
464
|
+
return String(value);
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
if (typeof value === "bigint")
|
|
468
|
+
return value.toString();
|
|
469
|
+
if (value instanceof Date)
|
|
470
|
+
return value.toISOString();
|
|
471
|
+
if (value instanceof Uint8Array)
|
|
472
|
+
return base64UrlEncode(value);
|
|
473
|
+
if (Array.isArray(value))
|
|
474
|
+
return value.map(toStableJson);
|
|
475
|
+
if (typeof value === "object" && value !== null) {
|
|
476
|
+
const out = {};
|
|
477
|
+
for (const [key, inner] of Object.entries(value).sort(([a], [b]) => a.localeCompare(b))) {
|
|
478
|
+
if (inner !== void 0)
|
|
479
|
+
out[key] = toStableJson(inner);
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
return String(value);
|
|
484
|
+
}
|
|
485
|
+
function sortRecord(record) {
|
|
486
|
+
const out = {};
|
|
487
|
+
for (const key of Object.keys(record).sort())
|
|
488
|
+
out[key] = record[key] ?? "";
|
|
489
|
+
return out;
|
|
490
|
+
}
|
|
491
|
+
function sortValueRecord(record, message) {
|
|
492
|
+
const out = {};
|
|
493
|
+
for (const key of Object.keys(record).sort()) {
|
|
494
|
+
assertSafeRecordKey(key, message);
|
|
495
|
+
const value = record[key];
|
|
496
|
+
if (value !== void 0)
|
|
497
|
+
out[key] = value;
|
|
498
|
+
}
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
function assertSafeRecordKey(key, message) {
|
|
502
|
+
if (key.length === 0 || isPrototypeMutationKey(key))
|
|
503
|
+
throwInvalidBookmark(message);
|
|
504
|
+
}
|
|
505
|
+
function isStringRecord(value) {
|
|
506
|
+
return isRecord2(value) && Object.values(value).every((inner) => typeof inner === "string");
|
|
507
|
+
}
|
|
508
|
+
function fnv1a64(input) {
|
|
509
|
+
let hash = 0xcbf29ce484222325n;
|
|
510
|
+
const prime = 0x100000001b3n;
|
|
511
|
+
for (const byte of new TextEncoder().encode(input)) {
|
|
512
|
+
hash ^= BigInt(byte);
|
|
513
|
+
hash = BigInt.asUintN(64, hash * prime);
|
|
514
|
+
}
|
|
515
|
+
return hash;
|
|
516
|
+
}
|
|
517
|
+
async function hmac(payload, secret) {
|
|
518
|
+
const keyBytes = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
|
|
519
|
+
const key = await crypto.subtle.importKey("raw", bytesToArrayBuffer(keyBytes), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
520
|
+
const signature = await crypto.subtle.sign("HMAC", key, bytesToArrayBuffer(new TextEncoder().encode(payload)));
|
|
521
|
+
return base64UrlEncode(new Uint8Array(signature));
|
|
522
|
+
}
|
|
523
|
+
function bytesToArrayBuffer(bytes) {
|
|
524
|
+
const copy = new ArrayBuffer(bytes.byteLength);
|
|
525
|
+
new Uint8Array(copy).set(bytes);
|
|
526
|
+
return copy;
|
|
527
|
+
}
|
|
528
|
+
function base64UrlEncode(bytes) {
|
|
529
|
+
let binary = "";
|
|
530
|
+
for (const byte of bytes)
|
|
531
|
+
binary += String.fromCharCode(byte);
|
|
532
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
533
|
+
}
|
|
534
|
+
function base64UrlDecode(value) {
|
|
535
|
+
const padded = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
536
|
+
const binary = atob(padded);
|
|
537
|
+
const bytes = new Uint8Array(binary.length);
|
|
538
|
+
for (let index = 0; index < binary.length; index += 1)
|
|
539
|
+
bytes[index] = binary.charCodeAt(index);
|
|
540
|
+
return bytes;
|
|
541
|
+
}
|
|
542
|
+
function constantTimeEqual(left, right) {
|
|
543
|
+
const length = Math.max(left.length, right.length);
|
|
544
|
+
let diff = left.length ^ right.length;
|
|
545
|
+
for (let index = 0; index < length; index += 1) {
|
|
546
|
+
diff |= (left.charCodeAt(index) || 0) ^ (right.charCodeAt(index) || 0);
|
|
547
|
+
}
|
|
548
|
+
return diff === 0;
|
|
549
|
+
}
|
|
550
|
+
function parseBookmark(value) {
|
|
551
|
+
if (!isRecord2(value) || value.version !== 1)
|
|
552
|
+
throwInvalidBookmark("Bookmark version is invalid");
|
|
553
|
+
if (typeof value.planFingerprint !== "string" || typeof value.snapshot !== "string") {
|
|
554
|
+
throwInvalidBookmark("Bookmark identity is invalid");
|
|
555
|
+
}
|
|
556
|
+
const position = value.position;
|
|
557
|
+
const fileIndex = isRecord2(position) ? position.fileIndex : void 0;
|
|
558
|
+
const rowGroup = isRecord2(position) ? position.rowGroup : void 0;
|
|
559
|
+
const rowOffset = isRecord2(position) ? position.rowOffset : void 0;
|
|
560
|
+
if (!isRecord2(position) || !isNonNegativeInteger(fileIndex) || !isNonNegativeInteger(rowGroup) || !isNonNegativeInteger(rowOffset)) {
|
|
561
|
+
throwInvalidBookmark("Bookmark position is invalid");
|
|
562
|
+
}
|
|
563
|
+
const bookmark = {
|
|
564
|
+
version: 1,
|
|
565
|
+
planFingerprint: value.planFingerprint,
|
|
566
|
+
snapshot: value.snapshot,
|
|
567
|
+
position: parseBookmarkPosition(position, fileIndex, rowGroup, rowOffset)
|
|
568
|
+
};
|
|
569
|
+
if (value.query !== void 0)
|
|
570
|
+
bookmark.query = parseBookmarkQuery(value.query);
|
|
571
|
+
if (value.writeState !== void 0)
|
|
572
|
+
bookmark.writeState = parseBookmarkWriteState(value.writeState);
|
|
573
|
+
if (value.operatorState !== void 0) {
|
|
574
|
+
bookmark.operatorState = parseBookmarkOperatorState(value.operatorState);
|
|
575
|
+
}
|
|
576
|
+
return bookmark;
|
|
577
|
+
}
|
|
578
|
+
function normalizeBookmarkWriteState(state) {
|
|
579
|
+
const normalized = {};
|
|
580
|
+
if (state.taskState !== void 0)
|
|
581
|
+
normalized.taskState = state.taskState;
|
|
582
|
+
if (state.idempotencyKey !== void 0)
|
|
583
|
+
normalized.idempotencyKey = state.idempotencyKey;
|
|
584
|
+
if (state.multipart !== void 0) {
|
|
585
|
+
normalized.multipart = {
|
|
586
|
+
uploadId: state.multipart.uploadId,
|
|
587
|
+
path: state.multipart.path,
|
|
588
|
+
parts: state.multipart.parts.map((part) => ({
|
|
589
|
+
partNumber: part.partNumber,
|
|
590
|
+
etag: part.etag,
|
|
591
|
+
byteSize: part.byteSize
|
|
592
|
+
})).sort((left, right) => left.partNumber - right.partNumber)
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
return normalized;
|
|
596
|
+
}
|
|
597
|
+
function normalizeBookmarkOperatorState(state) {
|
|
598
|
+
const normalized = {};
|
|
599
|
+
if (state.limitEmitted !== void 0)
|
|
600
|
+
normalized.limitEmitted = state.limitEmitted;
|
|
601
|
+
if (state.groupBy !== void 0)
|
|
602
|
+
normalized.groupBy = cloneInlineOrSpillState(state.groupBy);
|
|
603
|
+
if (state.topK !== void 0)
|
|
604
|
+
normalized.topK = cloneInlineOrSpillState(state.topK);
|
|
605
|
+
if (state.sort !== void 0)
|
|
606
|
+
normalized.sort = cloneInlineOrSpillState(state.sort);
|
|
607
|
+
if (state.sketches !== void 0) {
|
|
608
|
+
normalized.sketches = {};
|
|
609
|
+
for (const [key, value] of Object.entries(state.sketches).sort(([a], [b]) => a.localeCompare(b))) {
|
|
610
|
+
assertSafeRecordKey(key, "Bookmark sketches state is invalid");
|
|
611
|
+
normalized.sketches[key] = cloneBytes(value);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return normalized;
|
|
615
|
+
}
|
|
616
|
+
function cloneInlineOrSpillState(value) {
|
|
617
|
+
return value instanceof Uint8Array ? cloneBytes(value) : { spillRef: value.spillRef };
|
|
618
|
+
}
|
|
619
|
+
function cloneBytes(bytes) {
|
|
620
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
621
|
+
copy.set(bytes);
|
|
622
|
+
return copy;
|
|
623
|
+
}
|
|
624
|
+
function normalizeBookmarkQuery(query) {
|
|
625
|
+
const normalized = { source: query.source };
|
|
626
|
+
if (query.select !== void 0)
|
|
627
|
+
normalized.select = [...query.select];
|
|
628
|
+
if (query.projections !== void 0) {
|
|
629
|
+
normalized.projections = sortValueRecord(query.projections, "Bookmark projections are invalid");
|
|
630
|
+
}
|
|
631
|
+
if (query.where !== void 0)
|
|
632
|
+
normalized.where = query.where;
|
|
633
|
+
if (query.distinct !== void 0)
|
|
634
|
+
normalized.distinct = query.distinct;
|
|
635
|
+
if (query.windows !== void 0) {
|
|
636
|
+
normalized.windows = sortValueRecord(query.windows, "Bookmark windows are invalid");
|
|
637
|
+
for (const expr of Object.values(normalized.windows)) {
|
|
638
|
+
if (!isWindowExprSnapshot(expr))
|
|
639
|
+
throwInvalidBookmark("Bookmark windows are invalid");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (query.qualify !== void 0)
|
|
643
|
+
normalized.qualify = query.qualify;
|
|
644
|
+
if (query.orderBy !== void 0) {
|
|
645
|
+
normalized.orderBy = query.orderBy.map((term) => {
|
|
646
|
+
const normalizedTerm = {
|
|
647
|
+
column: term.column
|
|
648
|
+
};
|
|
649
|
+
if (term.direction !== void 0)
|
|
650
|
+
normalizedTerm.direction = term.direction;
|
|
651
|
+
if (term.nulls !== void 0)
|
|
652
|
+
normalizedTerm.nulls = term.nulls;
|
|
653
|
+
return normalizedTerm;
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
if (query.limit !== void 0)
|
|
657
|
+
normalized.limit = query.limit;
|
|
658
|
+
if (query.offset !== void 0)
|
|
659
|
+
normalized.offset = query.offset;
|
|
660
|
+
if (query.batchSize !== void 0)
|
|
661
|
+
normalized.batchSize = query.batchSize;
|
|
662
|
+
if (query.hive !== void 0)
|
|
663
|
+
normalized.hive = query.hive;
|
|
664
|
+
return normalized;
|
|
665
|
+
}
|
|
666
|
+
function parseBookmarkOperatorState(value) {
|
|
667
|
+
if (!isRecord2(value))
|
|
668
|
+
throwInvalidBookmark("Bookmark operator state is invalid");
|
|
669
|
+
const state = {};
|
|
670
|
+
if (value.limitEmitted !== void 0) {
|
|
671
|
+
state.limitEmitted = parseBookmarkNonNegativeInteger(value.limitEmitted, "limitEmitted");
|
|
672
|
+
}
|
|
673
|
+
if (value.groupBy !== void 0)
|
|
674
|
+
state.groupBy = parseInlineOrSpillState(value.groupBy);
|
|
675
|
+
if (value.topK !== void 0)
|
|
676
|
+
state.topK = parseInlineOrSpillState(value.topK);
|
|
677
|
+
if (value.sort !== void 0)
|
|
678
|
+
state.sort = parseInlineOrSpillState(value.sort);
|
|
679
|
+
if (value.sketches !== void 0) {
|
|
680
|
+
if (!isRecord2(value.sketches))
|
|
681
|
+
throwInvalidBookmark("Bookmark sketches state is invalid");
|
|
682
|
+
state.sketches = {};
|
|
683
|
+
for (const [key, inner] of Object.entries(value.sketches)) {
|
|
684
|
+
assertSafeRecordKey(key, "Bookmark sketches state is invalid");
|
|
685
|
+
state.sketches[key] = parseBase64UrlBytes(inner, "Bookmark sketches state is invalid");
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return state;
|
|
689
|
+
}
|
|
690
|
+
function parseInlineOrSpillState(value) {
|
|
691
|
+
if (typeof value === "string")
|
|
692
|
+
return parseBase64UrlBytes(value, "Bookmark operator state is invalid");
|
|
693
|
+
if (!isRecord2(value) || typeof value.spillRef !== "string" || value.spillRef.length === 0) {
|
|
694
|
+
throwInvalidBookmark("Bookmark operator state is invalid");
|
|
695
|
+
}
|
|
696
|
+
return { spillRef: value.spillRef };
|
|
697
|
+
}
|
|
698
|
+
function parseBookmarkWriteState(value) {
|
|
699
|
+
if (!isRecord2(value))
|
|
700
|
+
throwInvalidBookmark("Bookmark write state is invalid");
|
|
701
|
+
const state = {};
|
|
702
|
+
if (value.taskState !== void 0) {
|
|
703
|
+
if (!isTaskState(value.taskState))
|
|
704
|
+
throwInvalidBookmark("Bookmark write task state is invalid");
|
|
705
|
+
state.taskState = value.taskState;
|
|
706
|
+
}
|
|
707
|
+
if (value.idempotencyKey !== void 0) {
|
|
708
|
+
if (typeof value.idempotencyKey !== "string" || value.idempotencyKey.length === 0) {
|
|
709
|
+
throwInvalidBookmark("Bookmark write idempotency key is invalid");
|
|
710
|
+
}
|
|
711
|
+
state.idempotencyKey = value.idempotencyKey;
|
|
712
|
+
}
|
|
713
|
+
if (value.multipart !== void 0)
|
|
714
|
+
state.multipart = parseMultipartWriteState(value.multipart);
|
|
715
|
+
return normalizeBookmarkWriteState(state);
|
|
716
|
+
}
|
|
717
|
+
function parseMultipartWriteState(value) {
|
|
718
|
+
if (!isRecord2(value))
|
|
719
|
+
throwInvalidBookmark("Bookmark multipart state is invalid");
|
|
720
|
+
if (typeof value.uploadId !== "string" || value.uploadId.length === 0) {
|
|
721
|
+
throwInvalidBookmark("Bookmark multipart upload id is invalid");
|
|
722
|
+
}
|
|
723
|
+
if (typeof value.path !== "string" || value.path.length === 0) {
|
|
724
|
+
throwInvalidBookmark("Bookmark multipart path is invalid");
|
|
725
|
+
}
|
|
726
|
+
if (!Array.isArray(value.parts))
|
|
727
|
+
throwInvalidBookmark("Bookmark multipart parts are invalid");
|
|
728
|
+
return {
|
|
729
|
+
uploadId: value.uploadId,
|
|
730
|
+
path: value.path,
|
|
731
|
+
parts: value.parts.map(parseMultipartPart)
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function parseMultipartPart(value) {
|
|
735
|
+
if (!isRecord2(value))
|
|
736
|
+
throwInvalidBookmark("Bookmark multipart part is invalid");
|
|
737
|
+
if (!isPositiveInteger(value.partNumber)) {
|
|
738
|
+
throwInvalidBookmark("Bookmark multipart part number is invalid");
|
|
739
|
+
}
|
|
740
|
+
if (typeof value.etag !== "string" || value.etag.length === 0) {
|
|
741
|
+
throwInvalidBookmark("Bookmark multipart part etag is invalid");
|
|
742
|
+
}
|
|
743
|
+
if (!isNonNegativeInteger(value.byteSize)) {
|
|
744
|
+
throwInvalidBookmark("Bookmark multipart part byte size is invalid");
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
partNumber: value.partNumber,
|
|
748
|
+
etag: value.etag,
|
|
749
|
+
byteSize: value.byteSize
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function parseBase64UrlBytes(value, message) {
|
|
753
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]*$/u.test(value))
|
|
754
|
+
throwInvalidBookmark(message);
|
|
755
|
+
try {
|
|
756
|
+
return base64UrlDecode(value);
|
|
757
|
+
} catch {
|
|
758
|
+
throwInvalidBookmark(message);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function parseBookmarkQuery(value) {
|
|
762
|
+
if (!isRecord2(value) || typeof value.source !== "string" || value.source.length === 0) {
|
|
763
|
+
throwInvalidBookmark("Bookmark query is invalid");
|
|
764
|
+
}
|
|
765
|
+
const query = { source: value.source };
|
|
766
|
+
if (value.select !== void 0)
|
|
767
|
+
query.select = parseStringArray(value.select, "select");
|
|
768
|
+
if (value.projections !== void 0) {
|
|
769
|
+
if (!isRecord2(value.projections))
|
|
770
|
+
throwInvalidBookmark("Bookmark projections are invalid");
|
|
771
|
+
query.projections = {};
|
|
772
|
+
for (const [key, expr] of Object.entries(value.projections)) {
|
|
773
|
+
assertSafeRecordKey(key, "Bookmark projections are invalid");
|
|
774
|
+
query.projections[key] = parseBookmarkExpr(expr);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (value.where !== void 0)
|
|
778
|
+
query.where = parseBookmarkExpr(value.where);
|
|
779
|
+
if (value.distinct !== void 0) {
|
|
780
|
+
if (typeof value.distinct !== "boolean")
|
|
781
|
+
throwInvalidBookmark("Bookmark distinct is invalid");
|
|
782
|
+
query.distinct = value.distinct;
|
|
783
|
+
}
|
|
784
|
+
if (value.windows !== void 0) {
|
|
785
|
+
if (!isRecord2(value.windows))
|
|
786
|
+
throwInvalidBookmark("Bookmark windows are invalid");
|
|
787
|
+
query.windows = {};
|
|
788
|
+
for (const [key, expr] of Object.entries(value.windows)) {
|
|
789
|
+
assertSafeRecordKey(key, "Bookmark windows are invalid");
|
|
790
|
+
if (!isWindowExprSnapshot(expr))
|
|
791
|
+
throwInvalidBookmark("Bookmark windows are invalid");
|
|
792
|
+
query.windows[key] = expr;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (value.qualify !== void 0)
|
|
796
|
+
query.qualify = parseBookmarkExpr(value.qualify);
|
|
797
|
+
if (value.orderBy !== void 0)
|
|
798
|
+
query.orderBy = parseBookmarkOrderBy(value.orderBy);
|
|
799
|
+
if (value.limit !== void 0)
|
|
800
|
+
query.limit = parseBookmarkNonNegativeInteger(value.limit, "limit");
|
|
801
|
+
if (value.offset !== void 0)
|
|
802
|
+
query.offset = parseBookmarkNonNegativeInteger(value.offset, "offset");
|
|
803
|
+
if (value.batchSize !== void 0) {
|
|
804
|
+
if (!isPositiveInteger(value.batchSize))
|
|
805
|
+
throwInvalidBookmark("Bookmark batch size is invalid");
|
|
806
|
+
query.batchSize = value.batchSize;
|
|
807
|
+
}
|
|
808
|
+
if (value.hive !== void 0) {
|
|
809
|
+
if (typeof value.hive !== "boolean")
|
|
810
|
+
throwInvalidBookmark("Bookmark hive flag is invalid");
|
|
811
|
+
query.hive = value.hive;
|
|
812
|
+
}
|
|
813
|
+
return query;
|
|
814
|
+
}
|
|
815
|
+
function parseStringArray(value, field) {
|
|
816
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
817
|
+
throwInvalidBookmark(`Bookmark ${field} is invalid`);
|
|
818
|
+
}
|
|
819
|
+
return [...value];
|
|
820
|
+
}
|
|
821
|
+
function parseBookmarkOrderBy(value) {
|
|
822
|
+
if (!Array.isArray(value))
|
|
823
|
+
throwInvalidBookmark("Bookmark orderBy is invalid");
|
|
824
|
+
return value.map((term) => {
|
|
825
|
+
if (!isRecord2(term) || typeof term.column !== "string" || term.column.length === 0) {
|
|
826
|
+
throwInvalidBookmark("Bookmark orderBy is invalid");
|
|
827
|
+
}
|
|
828
|
+
const parsed = { column: term.column };
|
|
829
|
+
if (term.direction !== void 0) {
|
|
830
|
+
if (term.direction !== "asc" && term.direction !== "desc") {
|
|
831
|
+
throwInvalidBookmark("Bookmark orderBy direction is invalid");
|
|
832
|
+
}
|
|
833
|
+
parsed.direction = term.direction;
|
|
834
|
+
}
|
|
835
|
+
if (term.nulls !== void 0) {
|
|
836
|
+
if (term.nulls !== "first" && term.nulls !== "last") {
|
|
837
|
+
throwInvalidBookmark("Bookmark orderBy nulls is invalid");
|
|
838
|
+
}
|
|
839
|
+
parsed.nulls = term.nulls;
|
|
840
|
+
}
|
|
841
|
+
return parsed;
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
function parseBookmarkExpr(value) {
|
|
845
|
+
if (!isRecord2(value) || typeof value.kind !== "string") {
|
|
846
|
+
throwInvalidBookmark("Bookmark where expression is invalid");
|
|
847
|
+
}
|
|
848
|
+
return value;
|
|
849
|
+
}
|
|
850
|
+
function parseBookmarkNonNegativeInteger(value, field) {
|
|
851
|
+
if (!isNonNegativeInteger(value))
|
|
852
|
+
throwInvalidBookmark(`Bookmark ${field} is invalid`);
|
|
853
|
+
return value;
|
|
854
|
+
}
|
|
855
|
+
function parseBookmarkPosition(position, fileIndex, rowGroup, rowOffset) {
|
|
856
|
+
const parsed = {
|
|
857
|
+
fileIndex,
|
|
858
|
+
rowGroup,
|
|
859
|
+
rowOffset
|
|
860
|
+
};
|
|
861
|
+
if (position.taskId !== void 0) {
|
|
862
|
+
if (typeof position.taskId !== "string")
|
|
863
|
+
throwInvalidBookmark("Bookmark task id is invalid");
|
|
864
|
+
parsed.taskId = position.taskId;
|
|
865
|
+
}
|
|
866
|
+
if (position.outputManifestCursor !== void 0) {
|
|
867
|
+
if (!isNonNegativeInteger(position.outputManifestCursor)) {
|
|
868
|
+
throwInvalidBookmark("Bookmark output manifest cursor is invalid");
|
|
869
|
+
}
|
|
870
|
+
parsed.outputManifestCursor = position.outputManifestCursor;
|
|
871
|
+
}
|
|
872
|
+
return parsed;
|
|
873
|
+
}
|
|
874
|
+
function cloneCheckpoint(checkpoint) {
|
|
875
|
+
const clone = {
|
|
876
|
+
taskId: checkpoint.taskId,
|
|
877
|
+
state: checkpoint.state,
|
|
878
|
+
idempotencyKey: checkpoint.idempotencyKey,
|
|
879
|
+
updatedAtMs: checkpoint.updatedAtMs
|
|
880
|
+
};
|
|
881
|
+
if (checkpoint.output !== void 0)
|
|
882
|
+
clone.output = normalizeOutputEntry(checkpoint.output);
|
|
883
|
+
if (checkpoint.outputs !== void 0)
|
|
884
|
+
clone.outputs = checkpoint.outputs.map(normalizeOutputEntry);
|
|
885
|
+
return clone;
|
|
886
|
+
}
|
|
887
|
+
function throwInvalidBookmark(message) {
|
|
888
|
+
throw new LakeqlError("LAKEQL_BOOKMARK_INVALID", message);
|
|
889
|
+
}
|
|
890
|
+
function isRecord2(value) {
|
|
891
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
892
|
+
}
|
|
893
|
+
function isNonNegativeInteger(value) {
|
|
894
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
895
|
+
}
|
|
896
|
+
function isPositiveInteger(value) {
|
|
897
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
898
|
+
}
|
|
899
|
+
function isTaskState(value) {
|
|
900
|
+
return value === "planned" || value === "running" || value === "output-written" || value === "manifest-recorded" || value === "complete";
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// ../core/dist/window-query.js
|
|
904
|
+
function windowReadColumns(input) {
|
|
905
|
+
const columns = /* @__PURE__ */ new Set();
|
|
906
|
+
const windowAliases = new Set(Object.keys(input.windows ?? {}));
|
|
907
|
+
for (const column of input.select ?? []) {
|
|
908
|
+
if (column !== "*" && !windowAliases.has(column))
|
|
909
|
+
columns.add(column);
|
|
910
|
+
}
|
|
911
|
+
for (const term of input.orderBy ?? []) {
|
|
912
|
+
if (!windowAliases.has(term.column))
|
|
913
|
+
columns.add(term.column);
|
|
914
|
+
}
|
|
915
|
+
for (const expr of Object.values(input.projections ?? {}))
|
|
916
|
+
collectExprColumns(expr, columns);
|
|
917
|
+
for (const expr of Object.values(input.windows ?? {}))
|
|
918
|
+
collectWindowColumns(expr, columns);
|
|
919
|
+
collectExprColumns(input.where, columns);
|
|
920
|
+
collectExprColumns(input.qualify, columns);
|
|
921
|
+
for (const alias of windowAliases)
|
|
922
|
+
columns.delete(alias);
|
|
923
|
+
return columns.size === 0 ? void 0 : [...columns].sort();
|
|
924
|
+
}
|
|
925
|
+
function windowExpressions(windows) {
|
|
926
|
+
const exprs = [];
|
|
927
|
+
for (const expr of Object.values(windows ?? {})) {
|
|
928
|
+
exprs.push(...expr.over.partitionBy);
|
|
929
|
+
exprs.push(...expr.over.orderBy.map((term) => term.expr));
|
|
930
|
+
exprs.push(...expr.args);
|
|
931
|
+
if (expr.filter !== void 0)
|
|
932
|
+
exprs.push(expr.filter);
|
|
933
|
+
if (expr.over.frame?.start.offset !== void 0)
|
|
934
|
+
exprs.push(expr.over.frame.start.offset);
|
|
935
|
+
if (expr.over.frame?.end.offset !== void 0)
|
|
936
|
+
exprs.push(expr.over.frame.end.offset);
|
|
937
|
+
}
|
|
938
|
+
return exprs;
|
|
939
|
+
}
|
|
940
|
+
function collectWindowColumns(expr, columns) {
|
|
941
|
+
for (const part of expr.over.partitionBy)
|
|
942
|
+
collectExprColumns(part, columns);
|
|
943
|
+
for (const term of expr.over.orderBy)
|
|
944
|
+
collectExprColumns(term.expr, columns);
|
|
945
|
+
for (const arg of expr.args)
|
|
946
|
+
collectExprColumns(arg, columns);
|
|
947
|
+
collectExprColumns(expr.filter, columns);
|
|
948
|
+
if (expr.over.frame !== void 0) {
|
|
949
|
+
collectExprColumns(expr.over.frame.start.offset, columns);
|
|
950
|
+
collectExprColumns(expr.over.frame.end.offset, columns);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function compatibleWindowSortGroups(items) {
|
|
954
|
+
const groups = [];
|
|
955
|
+
const ordered = [...items].sort((left, right) => right.expr.over.orderBy.length - left.expr.over.orderBy.length);
|
|
956
|
+
for (const item of ordered) {
|
|
957
|
+
const group = groups.find((candidate) => windowSortSpecsCompatible(candidate.sortExpr, item.expr));
|
|
958
|
+
if (group === void 0) {
|
|
959
|
+
groups.push({ sortExpr: item.expr, items: [item] });
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
if (group.sortExpr.over.orderBy.length < item.expr.over.orderBy.length) {
|
|
963
|
+
group.sortExpr = item.expr;
|
|
964
|
+
}
|
|
965
|
+
group.items.push(item);
|
|
966
|
+
}
|
|
967
|
+
return groups;
|
|
968
|
+
}
|
|
969
|
+
function windowSortGroupCount(windows) {
|
|
970
|
+
const sortGroups = /* @__PURE__ */ new Set();
|
|
971
|
+
for (const group of compatibleWindowSortGroups(Object.values(windows).map((expr) => ({ expr })))) {
|
|
972
|
+
sortGroups.add(stableStringify({
|
|
973
|
+
partitionBy: group.sortExpr.over.partitionBy,
|
|
974
|
+
orderBy: group.sortExpr.over.orderBy
|
|
975
|
+
}));
|
|
976
|
+
}
|
|
977
|
+
return sortGroups.size;
|
|
978
|
+
}
|
|
979
|
+
function windowTaskPlanForWindows(windows, plannedBucketCount) {
|
|
980
|
+
const partitionSpecs = Object.values(windows).map((expr) => expr.over.partitionBy);
|
|
981
|
+
if (partitionSpecs.length === 0 || partitionSpecs.every((spec) => spec.length === 0)) {
|
|
982
|
+
return {
|
|
983
|
+
topology: "window-partition-fanout",
|
|
984
|
+
available: false,
|
|
985
|
+
bucketCount: 1,
|
|
986
|
+
reason: "window query has one global semantic partition"
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
if (partitionSpecs.some((spec) => spec.length === 0)) {
|
|
990
|
+
return {
|
|
991
|
+
topology: "window-partition-fanout",
|
|
992
|
+
available: false,
|
|
993
|
+
bucketCount: 1,
|
|
994
|
+
reason: "window query mixes global and partitioned windows"
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
const [first, ...rest] = partitionSpecs;
|
|
998
|
+
const firstKey = stableStringify(first ?? []);
|
|
999
|
+
if (rest.some((spec) => stableStringify(spec) !== firstKey)) {
|
|
1000
|
+
return {
|
|
1001
|
+
topology: "window-partition-fanout",
|
|
1002
|
+
available: false,
|
|
1003
|
+
bucketCount: 1,
|
|
1004
|
+
reason: "window partition specs are not identical"
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
return {
|
|
1008
|
+
topology: "window-partition-fanout",
|
|
1009
|
+
available: true,
|
|
1010
|
+
bucketCount: Math.max(1, Math.min(64, plannedBucketCount)),
|
|
1011
|
+
partitionBy: first ?? []
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
function windowSortSpecsCompatible(left, right) {
|
|
1015
|
+
if (stableExprListKey(left.over.partitionBy) !== stableExprListKey(right.over.partitionBy)) {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
return orderTermsPrefixOf(left.over.orderBy, right.over.orderBy) || orderTermsPrefixOf(right.over.orderBy, left.over.orderBy);
|
|
1019
|
+
}
|
|
1020
|
+
function orderTermsPrefixOf(prefix, full) {
|
|
1021
|
+
if (prefix.length > full.length)
|
|
1022
|
+
return false;
|
|
1023
|
+
for (let index = 0; index < prefix.length; index += 1) {
|
|
1024
|
+
if (stableOrderTermKey(prefix[index]) !== stableOrderTermKey(full[index]))
|
|
1025
|
+
return false;
|
|
1026
|
+
}
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
function stableExprListKey(exprs) {
|
|
1030
|
+
return stableStringify(exprs);
|
|
1031
|
+
}
|
|
1032
|
+
function stableOrderTermKey(term) {
|
|
1033
|
+
return stableStringify(term);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// ../core/dist/scalar-order.js
|
|
1037
|
+
function compareOrderedScalars(left, right, term = {}, context = "orderBy") {
|
|
1038
|
+
const direction = term.direction ?? "asc";
|
|
1039
|
+
const nulls = term.nulls ?? (direction === "asc" ? "last" : "first");
|
|
1040
|
+
const leftNull = left === null || left === void 0;
|
|
1041
|
+
const rightNull = right === null || right === void 0;
|
|
1042
|
+
if (leftNull || rightNull) {
|
|
1043
|
+
if (leftNull && rightNull)
|
|
1044
|
+
return 0;
|
|
1045
|
+
const nullOrder = nulls === "first" ? -1 : 1;
|
|
1046
|
+
return leftNull ? nullOrder : -nullOrder;
|
|
1047
|
+
}
|
|
1048
|
+
const comparison = compareNonNullScalars(left, right, context, term.column);
|
|
1049
|
+
return direction === "desc" ? -comparison : comparison;
|
|
1050
|
+
}
|
|
1051
|
+
function compareAggregateScalars(left, right) {
|
|
1052
|
+
if (left === null || right === null) {
|
|
1053
|
+
throw new LakeqlError("LAKEQL_TYPE_ERROR", "Cannot compare null aggregate values");
|
|
1054
|
+
}
|
|
1055
|
+
return compareNonNullScalars(left, right, "aggregate");
|
|
1056
|
+
}
|
|
1057
|
+
function numericAggregateValue(op, value) {
|
|
1058
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1059
|
+
throw new LakeqlError("LAKEQL_TYPE_ERROR", `${op} window aggregate requires numeric input`, {
|
|
1060
|
+
aggregate: op
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
return value;
|
|
1064
|
+
}
|
|
1065
|
+
function isSortableScalar(value) {
|
|
1066
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" || isTimestampValue(value);
|
|
1067
|
+
}
|
|
1068
|
+
function compareNonNullScalars(left, right, context, column) {
|
|
1069
|
+
if (!isSortableScalar(left) || !isSortableScalar(right)) {
|
|
1070
|
+
throw new LakeqlError("LAKEQL_TYPE_ERROR", `${context} values must be scalar`, {
|
|
1071
|
+
...column === void 0 ? {} : { column }
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
if (typeof left === "number" && !Number.isFinite(left) || typeof right === "number" && !Number.isFinite(right)) {
|
|
1075
|
+
throw new LakeqlError("LAKEQL_TYPE_ERROR", `${context} numeric values must be finite`, {
|
|
1076
|
+
...column === void 0 ? {} : { column }
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
if (isTimestampValue(left) || isTimestampValue(right)) {
|
|
1080
|
+
if (!isTimestampValue(left) || !isTimestampValue(right)) {
|
|
1081
|
+
throw new LakeqlError("LAKEQL_TYPE_ERROR", `${context} timestamp values must have matching types`, {
|
|
1082
|
+
...column === void 0 ? {} : { column }
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
return compareTimestampValues(left, right);
|
|
1086
|
+
}
|
|
1087
|
+
if (typeof left !== typeof right) {
|
|
1088
|
+
throw new LakeqlError("LAKEQL_TYPE_ERROR", `${context} values must have matching types`, {
|
|
1089
|
+
...column === void 0 ? {} : { column }
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
if (left < right)
|
|
1093
|
+
return -1;
|
|
1094
|
+
if (left > right)
|
|
1095
|
+
return 1;
|
|
1096
|
+
return 0;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// ../core/dist/window-utils.js
|
|
1100
|
+
function normalizedWindowName(fn) {
|
|
1101
|
+
return typeof fn === "string" ? fn : fn.aggregate;
|
|
1102
|
+
}
|
|
1103
|
+
function isAggregateWindow(fn) {
|
|
1104
|
+
return typeof fn === "object" && fn !== null && "aggregate" in fn;
|
|
1105
|
+
}
|
|
1106
|
+
function requireExactWindowAggregate(op) {
|
|
1107
|
+
if (op === "approx_count_distinct") {
|
|
1108
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "approx_count_distinct is not supported as a window aggregate because window results are exact", { aggregate: op });
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function partitionRows(rows, expr) {
|
|
1112
|
+
return rows.map((row, originalIndex) => ({
|
|
1113
|
+
row,
|
|
1114
|
+
originalIndex,
|
|
1115
|
+
partitionKey: expr.over.partitionBy.map((part) => evaluate(part, row)),
|
|
1116
|
+
orderKey: expr.over.orderBy.map((term) => evaluate(term.expr, row))
|
|
1117
|
+
}));
|
|
1118
|
+
}
|
|
1119
|
+
function sortWindowRows(rows, orderBy) {
|
|
1120
|
+
rows.sort((left, right) => compareWindowRows(left, right, orderBy));
|
|
1121
|
+
}
|
|
1122
|
+
function compareWindowRows(left, right, orderBy) {
|
|
1123
|
+
const partition = compareKeyValues(left.partitionKey, right.partitionKey, defaultOrderTerm());
|
|
1124
|
+
if (partition !== 0)
|
|
1125
|
+
return partition;
|
|
1126
|
+
for (let index = 0; index < orderBy.length; index += 1) {
|
|
1127
|
+
const term = orderBy[index] ?? defaultOrderTerm();
|
|
1128
|
+
const comparison = compareWindowValues(left.orderKey[index], right.orderKey[index], term);
|
|
1129
|
+
if (comparison !== 0)
|
|
1130
|
+
return comparison;
|
|
1131
|
+
}
|
|
1132
|
+
return left.originalIndex - right.originalIndex;
|
|
1133
|
+
}
|
|
1134
|
+
function samePartition(left, right) {
|
|
1135
|
+
return stableKey(left.partitionKey) === stableKey(right.partitionKey);
|
|
1136
|
+
}
|
|
1137
|
+
function samePeer(left, right) {
|
|
1138
|
+
return samePartition(left, right) && stableKey(left.orderKey) === stableKey(right.orderKey);
|
|
1139
|
+
}
|
|
1140
|
+
function partitionSpans(rows) {
|
|
1141
|
+
const spans = [];
|
|
1142
|
+
let start = 0;
|
|
1143
|
+
while (start < rows.length) {
|
|
1144
|
+
let end = start + 1;
|
|
1145
|
+
while (end < rows.length && samePartition(rows[start], rows[end])) {
|
|
1146
|
+
end += 1;
|
|
1147
|
+
}
|
|
1148
|
+
spans.push({ start, end });
|
|
1149
|
+
start = end;
|
|
1150
|
+
}
|
|
1151
|
+
return spans;
|
|
1152
|
+
}
|
|
1153
|
+
function filterRow(expr, row) {
|
|
1154
|
+
return expr.filter === void 0 || matches(expr.filter, row);
|
|
1155
|
+
}
|
|
1156
|
+
function stableKey(values) {
|
|
1157
|
+
return JSON.stringify(values.map(jsonSafeValue));
|
|
1158
|
+
}
|
|
1159
|
+
function compareKeyValues(left, right, term) {
|
|
1160
|
+
const length = Math.max(left.length, right.length);
|
|
1161
|
+
for (let index = 0; index < length; index += 1) {
|
|
1162
|
+
const comparison = compareWindowValues(left[index], right[index], term);
|
|
1163
|
+
if (comparison !== 0)
|
|
1164
|
+
return comparison;
|
|
1165
|
+
}
|
|
1166
|
+
return 0;
|
|
1167
|
+
}
|
|
1168
|
+
function compareWindowValues(left, right, term) {
|
|
1169
|
+
return compareOrderedScalars(left, right, term, "Window ORDER BY");
|
|
1170
|
+
}
|
|
1171
|
+
function defaultOrderTerm() {
|
|
1172
|
+
return { expr: { kind: "literal", value: null }, direction: "asc", nulls: "last" };
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
export { MemoryCheckpointAdapter, advanceTaskCheckpoint, assertBookmarkMatches, collectExprColumns, collectWindowColumns, compareAggregateScalars, compareOrderedScalars, compareWindowRows, compatibleWindowSortGroups, createBookmark, createOutputManifest, createOutputManifestFromCheckpoints, createTaskManifest, filterRow, fingerprint, isAggregateWindow, isPrototypeMutationKey, isSortableScalar, isWindowExprSnapshot, memoryCheckpointAdapter, normalizedWindowName, numericAggregateValue, partitionRows, partitionSpans, readOutputManifest, requireExactWindowAggregate, samePeer, signPaginationToken, sortWindowRows, stableKey, stableStringify, transitionTaskCheckpoint, verifyPaginationToken, windowExpressions, windowReadColumns, windowSortGroupCount, windowSortSpecsCompatible, windowTaskPlanForWindows, writeOutputManifest };
|