@malloy-publisher/server 0.2.4 → 0.2.5
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/app/api-doc.yaml +10 -8
- package/dist/app/assets/{EnvironmentPage-CMzxoXwh.js → EnvironmentPage-BAegPFOF.js} +1 -1
- package/dist/app/assets/{HomePage-DGB13NHr.js → HomePage-DpDWLD0m.js} +1 -1
- package/dist/app/assets/{LightMode-drhYHM27.js → LightMode-CAFl4Cvr.js} +1 -1
- package/dist/app/assets/{MainPage-BfFE47Sp.js → MainPage-DBHZF__d.js} +1 -1
- package/dist/app/assets/{MaterializationsPage-C_iwL_3A.js → MaterializationsPage-DS5Wrhkc.js} +1 -1
- package/dist/app/assets/{ModelPage-nls853_3.js → ModelPage-BE19OgP9.js} +1 -1
- package/dist/app/assets/{PackagePage-D_e_fXEW.js → PackagePage-D5gz7Abx.js} +1 -1
- package/dist/app/assets/{RouteError-Bej8W2nX.js → RouteError-BE1pcxrx.js} +1 -1
- package/dist/app/assets/{ThemeEditorPage-BoxpETpC.js → ThemeEditorPage-CiRxkL1D.js} +1 -1
- package/dist/app/assets/{WorkbookPage-DObenCMx.js → WorkbookPage-Czc9IG0b.js} +1 -1
- package/dist/app/assets/{core-CHmvIICy.es-C_LOF_0M.js → core-xdZbLgaF.es-BGrT15Sy.js} +1 -1
- package/dist/app/assets/{index-5p0haSOb.js → index-73xxtSWr.js} +1 -1
- package/dist/app/assets/{index-BmFFAoZx.js → index-BVkVGR63.js} +1 -1
- package/dist/app/assets/{index-QM4T1eJn.js → index-C22pKyUm.js} +1 -1
- package/dist/app/assets/{index-sZHvyEzw.js → index-DcYLvDJ2.js} +4 -4
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +660 -177
- package/dist/server.mjs +388 -40
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -251802,6 +251802,118 @@ var init_gate_registry_walk = __esm(() => {
|
|
|
251802
251802
|
init_authorize();
|
|
251803
251803
|
});
|
|
251804
251804
|
|
|
251805
|
+
// src/service/partition_annotation.ts
|
|
251806
|
+
import { payloadOf as payloadOf2, routeOf as routeOf2 } from "@malloydata/malloy";
|
|
251807
|
+
function noteRoute2(text) {
|
|
251808
|
+
return routeOf2({ value: text.trimStart() });
|
|
251809
|
+
}
|
|
251810
|
+
function containsPartitionAnnotationTag(texts) {
|
|
251811
|
+
return texts.some((text) => noteRoute2(text) === PARTITION_ROUTE);
|
|
251812
|
+
}
|
|
251813
|
+
function reachesPartitionTagBelow(node, seen = new WeakSet, depth = 0) {
|
|
251814
|
+
if (depth > MAX_PARTITION_IR_WALK_DEPTH) {
|
|
251815
|
+
throw new Error("partition-marker IR walk exceeded max depth");
|
|
251816
|
+
}
|
|
251817
|
+
if (node === null || typeof node !== "object")
|
|
251818
|
+
return false;
|
|
251819
|
+
if (seen.has(node))
|
|
251820
|
+
return false;
|
|
251821
|
+
seen.add(node);
|
|
251822
|
+
if (Array.isArray(node)) {
|
|
251823
|
+
return node.some((item) => reachesPartitionTagBelow(item, seen, depth + 1));
|
|
251824
|
+
}
|
|
251825
|
+
const record = node;
|
|
251826
|
+
if (depth > 0 && record.join !== undefined)
|
|
251827
|
+
return false;
|
|
251828
|
+
if (depth > 0) {
|
|
251829
|
+
for (const key of ["blockNotes", "notes"]) {
|
|
251830
|
+
const arr = record[key];
|
|
251831
|
+
if (!Array.isArray(arr))
|
|
251832
|
+
continue;
|
|
251833
|
+
const texts = arr.map((n) => typeof n === "string" ? n : n && typeof n === "object" && typeof n.text === "string" ? n.text : undefined).filter((text) => text !== undefined);
|
|
251834
|
+
if (containsPartitionAnnotationTag(texts))
|
|
251835
|
+
return true;
|
|
251836
|
+
}
|
|
251837
|
+
}
|
|
251838
|
+
return Object.entries(record).some(([key, value]) => depth === 0 && key === "annotations" ? false : reachesPartitionTagBelow(value, seen, depth + 1));
|
|
251839
|
+
}
|
|
251840
|
+
function notePayload2(text) {
|
|
251841
|
+
return payloadOf2({ value: text.trimStart() }) ?? "";
|
|
251842
|
+
}
|
|
251843
|
+
function rejectionMessage(sourceName, body, detail) {
|
|
251844
|
+
return `Source "${sourceName}" declares \`#(partition) ${body}\`: ${detail} ` + `#(partition) only accepts \`<column> = $GIVEN\`, where <column> is a ` + `single field or a dotted join path.`;
|
|
251845
|
+
}
|
|
251846
|
+
function reject(sourceName, body, cause, detail) {
|
|
251847
|
+
throw new PartitionAnnotationError(cause, rejectionMessage(sourceName, body, detail));
|
|
251848
|
+
}
|
|
251849
|
+
function parsePartitionAnnotation(sourceName, annotationText) {
|
|
251850
|
+
if (noteRoute2(annotationText) !== PARTITION_ROUTE)
|
|
251851
|
+
return null;
|
|
251852
|
+
const body = notePayload2(annotationText).trim();
|
|
251853
|
+
if (body.length === 0) {
|
|
251854
|
+
reject(sourceName, body, "empty_body", "the expression body is empty.");
|
|
251855
|
+
}
|
|
251856
|
+
if (COMPOUND_BOOLEAN_RE.test(body)) {
|
|
251857
|
+
reject(sourceName, body, "compound_boolean", "a compound boolean (`and`/`or`/`not`) is not allowed — declare one " + "`#(partition)` marker per column.");
|
|
251858
|
+
}
|
|
251859
|
+
if (IN_OPERATOR_RE.test(body)) {
|
|
251860
|
+
reject(sourceName, body, "in_operator", "the `in` operator is not allowed.");
|
|
251861
|
+
}
|
|
251862
|
+
if (NEGATED_OPERATOR_RE.test(body)) {
|
|
251863
|
+
reject(sourceName, body, "negated_operator", "`!=` is not allowed.");
|
|
251864
|
+
}
|
|
251865
|
+
if (COMPARISON_OPERATOR_RE.test(body)) {
|
|
251866
|
+
reject(sourceName, body, "comparison_operator", "only `=` is allowed, not `<`/`>`/`<=`/`>=`.");
|
|
251867
|
+
}
|
|
251868
|
+
const eq = body.indexOf("=");
|
|
251869
|
+
if (eq === -1) {
|
|
251870
|
+
reject(sourceName, body, "malformed_body", "no `=` was found.");
|
|
251871
|
+
}
|
|
251872
|
+
const left = body.slice(0, eq).trim();
|
|
251873
|
+
const right = body.slice(eq + 1).trim();
|
|
251874
|
+
if (!FIELD_PATH_RE.test(left)) {
|
|
251875
|
+
reject(sourceName, body, "left_not_field_path", `\`${left}\` is not a field path — the left side must be a single ` + "column or a dotted join path, not an expression.");
|
|
251876
|
+
}
|
|
251877
|
+
const givenMatch = GIVEN_REF_RE.exec(right);
|
|
251878
|
+
if (!givenMatch) {
|
|
251879
|
+
reject(sourceName, body, "missing_given_reference", `\`${right}\` is not a given reference — the right side must be ` + "`$NAME`.");
|
|
251880
|
+
}
|
|
251881
|
+
return { column: left, given: givenMatch[1] };
|
|
251882
|
+
}
|
|
251883
|
+
function collectPartitionPairs(sourceName, annotationTexts2) {
|
|
251884
|
+
const pairs = [];
|
|
251885
|
+
const seenGivens = new Map;
|
|
251886
|
+
for (const text of annotationTexts2) {
|
|
251887
|
+
const pair = parsePartitionAnnotation(sourceName, text);
|
|
251888
|
+
if (pair === null)
|
|
251889
|
+
continue;
|
|
251890
|
+
const priorColumn = seenGivens.get(pair.given);
|
|
251891
|
+
if (priorColumn !== undefined) {
|
|
251892
|
+
throw new PartitionAnnotationError("duplicate_given", `Source "${sourceName}" declares \`#(partition)\` on both ` + `\`${priorColumn}\` and \`${pair.column}\` for the same given ` + `\`$${pair.given}\` — a given can back at most one partition ` + `column per source.`);
|
|
251893
|
+
}
|
|
251894
|
+
seenGivens.set(pair.given, pair.column);
|
|
251895
|
+
pairs.push(pair);
|
|
251896
|
+
}
|
|
251897
|
+
return pairs;
|
|
251898
|
+
}
|
|
251899
|
+
var PARTITION_ROUTE = "partition", MAX_PARTITION_IR_WALK_DEPTH = 200, PartitionAnnotationError, IDENT = "[A-Za-z_][A-Za-z0-9_]*", FIELD_PATH_RE, GIVEN_REF_RE, COMPOUND_BOOLEAN_RE, IN_OPERATOR_RE, NEGATED_OPERATOR_RE, COMPARISON_OPERATOR_RE;
|
|
251900
|
+
var init_partition_annotation = __esm(() => {
|
|
251901
|
+
init_errors();
|
|
251902
|
+
PartitionAnnotationError = class PartitionAnnotationError extends ModelCompilationError {
|
|
251903
|
+
rejectionCause;
|
|
251904
|
+
constructor(rejectionCause, message) {
|
|
251905
|
+
super({ message });
|
|
251906
|
+
this.rejectionCause = rejectionCause;
|
|
251907
|
+
}
|
|
251908
|
+
};
|
|
251909
|
+
FIELD_PATH_RE = new RegExp(`^${IDENT}(?:\\.${IDENT})*$`);
|
|
251910
|
+
GIVEN_REF_RE = new RegExp(`^\\$(${IDENT})$`);
|
|
251911
|
+
COMPOUND_BOOLEAN_RE = /\b(and|or|not)\b/i;
|
|
251912
|
+
IN_OPERATOR_RE = /\bin\b/i;
|
|
251913
|
+
NEGATED_OPERATOR_RE = /!=/;
|
|
251914
|
+
COMPARISON_OPERATOR_RE = /(>=|<=|>|<)/;
|
|
251915
|
+
});
|
|
251916
|
+
|
|
251805
251917
|
// src/service/gate_classification.ts
|
|
251806
251918
|
import {
|
|
251807
251919
|
isSourceDef as isSourceDef4
|
|
@@ -252032,12 +252144,142 @@ function isBareFalseLiteral(expr) {
|
|
|
252032
252144
|
function computeGivenDeclaredTypes(givens) {
|
|
252033
252145
|
return new Map((givens ?? []).filter((g) => g.name != null && g.type != null).map((g) => [g.name, g.type]));
|
|
252034
252146
|
}
|
|
252147
|
+
function resolveEntryPointPartitions(struct, modelDef, seen = new Set) {
|
|
252148
|
+
if (!struct || !modelDef || seen.has(struct))
|
|
252149
|
+
return [];
|
|
252150
|
+
seen.add(struct);
|
|
252151
|
+
const label = struct.as ?? struct.name;
|
|
252152
|
+
const own = collectPartitionPairs(label, ownLevelNotes(struct.annotations).map((note) => note.text));
|
|
252153
|
+
if (own.length > 0)
|
|
252154
|
+
return own;
|
|
252155
|
+
let inherited = struct.annotations?.inherits;
|
|
252156
|
+
for (let depth = 0;inherited && depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
252157
|
+
const pairs = collectPartitionPairs(label, ownLevelNoteTexts(inherited));
|
|
252158
|
+
if (pairs.length > 0)
|
|
252159
|
+
return pairs;
|
|
252160
|
+
inherited = inherited.inherits;
|
|
252161
|
+
}
|
|
252162
|
+
if (inherited)
|
|
252163
|
+
throw unresolvableAncestry(label);
|
|
252164
|
+
const declared = resolveDeclaredSource(struct, modelDef);
|
|
252165
|
+
if (declared.kind === "unresolvable")
|
|
252166
|
+
throw unresolvableAncestry(label);
|
|
252167
|
+
if (declared.kind === "resolved") {
|
|
252168
|
+
const pairs = resolveEntryPointPartitions(declared.source, modelDef, seen);
|
|
252169
|
+
if (pairs.length > 0)
|
|
252170
|
+
return pairs;
|
|
252171
|
+
}
|
|
252172
|
+
const queryBase = resolveQuerySourceBase(struct, modelDef);
|
|
252173
|
+
if (queryBase)
|
|
252174
|
+
return resolveEntryPointPartitions(queryBase, modelDef, seen);
|
|
252175
|
+
return [];
|
|
252176
|
+
}
|
|
252177
|
+
function unresolvableAncestry(label) {
|
|
252178
|
+
return new PartitionAnnotationError("ancestry_unresolvable", `Could not resolve whether source "${label}" carries a ` + `\`#(partition)\` marker: the derivation chain it inherits from ` + `could not be read. Denying rather than serving unfiltered.`);
|
|
252179
|
+
}
|
|
252180
|
+
function assertPartitionAnnotationsValid(modelDef) {
|
|
252181
|
+
if (!modelDef)
|
|
252182
|
+
return;
|
|
252183
|
+
for (const [key, obj] of Object.entries(modelDef.contents)) {
|
|
252184
|
+
if (!isSourceDef4(obj))
|
|
252185
|
+
continue;
|
|
252186
|
+
const label = obj.as ?? obj.name ?? key;
|
|
252187
|
+
if (obj.type !== "composite") {
|
|
252188
|
+
let resolved = [];
|
|
252189
|
+
try {
|
|
252190
|
+
resolved = resolveEntryPointPartitions(obj, modelDef);
|
|
252191
|
+
} catch (err) {
|
|
252192
|
+
if (!(err instanceof PartitionAnnotationError) || err.rejectionCause !== "ancestry_unresolvable") {
|
|
252193
|
+
throw err;
|
|
252194
|
+
}
|
|
252195
|
+
continue;
|
|
252196
|
+
}
|
|
252197
|
+
assertNoUnreachableMarker(obj, label, resolved);
|
|
252198
|
+
continue;
|
|
252199
|
+
}
|
|
252200
|
+
if (resolveEntryPointPartitions(obj, modelDef).length > 0) {
|
|
252201
|
+
throw new PartitionAnnotationError("partitioned_composite", `Source "${label}" is a composite source (\`compose(...)\`) and ` + `also declares \`#(partition)\` (on itself or an ancestor it ` + `derives from). A composite run target compiles each query ` + `against one resolved member branch, which a partition filter ` + `cannot attach to — declare \`#(partition)\` on a non-composite ` + `source instead.`);
|
|
252202
|
+
}
|
|
252203
|
+
const marked = partitionedMemberLabel(obj, modelDef);
|
|
252204
|
+
if (marked !== undefined) {
|
|
252205
|
+
throw new PartitionAnnotationError("partitioned_composite", `Source "${label}" is a composite source (\`compose(...)\`) with ` + `member "${marked}", which declares \`#(partition)\`. MEASURED: ` + `querying the composite reads EVERY partition of that member — ` + `the graft lands on the composite's own struct, not the ` + `resolved member branch, so no filter reaches the executed ` + `query. Remove the composite, or drop \`#(partition)\` from ` + `"${marked}" and expose it as its own source.`);
|
|
252206
|
+
}
|
|
252207
|
+
}
|
|
252208
|
+
}
|
|
252209
|
+
function assertNoUnreachableMarker(struct, label, resolved) {
|
|
252210
|
+
if (resolved.length > 0)
|
|
252211
|
+
return;
|
|
252212
|
+
let reachesBelow;
|
|
252213
|
+
try {
|
|
252214
|
+
reachesBelow = reachesPartitionTagBelow(struct);
|
|
252215
|
+
} catch {
|
|
252216
|
+
reachesBelow = true;
|
|
252217
|
+
}
|
|
252218
|
+
if (!reachesBelow)
|
|
252219
|
+
return;
|
|
252220
|
+
throw new PartitionAnnotationError("marker_unreachable", `Source "${label}" carries a \`#(partition)\` marker that is not ` + `declared on the source itself — it sits on a field, a view, or an ` + `inline \`compose(...)\` inside it. Nothing would filter this ` + `source: every caller would read every partition. Move the marker ` + `onto the \`source:\` declaration.`);
|
|
252221
|
+
}
|
|
252222
|
+
function partitionedMemberLabel(composite, modelDef, seen = new Set) {
|
|
252223
|
+
if (seen.has(composite))
|
|
252224
|
+
return;
|
|
252225
|
+
seen.add(composite);
|
|
252226
|
+
for (const member of composite.sources ?? []) {
|
|
252227
|
+
const label = member.as ?? member.name;
|
|
252228
|
+
if (resolveEntryPointPartitions(member, modelDef).length > 0) {
|
|
252229
|
+
return label;
|
|
252230
|
+
}
|
|
252231
|
+
if (member.type === "composite") {
|
|
252232
|
+
const nested = partitionedMemberLabel(member, modelDef, seen);
|
|
252233
|
+
if (nested !== undefined)
|
|
252234
|
+
return nested;
|
|
252235
|
+
}
|
|
252236
|
+
}
|
|
252237
|
+
return;
|
|
252238
|
+
}
|
|
252239
|
+
async function resolvePartitionGraftEntries(struct, originModelDef, graftScope, deps) {
|
|
252240
|
+
if (!struct || !originModelDef)
|
|
252241
|
+
return [];
|
|
252242
|
+
const pairs = resolveEntryPointPartitions(struct, originModelDef);
|
|
252243
|
+
if (pairs.length === 0)
|
|
252244
|
+
return [];
|
|
252245
|
+
const label = struct.as ?? struct.name;
|
|
252246
|
+
if (!graftScope) {
|
|
252247
|
+
logger.debug("Partition filter has no graft scope to attach to; denying", { modelPath: deps.modelPath, label });
|
|
252248
|
+
throw new Error(`partition on "${label}" has no graft scope`);
|
|
252249
|
+
}
|
|
252250
|
+
const graftTarget = resolveGraftTarget(struct, originModelDef, graftScope.modelDef);
|
|
252251
|
+
if (!graftTarget) {
|
|
252252
|
+
logger.debug("Partition filter resolved to no graft target; denying", {
|
|
252253
|
+
modelPath: deps.modelPath,
|
|
252254
|
+
label
|
|
252255
|
+
});
|
|
252256
|
+
throw new Error(`partition on "${label}" resolved to no graft target`);
|
|
252257
|
+
}
|
|
252258
|
+
const entries = [];
|
|
252259
|
+
for (const pair of pairs) {
|
|
252260
|
+
if (!deps.givenDeclaredTypes.has(pair.given)) {
|
|
252261
|
+
logger.warn("Partition references a given off the model surface; denying", { modelPath: deps.modelPath, graftTarget, givenName: pair.given });
|
|
252262
|
+
throw new Error(`partition on "${label}" references \`$${pair.given}\`, which is not on this model's given surface`);
|
|
252263
|
+
}
|
|
252264
|
+
const filterText = `${pair.column} = $${pair.given}`;
|
|
252265
|
+
const condition = await liftGateCondition(graftTarget, filterText, graftScope.materializer);
|
|
252266
|
+
entries.push({
|
|
252267
|
+
label,
|
|
252268
|
+
graftTarget,
|
|
252269
|
+
filterText,
|
|
252270
|
+
condition,
|
|
252271
|
+
givenNames: [pair.given]
|
|
252272
|
+
});
|
|
252273
|
+
}
|
|
252274
|
+
return entries;
|
|
252275
|
+
}
|
|
252035
252276
|
var init_gate_classification = __esm(() => {
|
|
252036
252277
|
init_logger();
|
|
252037
252278
|
init_annotations();
|
|
252038
252279
|
init_authorize();
|
|
252039
252280
|
init_gate_dimension();
|
|
252040
252281
|
init_gate_registry_walk();
|
|
252282
|
+
init_partition_annotation();
|
|
252041
252283
|
});
|
|
252042
252284
|
|
|
252043
252285
|
// src/service/incremental_declaration.ts
|
|
@@ -252234,8 +252476,24 @@ function assertMaterializationEligible(persistSource) {
|
|
|
252234
252476
|
message: `Source '${sourceName}' cannot be materialized into a storage ` + `destination: it is protected by an #(authorize) gate (its own or a ` + `joined source's). An authorize expression is evaluated per request; ` + `a materialized-once table served frozen carries no gate, so it would ` + `be served to everyone, bypassing authorization. This is refused for ` + `safety. Serve this source live (drop 'storage=').`
|
|
252235
252477
|
});
|
|
252236
252478
|
}
|
|
252479
|
+
if (referencesPartition(persistSource)) {
|
|
252480
|
+
recordEligibilityRefused("partition");
|
|
252481
|
+
throw new MaterializationEligibilityError({
|
|
252482
|
+
reason: "partition",
|
|
252483
|
+
message: `Source '${sourceName}' cannot be materialized into a storage ` + `destination: it declares a #(partition) marker. A partition ` + `filter binds a given at query time (the same mechanism as ` + `row-level access control), so a materialized-once table served ` + `frozen would leak every partition's rows to every caller. This ` + `is refused for safety. Serve this source live (drop 'storage=').`
|
|
252484
|
+
});
|
|
252485
|
+
}
|
|
252237
252486
|
}
|
|
252238
252487
|
function assertColocatedPersistNotAuthorizeGated(persistSource, sourceName = persistSource.name, origin2 = "persist", gateOutcome) {
|
|
252488
|
+
if (referencesPartition(persistSource)) {
|
|
252489
|
+
recordEligibilityRefused("partition");
|
|
252490
|
+
const what2 = origin2 === "preaggregate" ? `Pre-aggregation rollup '${sourceName}'` : `Source '${sourceName}'`;
|
|
252491
|
+
const gated2 = origin2 === "preaggregate" ? `the source '${sourceName}' rolls up declares` : `it declares`;
|
|
252492
|
+
throw new MaterializationEligibilityError({
|
|
252493
|
+
reason: "partition",
|
|
252494
|
+
message: `${what2} cannot be materialized (colocated '#@ persist'): ` + `${gated2} a #(partition) marker. A partition filter binds a ` + `given at query time; this pass cannot prove the persisted ` + `artifact's read path still applies it, so this is refused for ` + `safety. Move the marker to a source that is not materialized, ` + `or stop persisting this one.`
|
|
252495
|
+
});
|
|
252496
|
+
}
|
|
252239
252497
|
if (!referencesAuthorize(persistSource))
|
|
252240
252498
|
return;
|
|
252241
252499
|
if (origin2 === "persist" && gateOutcome?.classification === "row_level" && gateOutcome.attributed) {
|
|
@@ -252311,6 +252569,44 @@ function walkForGiven(node, seen, depth) {
|
|
|
252311
252569
|
}
|
|
252312
252570
|
return false;
|
|
252313
252571
|
}
|
|
252572
|
+
function referencesPartition(persistSource) {
|
|
252573
|
+
try {
|
|
252574
|
+
return walkForPartition(persistSource._sourceDef, new WeakSet, 0);
|
|
252575
|
+
} catch {
|
|
252576
|
+
return true;
|
|
252577
|
+
}
|
|
252578
|
+
}
|
|
252579
|
+
function walkForPartition(node, seen, depth) {
|
|
252580
|
+
if (depth > MAX_GIVEN_WALK_DEPTH) {
|
|
252581
|
+
throw new Error("partition-usage walk exceeded max depth");
|
|
252582
|
+
}
|
|
252583
|
+
if (node === null || typeof node !== "object")
|
|
252584
|
+
return false;
|
|
252585
|
+
if (seen.has(node))
|
|
252586
|
+
return false;
|
|
252587
|
+
seen.add(node);
|
|
252588
|
+
if (Array.isArray(node)) {
|
|
252589
|
+
for (const item of node) {
|
|
252590
|
+
if (walkForPartition(item, seen, depth + 1))
|
|
252591
|
+
return true;
|
|
252592
|
+
}
|
|
252593
|
+
return false;
|
|
252594
|
+
}
|
|
252595
|
+
const record = node;
|
|
252596
|
+
for (const key of ["blockNotes", "notes"]) {
|
|
252597
|
+
const arr = record[key];
|
|
252598
|
+
if (!Array.isArray(arr))
|
|
252599
|
+
continue;
|
|
252600
|
+
const texts = arr.map((n) => typeof n === "string" ? n : n && typeof n === "object" && typeof n.text === "string" ? n.text : undefined).filter((text) => text !== undefined);
|
|
252601
|
+
if (containsPartitionAnnotationTag(texts))
|
|
252602
|
+
return true;
|
|
252603
|
+
}
|
|
252604
|
+
for (const value of Object.values(record)) {
|
|
252605
|
+
if (walkForPartition(value, seen, depth + 1))
|
|
252606
|
+
return true;
|
|
252607
|
+
}
|
|
252608
|
+
return false;
|
|
252609
|
+
}
|
|
252314
252610
|
function referencesAuthorize(persistSource) {
|
|
252315
252611
|
try {
|
|
252316
252612
|
return walkForAuthorize(persistSource._sourceDef, new WeakSet, 0);
|
|
@@ -252411,6 +252707,7 @@ var init_materialization_eligibility = __esm(() => {
|
|
|
252411
252707
|
init_errors();
|
|
252412
252708
|
init_materialization_metrics();
|
|
252413
252709
|
init_authorize();
|
|
252710
|
+
init_partition_annotation();
|
|
252414
252711
|
});
|
|
252415
252712
|
|
|
252416
252713
|
// src/service/preaggregation_compile.ts
|
|
@@ -253865,6 +254162,7 @@ var init_model = __esm(() => {
|
|
|
253865
254162
|
init_query_metadata();
|
|
253866
254163
|
init_preaggregation_validation();
|
|
253867
254164
|
init_gate_registry_walk();
|
|
254165
|
+
init_partition_annotation();
|
|
253868
254166
|
init_gate_classification();
|
|
253869
254167
|
init_source_extraction();
|
|
253870
254168
|
init_authorize_metrics();
|
|
@@ -254060,20 +254358,7 @@ var init_model = __esm(() => {
|
|
|
254060
254358
|
if (!modelDef)
|
|
254061
254359
|
return false;
|
|
254062
254360
|
try {
|
|
254063
|
-
const
|
|
254064
|
-
for (const obj of Object.values(modelDef.contents)) {
|
|
254065
|
-
if (isSourceDef6(obj))
|
|
254066
|
-
structs.push(obj);
|
|
254067
|
-
}
|
|
254068
|
-
for (const value of Object.values(modelDef.sourceRegistry ?? {})) {
|
|
254069
|
-
const entry = value.entry;
|
|
254070
|
-
if (entry.type === "source_registry_reference")
|
|
254071
|
-
continue;
|
|
254072
|
-
if (isSourceDef6(entry))
|
|
254073
|
-
structs.push(entry);
|
|
254074
|
-
}
|
|
254075
|
-
structs.push(...derivedStructsReachable(structs, modelDef));
|
|
254076
|
-
for (const struct of structs) {
|
|
254361
|
+
for (const struct of this.reachableStructsForNoteSweep(modelDef)) {
|
|
254077
254362
|
if (containsAuthorizeAnnotationTag(annotationTexts(struct.annotations) ?? [])) {
|
|
254078
254363
|
return true;
|
|
254079
254364
|
}
|
|
@@ -254090,6 +254375,43 @@ var init_model = __esm(() => {
|
|
|
254090
254375
|
})();
|
|
254091
254376
|
return this.anyAuthorizeNote;
|
|
254092
254377
|
}
|
|
254378
|
+
reachableStructsForNoteSweep(modelDef) {
|
|
254379
|
+
const structs = [];
|
|
254380
|
+
for (const obj of Object.values(modelDef.contents)) {
|
|
254381
|
+
if (isSourceDef6(obj))
|
|
254382
|
+
structs.push(obj);
|
|
254383
|
+
}
|
|
254384
|
+
for (const value of Object.values(modelDef.sourceRegistry ?? {})) {
|
|
254385
|
+
const entry = value.entry;
|
|
254386
|
+
if (entry.type === "source_registry_reference")
|
|
254387
|
+
continue;
|
|
254388
|
+
if (isSourceDef6(entry))
|
|
254389
|
+
structs.push(entry);
|
|
254390
|
+
}
|
|
254391
|
+
structs.push(...derivedStructsReachable(structs, modelDef));
|
|
254392
|
+
return structs;
|
|
254393
|
+
}
|
|
254394
|
+
anyPartitionNote;
|
|
254395
|
+
hasAnyPartitionNote() {
|
|
254396
|
+
if (this.anyPartitionNote !== undefined)
|
|
254397
|
+
return this.anyPartitionNote;
|
|
254398
|
+
this.anyPartitionNote = (() => {
|
|
254399
|
+
const modelDef = this.modelDef;
|
|
254400
|
+
if (!modelDef)
|
|
254401
|
+
return false;
|
|
254402
|
+
try {
|
|
254403
|
+
for (const struct of this.reachableStructsForNoteSweep(modelDef)) {
|
|
254404
|
+
if (containsPartitionAnnotationTag(annotationTexts(struct.annotations) ?? [])) {
|
|
254405
|
+
return true;
|
|
254406
|
+
}
|
|
254407
|
+
}
|
|
254408
|
+
return false;
|
|
254409
|
+
} catch {
|
|
254410
|
+
return true;
|
|
254411
|
+
}
|
|
254412
|
+
})();
|
|
254413
|
+
return this.anyPartitionNote;
|
|
254414
|
+
}
|
|
254093
254415
|
async assertAuthorized(sourceName, _givens, bypassAuthorize = false, graftScope = this.defaultGraftScope()) {
|
|
254094
254416
|
if (bypassAuthorize) {
|
|
254095
254417
|
this.noteAuthorizeBypass("source", sourceName);
|
|
@@ -254146,7 +254468,7 @@ var init_model = __esm(() => {
|
|
|
254146
254468
|
entryPointGates = Array.from(byKey.values());
|
|
254147
254469
|
}
|
|
254148
254470
|
}
|
|
254149
|
-
return { entryPointGates, modelDef };
|
|
254471
|
+
return { entryPointGates, modelDef, struct };
|
|
254150
254472
|
}
|
|
254151
254473
|
async assertAuthorizedFromCompiledRunnable(runnable, givens) {
|
|
254152
254474
|
await this.authorizeAndBindRunnable(runnable, givens, {
|
|
@@ -254158,7 +254480,7 @@ var init_model = __esm(() => {
|
|
|
254158
254480
|
return this.rowLevelFilteredRunnables.has(runnable);
|
|
254159
254481
|
}
|
|
254160
254482
|
async probeEntryPointGates(runnable, givens, graftScope, skipOwnSourceGate = false) {
|
|
254161
|
-
const { entryPointGates, modelDef } = await this.collectAuthorizeEntryPointGates(runnable, givens, graftScope, skipOwnSourceGate);
|
|
254483
|
+
const { entryPointGates, modelDef, struct } = await this.collectAuthorizeEntryPointGates(runnable, givens, graftScope, skipOwnSourceGate);
|
|
254162
254484
|
const rowLevel = [];
|
|
254163
254485
|
for (const entry of entryPointGates) {
|
|
254164
254486
|
const resolution = modelDef ? await this.resolveGateShape(entry, modelDef, graftScope) : { shape: "rejected", cause: undefined };
|
|
@@ -254177,6 +254499,16 @@ var init_model = __esm(() => {
|
|
|
254177
254499
|
recordRowLevelGateRejected(resolution.cause);
|
|
254178
254500
|
throw new AccessDeniedError(`Access denied for source "${entry.label}".`);
|
|
254179
254501
|
}
|
|
254502
|
+
try {
|
|
254503
|
+
rowLevel.push(...await resolvePartitionGraftEntries(struct, modelDef, graftScope, this.gateClassificationDeps()));
|
|
254504
|
+
} catch (err) {
|
|
254505
|
+
recordRowLevelGateDecision("denied_by_gate");
|
|
254506
|
+
logger.debug("Partition filter could not be resolved; denying", {
|
|
254507
|
+
modelPath: this.modelPath,
|
|
254508
|
+
error: err instanceof Error ? err.message : String(err)
|
|
254509
|
+
});
|
|
254510
|
+
throw new AccessDeniedError(`Access denied for source "${struct?.as ?? struct?.name ?? "unknown"}".`);
|
|
254511
|
+
}
|
|
254180
254512
|
return rowLevel;
|
|
254181
254513
|
}
|
|
254182
254514
|
async queryEntryPointHasRowLevelGate(runnable) {
|
|
@@ -254189,7 +254521,7 @@ var init_model = __esm(() => {
|
|
|
254189
254521
|
if (compositeResolvedSourceDef) {
|
|
254190
254522
|
gates.push(...this.collectEntryPointGates(compositeResolvedSourceDef, modelDef, seen, true, undefined, struct ? ownLevelNotes(struct.annotations) : []));
|
|
254191
254523
|
}
|
|
254192
|
-
return gates.length > 0;
|
|
254524
|
+
return gates.length > 0 || resolveEntryPointPartitions(struct, modelDef).length > 0;
|
|
254193
254525
|
} catch {
|
|
254194
254526
|
return true;
|
|
254195
254527
|
}
|
|
@@ -254520,6 +254852,7 @@ var init_model = __esm(() => {
|
|
|
254520
254852
|
filterMap = sourceResult.filterMap;
|
|
254521
254853
|
const queryResult = Model.getQueries(modelDef);
|
|
254522
254854
|
queries = queryResult.queries;
|
|
254855
|
+
assertPartitionAnnotationsValid(modelDef);
|
|
254523
254856
|
assertNoMisplacedAuthorizeAnnotations([
|
|
254524
254857
|
...sourceResult.misplacedAuthorize,
|
|
254525
254858
|
...queryResult.misplacedAuthorize
|
|
@@ -255147,7 +255480,7 @@ run: ${sourceName ? `${quoteMalloyIdentifier2(sourceName)} -> ` : ""}${quoteMall
|
|
|
255147
255480
|
liveRunnable = runnable;
|
|
255148
255481
|
const storageRoutingPossible = getPersistStorageMode() === "on" && this.serveBindings.length > 0 && !!this.serveDestinationConfig;
|
|
255149
255482
|
const preaggServeMaterializer = this.preaggregateServeMaterializer;
|
|
255150
|
-
const routingBlockedByRowLevelGate = (storageRoutingPossible || !!preaggServeMaterializer) && !bypassAuthorize && this.hasAnyAuthorizeNote() && await this.queryEntryPointHasRowLevelGate(runnable);
|
|
255483
|
+
const routingBlockedByRowLevelGate = (storageRoutingPossible || !!preaggServeMaterializer) && !bypassAuthorize && (this.hasAnyAuthorizeNote() || this.hasAnyPartitionNote()) && await this.queryEntryPointHasRowLevelGate(runnable);
|
|
255151
255484
|
if (routingBlockedByRowLevelGate) {
|
|
255152
255485
|
recordStorageServeRouting("blocked_by_row_level_gate");
|
|
255153
255486
|
}
|
|
@@ -256631,10 +256964,10 @@ var require_recursive_readdir = __commonJS((exports, module) => {
|
|
|
256631
256964
|
ignores = [];
|
|
256632
256965
|
}
|
|
256633
256966
|
if (!callback) {
|
|
256634
|
-
return new Promise(function(resolve4,
|
|
256967
|
+
return new Promise(function(resolve4, reject2) {
|
|
256635
256968
|
readdir3(path9, ignores || [], function(err, data) {
|
|
256636
256969
|
if (err) {
|
|
256637
|
-
|
|
256970
|
+
reject2(err);
|
|
256638
256971
|
} else {
|
|
256639
256972
|
resolve4(data);
|
|
256640
256973
|
}
|
|
@@ -288124,19 +288457,34 @@ init_model();
|
|
|
288124
288457
|
init_errors();
|
|
288125
288458
|
var PERSIST_LINE_PATTERN = /^\s*#@\s+persist\b/;
|
|
288126
288459
|
var UNQUOTED_NAME_PATTERN = /(?<![.\w])name\s*=\s*(?!["'])/;
|
|
288460
|
+
var QUOTED_NAME_VALUE_PATTERN = /(?<![.\w])name\s*=\s*(["'])(.*?)\1/g;
|
|
288461
|
+
var SAFE_NAME_PATH = /^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*$/;
|
|
288127
288462
|
function assertPersistNamesQuoted(modelSource, modelPath) {
|
|
288128
|
-
const
|
|
288463
|
+
const unquoted = [];
|
|
288464
|
+
const unsafe = [];
|
|
288129
288465
|
for (const rawLine of modelSource.split(`
|
|
288130
288466
|
`)) {
|
|
288131
288467
|
if (!PERSIST_LINE_PATTERN.test(rawLine))
|
|
288132
288468
|
continue;
|
|
288133
288469
|
if (UNQUOTED_NAME_PATTERN.test(rawLine)) {
|
|
288134
|
-
|
|
288470
|
+
unquoted.push(rawLine.trim());
|
|
288471
|
+
continue;
|
|
288135
288472
|
}
|
|
288473
|
+
for (const match of rawLine.matchAll(QUOTED_NAME_VALUE_PATTERN)) {
|
|
288474
|
+
if (!SAFE_NAME_PATH.test(match[2])) {
|
|
288475
|
+
unsafe.push(rawLine.trim());
|
|
288476
|
+
break;
|
|
288477
|
+
}
|
|
288478
|
+
}
|
|
288479
|
+
}
|
|
288480
|
+
if (unquoted.length > 0) {
|
|
288481
|
+
throw new ModelCompilationError({
|
|
288482
|
+
message: `${modelPath}: persist annotation name must be quoted. Write a quoted ` + `value like name="engaged_events" (or a dialect table path such as ` + `name="my_dataset.engaged_events"), not a bare value -- an unquoted ` + `persist name is dropped from the build plan, so the source would ` + `publish but never materialize. Offending annotation(s): ` + `${unquoted.join("; ")}.`
|
|
288483
|
+
});
|
|
288136
288484
|
}
|
|
288137
|
-
if (
|
|
288485
|
+
if (unsafe.length > 0) {
|
|
288138
288486
|
throw new ModelCompilationError({
|
|
288139
|
-
message: `${modelPath}: persist annotation name must be
|
|
288487
|
+
message: `${modelPath}: persist annotation name must be a plain identifier path -- ` + `dot-separated segments of letters, digits, underscores, and hyphens ` + `(e.g. name="engaged_events", name="my_dataset.engaged_events", or a ` + `hyphenated container path name="my-proj.mydataset.engaged_events"). ` + `The name is inlined into the materialization table DDL, so a value ` + `containing a quote, backtick, semicolon, or space is rejected. ` + `Offending annotation(s): ${unsafe.join("; ")}.`
|
|
288140
288488
|
});
|
|
288141
288489
|
}
|
|
288142
288490
|
}
|
|
@@ -290288,8 +290636,8 @@ ${source}` : source ?? "";
|
|
|
290288
290636
|
}
|
|
290289
290637
|
async fetchManifestEntriesWithTimeout(manifestLocation) {
|
|
290290
290638
|
let timer;
|
|
290291
|
-
const timeout = new Promise((_,
|
|
290292
|
-
timer = setTimeout(() =>
|
|
290639
|
+
const timeout = new Promise((_, reject2) => {
|
|
290640
|
+
timer = setTimeout(() => reject2(new Error(`Timed out after ${MANIFEST_FETCH_TIMEOUT_MS}ms fetching manifest ${manifestLocation}`)), MANIFEST_FETCH_TIMEOUT_MS);
|
|
290293
290641
|
});
|
|
290294
290642
|
try {
|
|
290295
290643
|
return await Promise.race([
|
|
@@ -291851,8 +292199,8 @@ class EnvironmentStore {
|
|
|
291851
292199
|
}
|
|
291852
292200
|
const file = fs9.createWriteStream(zipFilePath);
|
|
291853
292201
|
item.Body.transformToWebStream().pipeTo(Writable.toWeb(file));
|
|
291854
|
-
await new Promise((resolve5,
|
|
291855
|
-
file.on("error",
|
|
292202
|
+
await new Promise((resolve5, reject2) => {
|
|
292203
|
+
file.on("error", reject2);
|
|
291856
292204
|
file.on("finish", resolve5);
|
|
291857
292205
|
});
|
|
291858
292206
|
await this.unzipEnvironment(zipFilePath);
|
|
@@ -291891,8 +292239,8 @@ class EnvironmentStore {
|
|
|
291891
292239
|
}
|
|
291892
292240
|
const file = fs9.createWriteStream(absoluteFilePath);
|
|
291893
292241
|
item.Body.transformToWebStream().pipeTo(Writable.toWeb(file));
|
|
291894
|
-
await new Promise((resolve5,
|
|
291895
|
-
file.on("error",
|
|
292242
|
+
await new Promise((resolve5, reject2) => {
|
|
292243
|
+
file.on("error", reject2);
|
|
291896
292244
|
file.on("finish", resolve5);
|
|
291897
292245
|
});
|
|
291898
292246
|
}));
|
|
@@ -291913,7 +292261,7 @@ class EnvironmentStore {
|
|
|
291913
292261
|
await fs9.promises.mkdir(absoluteDirPath, { recursive: true });
|
|
291914
292262
|
const repoUrl = `https://github.com/${owner}/${repoName}`;
|
|
291915
292263
|
const reporter = new CloneProgressReporter(cloneProgressLabel(`${owner}/${repoName}`, progressContext));
|
|
291916
|
-
await new Promise((resolve5,
|
|
292264
|
+
await new Promise((resolve5, reject2) => {
|
|
291917
292265
|
esm_default2({
|
|
291918
292266
|
progress: (event) => reporter.onProgress(event)
|
|
291919
292267
|
}).clone(repoUrl, absoluteDirPath, GIT_CLONE_OPTIONS, (err) => {
|
|
@@ -291925,7 +292273,7 @@ class EnvironmentStore {
|
|
|
291925
292273
|
}
|
|
291926
292274
|
const errorData = this.extractErrorDataFromError(err);
|
|
291927
292275
|
logger.error(`Failed to clone GitHub repository "${repoUrl}"`, errorData);
|
|
291928
|
-
|
|
292276
|
+
reject2(err);
|
|
291929
292277
|
return;
|
|
291930
292278
|
}
|
|
291931
292279
|
resolve5();
|
|
@@ -292708,10 +293056,10 @@ class Protocol {
|
|
|
292708
293056
|
}
|
|
292709
293057
|
request(request, resultSchema, options) {
|
|
292710
293058
|
const { relatedRequestId, resumptionToken, onresumptiontoken } = options !== null && options !== undefined ? options : {};
|
|
292711
|
-
return new Promise((resolve5,
|
|
293059
|
+
return new Promise((resolve5, reject2) => {
|
|
292712
293060
|
var _a2, _b, _c, _d, _e, _f;
|
|
292713
293061
|
if (!this._transport) {
|
|
292714
|
-
|
|
293062
|
+
reject2(new Error("Not connected"));
|
|
292715
293063
|
return;
|
|
292716
293064
|
}
|
|
292717
293065
|
if (((_a2 = this._options) === null || _a2 === undefined ? undefined : _a2.enforceStrictCapabilities) === true) {
|
|
@@ -292747,7 +293095,7 @@ class Protocol {
|
|
|
292747
293095
|
reason: String(reason)
|
|
292748
293096
|
}
|
|
292749
293097
|
}, { relatedRequestId, resumptionToken, onresumptiontoken }).catch((error) => this._onerror(new Error(`Failed to send cancellation: ${error}`)));
|
|
292750
|
-
|
|
293098
|
+
reject2(reason);
|
|
292751
293099
|
};
|
|
292752
293100
|
this._responseHandlers.set(messageId, (response) => {
|
|
292753
293101
|
var _a3;
|
|
@@ -292755,13 +293103,13 @@ class Protocol {
|
|
|
292755
293103
|
return;
|
|
292756
293104
|
}
|
|
292757
293105
|
if (response instanceof Error) {
|
|
292758
|
-
return
|
|
293106
|
+
return reject2(response);
|
|
292759
293107
|
}
|
|
292760
293108
|
try {
|
|
292761
293109
|
const result = resultSchema.parse(response.result);
|
|
292762
293110
|
resolve5(result);
|
|
292763
293111
|
} catch (error) {
|
|
292764
|
-
|
|
293112
|
+
reject2(error);
|
|
292765
293113
|
}
|
|
292766
293114
|
});
|
|
292767
293115
|
(_d = options === null || options === undefined ? undefined : options.signal) === null || _d === undefined || _d.addEventListener("abort", () => {
|
|
@@ -292773,7 +293121,7 @@ class Protocol {
|
|
|
292773
293121
|
this._setupTimeout(messageId, timeout, options === null || options === undefined ? undefined : options.maxTotalTimeout, timeoutHandler, (_f = options === null || options === undefined ? undefined : options.resetTimeoutOnProgress) !== null && _f !== undefined ? _f : false);
|
|
292774
293122
|
this._transport.send(jsonrpcRequest, { relatedRequestId, resumptionToken, onresumptiontoken }).catch((error) => {
|
|
292775
293123
|
this._cleanupTimeout(messageId);
|
|
292776
|
-
|
|
293124
|
+
reject2(error);
|
|
292777
293125
|
});
|
|
292778
293126
|
});
|
|
292779
293127
|
}
|
|
@@ -306222,10 +306570,10 @@ var promisifyStore = (passedStore) => {
|
|
|
306222
306570
|
|
|
306223
306571
|
class PromisifiedStore {
|
|
306224
306572
|
async increment(key) {
|
|
306225
|
-
return new Promise((resolve6,
|
|
306573
|
+
return new Promise((resolve6, reject2) => {
|
|
306226
306574
|
legacyStore.incr(key, (error, totalHits, resetTime) => {
|
|
306227
306575
|
if (error)
|
|
306228
|
-
|
|
306576
|
+
reject2(error);
|
|
306229
306577
|
resolve6({ totalHits, resetTime });
|
|
306230
306578
|
});
|
|
306231
306579
|
});
|