@malloy-publisher/server 0.2.3 → 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/README.docker.md +1 -1
- package/dist/app/api-doc.yaml +151 -25
- package/dist/app/assets/{EnvironmentPage-fjRmUv5U.js → EnvironmentPage-BAegPFOF.js} +1 -1
- package/dist/app/assets/{HomePage-BYcfSCYQ.js → HomePage-DpDWLD0m.js} +1 -1
- package/dist/app/assets/{LightMode-HqTJd5sS.js → LightMode-CAFl4Cvr.js} +1 -1
- package/dist/app/assets/{MainPage-BWO02VL1.js → MainPage-DBHZF__d.js} +1 -1
- package/dist/app/assets/{MaterializationsPage-DtQTKXio.js → MaterializationsPage-DS5Wrhkc.js} +1 -1
- package/dist/app/assets/{ModelPage-CHHRPUfJ.js → ModelPage-BE19OgP9.js} +1 -1
- package/dist/app/assets/{PackagePage-Dg83Zo7T.js → PackagePage-D5gz7Abx.js} +1 -1
- package/dist/app/assets/{RouteError-BYustUzP.js → RouteError-BE1pcxrx.js} +1 -1
- package/dist/app/assets/{ThemeEditorPage-DvOIp3pJ.js → ThemeEditorPage-CiRxkL1D.js} +1 -1
- package/dist/app/assets/{WorkbookPage-jGyARm9R.js → WorkbookPage-Czc9IG0b.js} +1 -1
- package/dist/app/assets/{core-Dvp73xXv.es-C2zK6mlo.js → core-xdZbLgaF.es-BGrT15Sy.js} +1 -1
- package/dist/app/assets/{index-CLXnplGh.js → index-73xxtSWr.js} +1 -1
- package/dist/app/assets/{index-BOsTtwKH.js → index-BVkVGR63.js} +1 -1
- package/dist/app/assets/{index-DFsqNVYX.js → index-C22pKyUm.js} +1 -1
- package/dist/app/assets/{index-CjFtnxaN.js → index-DcYLvDJ2.js} +4 -4
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +668 -178
- package/dist/server.mjs +19505 -17970
- package/dist/{sshcrypto-vd2k5hq9.node → sshcrypto-xqan60jb.node} +0 -0
- package/package.json +1 -1
|
@@ -2416,7 +2416,7 @@ function httpError(code, message) {
|
|
|
2416
2416
|
}
|
|
2417
2417
|
};
|
|
2418
2418
|
}
|
|
2419
|
-
var NotImplementedError, BadRequestError, InvalidArgumentError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, ConnectionNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, MaterializationEligibilityError, FrozenConfigError, AccessDeniedError, NotQueryableError, MaterializationNotFoundError, MaterializationConflictError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
|
|
2419
|
+
var NotImplementedError, BadRequestError, InvalidArgumentError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, ConnectionNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, MaterializationEligibilityError, PublisherConfigError, FrozenConfigError, AccessDeniedError, NotQueryableError, MaterializationNotFoundError, MaterializationConflictError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
|
|
2420
2420
|
var init_errors = __esm(() => {
|
|
2421
2421
|
init_constants();
|
|
2422
2422
|
NotImplementedError = class NotImplementedError extends Error {
|
|
@@ -2489,6 +2489,13 @@ var init_errors = __esm(() => {
|
|
|
2489
2489
|
this.reason = error.reason;
|
|
2490
2490
|
}
|
|
2491
2491
|
};
|
|
2492
|
+
PublisherConfigError = class PublisherConfigError extends Error {
|
|
2493
|
+
constructor(configName, cause) {
|
|
2494
|
+
super(`Could not read ${configName}: ${cause instanceof Error ? cause.message : String(cause)}. Fix the file, or move it aside to fall back to the bundled default.`);
|
|
2495
|
+
this.name = "PublisherConfigError";
|
|
2496
|
+
this.cause = cause;
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2492
2499
|
FrozenConfigError = class FrozenConfigError extends Error {
|
|
2493
2500
|
constructor(message = `Publisher config can't be updated when ${PUBLISHER_CONFIG_NAME} has { "frozenConfig": true }`) {
|
|
2494
2501
|
super(message);
|
|
@@ -13352,6 +13359,79 @@ var init_data_styles = __esm(() => {
|
|
|
13352
13359
|
init_logger();
|
|
13353
13360
|
});
|
|
13354
13361
|
|
|
13362
|
+
// src/service/annotations.ts
|
|
13363
|
+
import { Annotations } from "@malloydata/malloy";
|
|
13364
|
+
function isReservedRoute(route) {
|
|
13365
|
+
return route === "" || !/[\p{L}\p{N}]/u.test(route);
|
|
13366
|
+
}
|
|
13367
|
+
function ownModelAnnotations(modelDef) {
|
|
13368
|
+
return foldModelAnnotations(modelDef, (id) => id === modelDef.modelID || id.startsWith("internal://"));
|
|
13369
|
+
}
|
|
13370
|
+
function modelAnnotations(modelDef) {
|
|
13371
|
+
return foldModelAnnotations(modelDef, () => true);
|
|
13372
|
+
}
|
|
13373
|
+
function foldModelAnnotations(modelDef, admits) {
|
|
13374
|
+
const registry = modelDef.modelAnnotations ?? {};
|
|
13375
|
+
const visited = new Set;
|
|
13376
|
+
const order = [];
|
|
13377
|
+
const visit = (id) => {
|
|
13378
|
+
if (!admits(id))
|
|
13379
|
+
return;
|
|
13380
|
+
if (visited.has(id))
|
|
13381
|
+
return;
|
|
13382
|
+
visited.add(id);
|
|
13383
|
+
const entry = registry[id];
|
|
13384
|
+
if (!entry)
|
|
13385
|
+
return;
|
|
13386
|
+
for (const dep of entry.inheritsFrom)
|
|
13387
|
+
visit(dep);
|
|
13388
|
+
order.push(id);
|
|
13389
|
+
};
|
|
13390
|
+
visit(modelDef.modelID);
|
|
13391
|
+
let folded;
|
|
13392
|
+
for (const id of order) {
|
|
13393
|
+
const own = registry[id].ownNotes;
|
|
13394
|
+
if (!own.notes?.length && !own.blockNotes?.length)
|
|
13395
|
+
continue;
|
|
13396
|
+
folded = {
|
|
13397
|
+
notes: own.notes,
|
|
13398
|
+
blockNotes: own.blockNotes,
|
|
13399
|
+
inherits: folded
|
|
13400
|
+
};
|
|
13401
|
+
}
|
|
13402
|
+
return folded ?? {};
|
|
13403
|
+
}
|
|
13404
|
+
function ownModelNotes(modelDef) {
|
|
13405
|
+
const registry = modelDef.modelAnnotations ?? {};
|
|
13406
|
+
const isSameDocument = (id) => id === modelDef.modelID || id.startsWith("internal://");
|
|
13407
|
+
const seen = new Set;
|
|
13408
|
+
const texts = [];
|
|
13409
|
+
const visit = (id) => {
|
|
13410
|
+
if (seen.has(id) || !isSameDocument(id))
|
|
13411
|
+
return;
|
|
13412
|
+
seen.add(id);
|
|
13413
|
+
const entry = registry[id];
|
|
13414
|
+
if (!entry)
|
|
13415
|
+
return;
|
|
13416
|
+
for (const dep of entry.inheritsFrom)
|
|
13417
|
+
visit(dep);
|
|
13418
|
+
texts.push(...ownLevelNoteTexts(entry.ownNotes));
|
|
13419
|
+
};
|
|
13420
|
+
visit(modelDef.modelID);
|
|
13421
|
+
return texts;
|
|
13422
|
+
}
|
|
13423
|
+
function annotationTexts(annote) {
|
|
13424
|
+
const texts = new Annotations(annote).texts();
|
|
13425
|
+
return texts.length > 0 ? texts : undefined;
|
|
13426
|
+
}
|
|
13427
|
+
function ownLevelNoteTexts(annote) {
|
|
13428
|
+
return ownLevelNotes(annote).map((note) => note.text);
|
|
13429
|
+
}
|
|
13430
|
+
function ownLevelNotes(annote) {
|
|
13431
|
+
return [...annote?.blockNotes ?? [], ...annote?.notes ?? []];
|
|
13432
|
+
}
|
|
13433
|
+
var init_annotations = () => {};
|
|
13434
|
+
|
|
13355
13435
|
// src/service/gate_dimension.ts
|
|
13356
13436
|
import {
|
|
13357
13437
|
isJoined,
|
|
@@ -13443,78 +13523,590 @@ var init_gate_dimension = __esm(() => {
|
|
|
13443
13523
|
init_errors();
|
|
13444
13524
|
});
|
|
13445
13525
|
|
|
13446
|
-
// src/service/
|
|
13447
|
-
import {
|
|
13448
|
-
function
|
|
13449
|
-
|
|
13526
|
+
// src/service/gate_registry_walk.ts
|
|
13527
|
+
import { isSourceDef as isSourceDef2 } from "@malloydata/malloy";
|
|
13528
|
+
function resolveDeclaredSource(struct, modelDef) {
|
|
13529
|
+
if (!modelDef)
|
|
13530
|
+
return { kind: "none" };
|
|
13531
|
+
let sawBrokenEntry = false;
|
|
13532
|
+
for (const id of [struct.referenceID, struct.sourceID]) {
|
|
13533
|
+
const entry = id ? modelDef.sourceRegistry?.[id]?.entry : undefined;
|
|
13534
|
+
if (!entry)
|
|
13535
|
+
continue;
|
|
13536
|
+
const declared = entry.type === "source_registry_reference" ? modelDef.contents[entry.name] : entry;
|
|
13537
|
+
if (declared === struct)
|
|
13538
|
+
continue;
|
|
13539
|
+
if (!declared || !isSourceDef2(declared)) {
|
|
13540
|
+
sawBrokenEntry = true;
|
|
13541
|
+
continue;
|
|
13542
|
+
}
|
|
13543
|
+
return { kind: "resolved", source: declared };
|
|
13544
|
+
}
|
|
13545
|
+
return sawBrokenEntry ? { kind: "unresolvable" } : { kind: "none" };
|
|
13450
13546
|
}
|
|
13451
|
-
function
|
|
13452
|
-
|
|
13547
|
+
function ancestorGateExprs(struct, modelDef, seen = new Set) {
|
|
13548
|
+
let inherited = struct.annotations?.inherits;
|
|
13549
|
+
for (let depth = 0;inherited && depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
13550
|
+
const exprs2 = collectAuthorizeExprs(ownLevelNoteTexts(inherited));
|
|
13551
|
+
if (exprs2.length > 0)
|
|
13552
|
+
return exprs2;
|
|
13553
|
+
inherited = inherited.inherits;
|
|
13554
|
+
}
|
|
13555
|
+
if (inherited)
|
|
13556
|
+
return ["false"];
|
|
13557
|
+
seen.add(struct);
|
|
13558
|
+
if (seen.size > ANCESTOR_WALK_MAX_DEPTH)
|
|
13559
|
+
return ["false"];
|
|
13560
|
+
const declared = resolveDeclaredSource(struct, modelDef);
|
|
13561
|
+
if (declared.kind === "unresolvable")
|
|
13562
|
+
return ["false"];
|
|
13563
|
+
if (declared.kind === "none" || seen.has(declared.source))
|
|
13564
|
+
return [];
|
|
13565
|
+
const exprs = collectAuthorizeExprs(ownLevelNoteTexts(declared.source.annotations));
|
|
13566
|
+
return exprs.length > 0 ? exprs : ancestorGateExprs(declared.source, modelDef, seen);
|
|
13453
13567
|
}
|
|
13454
|
-
function
|
|
13455
|
-
|
|
13568
|
+
function resolveQuerySourceBase(struct, modelDef) {
|
|
13569
|
+
const duck = struct;
|
|
13570
|
+
if (duck.type !== "query_source")
|
|
13571
|
+
return;
|
|
13572
|
+
const ref = duck.query?.structRef;
|
|
13573
|
+
const base = typeof ref === "string" ? modelDef?.contents[ref] : ref;
|
|
13574
|
+
return base && isSourceDef2(base) ? base : undefined;
|
|
13456
13575
|
}
|
|
13457
|
-
function
|
|
13458
|
-
const
|
|
13459
|
-
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13576
|
+
function resolveCompositeResolvedBase(struct) {
|
|
13577
|
+
const duck = struct;
|
|
13578
|
+
return duck.type === "query_source" ? duck.query?.compositeResolvedSourceDef : undefined;
|
|
13579
|
+
}
|
|
13580
|
+
function effectiveAncestorGateExprs(struct, modelDef, seen = new Set) {
|
|
13581
|
+
const direct = ancestorGateExprs(struct, modelDef, new Set(seen));
|
|
13582
|
+
if (direct.length > 0)
|
|
13583
|
+
return [direct];
|
|
13584
|
+
if (seen.has(struct))
|
|
13585
|
+
return [];
|
|
13586
|
+
seen.add(struct);
|
|
13587
|
+
const groups = [];
|
|
13588
|
+
const base = resolveQuerySourceBase(struct, modelDef);
|
|
13589
|
+
if (!base) {
|
|
13590
|
+
const duck = struct;
|
|
13591
|
+
if (duck.type === "query_source")
|
|
13592
|
+
groups.push(["false"]);
|
|
13593
|
+
} else if (!seen.has(base)) {
|
|
13594
|
+
const ownExprs = collectAuthorizeExprs(ownLevelNoteTexts(base.annotations));
|
|
13595
|
+
groups.push(...ownExprs.length > 0 ? [ownExprs] : effectiveAncestorGateExprs(base, modelDef, seen));
|
|
13596
|
+
}
|
|
13597
|
+
const composite = resolveCompositeResolvedBase(struct);
|
|
13598
|
+
if (composite && !seen.has(composite)) {
|
|
13599
|
+
const parentOwnNotes = base ? ownLevelNotes(base.annotations) : [];
|
|
13600
|
+
const compositeOwnNotes = ownLevelNotes(composite.annotations).filter((note) => !parentOwnNotes.includes(note));
|
|
13601
|
+
const compositeOwn = collectAuthorizeExprs(compositeOwnNotes.map((note) => note.text));
|
|
13602
|
+
groups.push(...compositeOwn.length > 0 ? [compositeOwn] : effectiveAncestorGateExprs(composite, modelDef, seen));
|
|
13603
|
+
}
|
|
13604
|
+
return groups;
|
|
13605
|
+
}
|
|
13606
|
+
function derivedStructsReachable(roots, modelDef) {
|
|
13607
|
+
const seen = new Set(roots);
|
|
13608
|
+
const found = [];
|
|
13609
|
+
const worklist = [...roots];
|
|
13610
|
+
for (let i = 0;i < worklist.length; i++) {
|
|
13611
|
+
const struct = worklist[i];
|
|
13612
|
+
for (const next of [
|
|
13613
|
+
resolveQuerySourceBase(struct, modelDef),
|
|
13614
|
+
resolveCompositeResolvedBase(struct)
|
|
13615
|
+
]) {
|
|
13616
|
+
if (!next || seen.has(next))
|
|
13617
|
+
continue;
|
|
13618
|
+
seen.add(next);
|
|
13619
|
+
found.push(next);
|
|
13620
|
+
worklist.push(next);
|
|
13621
|
+
}
|
|
13622
|
+
}
|
|
13623
|
+
return found;
|
|
13624
|
+
}
|
|
13625
|
+
var ANCESTOR_WALK_MAX_DEPTH = 32;
|
|
13626
|
+
var init_gate_registry_walk = __esm(() => {
|
|
13627
|
+
init_annotations();
|
|
13628
|
+
init_authorize();
|
|
13629
|
+
});
|
|
13630
|
+
|
|
13631
|
+
// src/service/partition_annotation.ts
|
|
13632
|
+
import { payloadOf as payloadOf2, routeOf as routeOf2 } from "@malloydata/malloy";
|
|
13633
|
+
function noteRoute2(text) {
|
|
13634
|
+
return routeOf2({ value: text.trimStart() });
|
|
13635
|
+
}
|
|
13636
|
+
function containsPartitionAnnotationTag(texts) {
|
|
13637
|
+
return texts.some((text) => noteRoute2(text) === PARTITION_ROUTE);
|
|
13638
|
+
}
|
|
13639
|
+
function reachesPartitionTagBelow(node, seen = new WeakSet, depth = 0) {
|
|
13640
|
+
if (depth > MAX_PARTITION_IR_WALK_DEPTH) {
|
|
13641
|
+
throw new Error("partition-marker IR walk exceeded max depth");
|
|
13642
|
+
}
|
|
13643
|
+
if (node === null || typeof node !== "object")
|
|
13644
|
+
return false;
|
|
13645
|
+
if (seen.has(node))
|
|
13646
|
+
return false;
|
|
13647
|
+
seen.add(node);
|
|
13648
|
+
if (Array.isArray(node)) {
|
|
13649
|
+
return node.some((item) => reachesPartitionTagBelow(item, seen, depth + 1));
|
|
13650
|
+
}
|
|
13651
|
+
const record = node;
|
|
13652
|
+
if (depth > 0 && record.join !== undefined)
|
|
13653
|
+
return false;
|
|
13654
|
+
if (depth > 0) {
|
|
13655
|
+
for (const key of ["blockNotes", "notes"]) {
|
|
13656
|
+
const arr = record[key];
|
|
13657
|
+
if (!Array.isArray(arr))
|
|
13658
|
+
continue;
|
|
13659
|
+
const texts = arr.map((n) => typeof n === "string" ? n : n && typeof n === "object" && typeof n.text === "string" ? n.text : undefined).filter((text) => text !== undefined);
|
|
13660
|
+
if (containsPartitionAnnotationTag(texts))
|
|
13661
|
+
return true;
|
|
13662
|
+
}
|
|
13663
|
+
}
|
|
13664
|
+
return Object.entries(record).some(([key, value]) => depth === 0 && key === "annotations" ? false : reachesPartitionTagBelow(value, seen, depth + 1));
|
|
13665
|
+
}
|
|
13666
|
+
function notePayload2(text) {
|
|
13667
|
+
return payloadOf2({ value: text.trimStart() }) ?? "";
|
|
13668
|
+
}
|
|
13669
|
+
function rejectionMessage(sourceName, body, detail) {
|
|
13670
|
+
return `Source "${sourceName}" declares \`#(partition) ${body}\`: ${detail} ` + `#(partition) only accepts \`<column> = $GIVEN\`, where <column> is a ` + `single field or a dotted join path.`;
|
|
13671
|
+
}
|
|
13672
|
+
function reject(sourceName, body, cause, detail) {
|
|
13673
|
+
throw new PartitionAnnotationError(cause, rejectionMessage(sourceName, body, detail));
|
|
13674
|
+
}
|
|
13675
|
+
function parsePartitionAnnotation(sourceName, annotationText) {
|
|
13676
|
+
if (noteRoute2(annotationText) !== PARTITION_ROUTE)
|
|
13677
|
+
return null;
|
|
13678
|
+
const body = notePayload2(annotationText).trim();
|
|
13679
|
+
if (body.length === 0) {
|
|
13680
|
+
reject(sourceName, body, "empty_body", "the expression body is empty.");
|
|
13681
|
+
}
|
|
13682
|
+
if (COMPOUND_BOOLEAN_RE.test(body)) {
|
|
13683
|
+
reject(sourceName, body, "compound_boolean", "a compound boolean (`and`/`or`/`not`) is not allowed — declare one " + "`#(partition)` marker per column.");
|
|
13684
|
+
}
|
|
13685
|
+
if (IN_OPERATOR_RE.test(body)) {
|
|
13686
|
+
reject(sourceName, body, "in_operator", "the `in` operator is not allowed.");
|
|
13687
|
+
}
|
|
13688
|
+
if (NEGATED_OPERATOR_RE.test(body)) {
|
|
13689
|
+
reject(sourceName, body, "negated_operator", "`!=` is not allowed.");
|
|
13690
|
+
}
|
|
13691
|
+
if (COMPARISON_OPERATOR_RE.test(body)) {
|
|
13692
|
+
reject(sourceName, body, "comparison_operator", "only `=` is allowed, not `<`/`>`/`<=`/`>=`.");
|
|
13693
|
+
}
|
|
13694
|
+
const eq = body.indexOf("=");
|
|
13695
|
+
if (eq === -1) {
|
|
13696
|
+
reject(sourceName, body, "malformed_body", "no `=` was found.");
|
|
13697
|
+
}
|
|
13698
|
+
const left = body.slice(0, eq).trim();
|
|
13699
|
+
const right = body.slice(eq + 1).trim();
|
|
13700
|
+
if (!FIELD_PATH_RE.test(left)) {
|
|
13701
|
+
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.");
|
|
13702
|
+
}
|
|
13703
|
+
const givenMatch = GIVEN_REF_RE.exec(right);
|
|
13704
|
+
if (!givenMatch) {
|
|
13705
|
+
reject(sourceName, body, "missing_given_reference", `\`${right}\` is not a given reference — the right side must be ` + "`$NAME`.");
|
|
13706
|
+
}
|
|
13707
|
+
return { column: left, given: givenMatch[1] };
|
|
13708
|
+
}
|
|
13709
|
+
function collectPartitionPairs(sourceName, annotationTexts2) {
|
|
13710
|
+
const pairs = [];
|
|
13711
|
+
const seenGivens = new Map;
|
|
13712
|
+
for (const text of annotationTexts2) {
|
|
13713
|
+
const pair = parsePartitionAnnotation(sourceName, text);
|
|
13714
|
+
if (pair === null)
|
|
13479
13715
|
continue;
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13716
|
+
const priorColumn = seenGivens.get(pair.given);
|
|
13717
|
+
if (priorColumn !== undefined) {
|
|
13718
|
+
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.`);
|
|
13719
|
+
}
|
|
13720
|
+
seenGivens.set(pair.given, pair.column);
|
|
13721
|
+
pairs.push(pair);
|
|
13485
13722
|
}
|
|
13486
|
-
return
|
|
13723
|
+
return pairs;
|
|
13487
13724
|
}
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13725
|
+
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;
|
|
13726
|
+
var init_partition_annotation = __esm(() => {
|
|
13727
|
+
init_errors();
|
|
13728
|
+
PartitionAnnotationError = class PartitionAnnotationError extends ModelCompilationError {
|
|
13729
|
+
rejectionCause;
|
|
13730
|
+
constructor(rejectionCause, message) {
|
|
13731
|
+
super({ message });
|
|
13732
|
+
this.rejectionCause = rejectionCause;
|
|
13733
|
+
}
|
|
13734
|
+
};
|
|
13735
|
+
FIELD_PATH_RE = new RegExp(`^${IDENT}(?:\\.${IDENT})*$`);
|
|
13736
|
+
GIVEN_REF_RE = new RegExp(`^\\$(${IDENT})$`);
|
|
13737
|
+
COMPOUND_BOOLEAN_RE = /\b(and|or|not)\b/i;
|
|
13738
|
+
IN_OPERATOR_RE = /\bin\b/i;
|
|
13739
|
+
NEGATED_OPERATOR_RE = /!=/;
|
|
13740
|
+
COMPARISON_OPERATOR_RE = /(>=|<=|>|<)/;
|
|
13741
|
+
});
|
|
13742
|
+
|
|
13743
|
+
// src/service/gate_classification.ts
|
|
13744
|
+
import {
|
|
13745
|
+
isSourceDef as isSourceDef3
|
|
13746
|
+
} from "@malloydata/malloy";
|
|
13747
|
+
function createGateClassificationDeps(givens, modelPath) {
|
|
13748
|
+
return {
|
|
13749
|
+
gateShapeCache: new Map,
|
|
13750
|
+
givenDeclaredTypes: computeGivenDeclaredTypes(givens),
|
|
13751
|
+
modelPath
|
|
13503
13752
|
};
|
|
13504
|
-
visit(modelDef.modelID);
|
|
13505
|
-
return texts;
|
|
13506
13753
|
}
|
|
13507
|
-
function
|
|
13508
|
-
const
|
|
13509
|
-
|
|
13754
|
+
function gateExprsForOwnAnnotations(struct, modelDef, excludeNotes = []) {
|
|
13755
|
+
const ownNotes = ownLevelNotes(struct.annotations).filter((note) => !excludeNotes.includes(note));
|
|
13756
|
+
try {
|
|
13757
|
+
const own = collectAuthorizeExprs(ownNotes.map((note) => note.text));
|
|
13758
|
+
if (own.length > 0) {
|
|
13759
|
+
return { exprs: own, fromAncestor: false };
|
|
13760
|
+
}
|
|
13761
|
+
const ancestor = ancestorGateExprs(struct, modelDef);
|
|
13762
|
+
return { exprs: ancestor, fromAncestor: ancestor.length > 0 };
|
|
13763
|
+
} catch {
|
|
13764
|
+
return { exprs: ["false"], fromAncestor: false };
|
|
13765
|
+
}
|
|
13510
13766
|
}
|
|
13511
|
-
function
|
|
13512
|
-
|
|
13767
|
+
function collectEntryPointGates(struct, modelDef, seen = new Set, treatAsOwnGate = false, entryPointStruct = struct, excludeNotes = []) {
|
|
13768
|
+
if (!struct || !modelDef || seen.has(struct))
|
|
13769
|
+
return [];
|
|
13770
|
+
seen.add(struct);
|
|
13771
|
+
const results = [];
|
|
13772
|
+
const label = struct.as ?? struct.name;
|
|
13773
|
+
const { exprs: ownExprs, fromAncestor } = gateExprsForOwnAnnotations(struct, modelDef, excludeNotes);
|
|
13774
|
+
if (ownExprs.length > 0) {
|
|
13775
|
+
results.push({
|
|
13776
|
+
label,
|
|
13777
|
+
exprs: ownExprs,
|
|
13778
|
+
selfContained: fromAncestor || !treatAsOwnGate,
|
|
13779
|
+
struct: entryPointStruct
|
|
13780
|
+
});
|
|
13781
|
+
}
|
|
13782
|
+
const duck = struct;
|
|
13783
|
+
if (duck.type === "query_source") {
|
|
13784
|
+
const base = resolveQuerySourceBase(struct, modelDef);
|
|
13785
|
+
if (base) {
|
|
13786
|
+
results.push(...collectEntryPointGates(base, modelDef, seen, false, entryPointStruct));
|
|
13787
|
+
} else {
|
|
13788
|
+
results.push({
|
|
13789
|
+
label,
|
|
13790
|
+
exprs: ["false"],
|
|
13791
|
+
selfContained: true
|
|
13792
|
+
});
|
|
13793
|
+
}
|
|
13794
|
+
const resolved = duck.query?.compositeResolvedSourceDef;
|
|
13795
|
+
if (resolved) {
|
|
13796
|
+
results.push(...collectEntryPointGates(resolved, modelDef, seen, false, entryPointStruct, base ? ownLevelNotes(base.annotations) : []));
|
|
13797
|
+
}
|
|
13798
|
+
}
|
|
13799
|
+
return results;
|
|
13513
13800
|
}
|
|
13514
|
-
function
|
|
13515
|
-
|
|
13801
|
+
async function resolveGateShape(entry, originModelDef, graftScope, deps) {
|
|
13802
|
+
if (!entry.struct)
|
|
13803
|
+
return { shape: "rejected" };
|
|
13804
|
+
if (!graftScope) {
|
|
13805
|
+
logger.debug("Row-level gate has no graft scope to attach to (no scope was available at all, not the gate's own condition); denying", { modelPath: deps.modelPath, label: entry.label });
|
|
13806
|
+
return { shape: "rejected" };
|
|
13807
|
+
}
|
|
13808
|
+
const graftTarget = resolveGraftTarget(entry.struct, originModelDef, graftScope.modelDef);
|
|
13809
|
+
if (!graftTarget) {
|
|
13810
|
+
return { shape: "rejected" };
|
|
13811
|
+
}
|
|
13812
|
+
const filterText = gateFilterText(entry.exprs);
|
|
13813
|
+
const cacheKey = `${graftScope.cacheScope}\x00${graftTarget}\x00${filterText}`;
|
|
13814
|
+
let cached = deps.gateShapeCache.get(cacheKey);
|
|
13815
|
+
if (!cached) {
|
|
13816
|
+
let condition;
|
|
13817
|
+
try {
|
|
13818
|
+
condition = await liftGateCondition(graftTarget, filterText, graftScope.materializer);
|
|
13819
|
+
} catch (err) {
|
|
13820
|
+
logger.debug("Row-level gate condition failed to lift; denying", {
|
|
13821
|
+
modelPath: deps.modelPath,
|
|
13822
|
+
graftTarget,
|
|
13823
|
+
error: err instanceof Error ? err.message : String(err)
|
|
13824
|
+
});
|
|
13825
|
+
return { shape: "rejected" };
|
|
13826
|
+
}
|
|
13827
|
+
let classification;
|
|
13828
|
+
const hasUsableExpr = condition.e !== undefined && condition.e !== null && typeof condition.e === "object" && typeof condition.e.node === "string";
|
|
13829
|
+
if (!hasUsableExpr) {
|
|
13830
|
+
classification = {
|
|
13831
|
+
shape: "rejected",
|
|
13832
|
+
cause: "unclassifiable_condition",
|
|
13833
|
+
detail: "this entry's lifted condition carries no usable expression"
|
|
13834
|
+
};
|
|
13835
|
+
} else if (isBareFalseLiteral(condition.e)) {
|
|
13836
|
+
classification = { shape: "row_level", givenNames: [] };
|
|
13837
|
+
} else {
|
|
13838
|
+
const targetStruct = graftScope.modelDef.contents[graftTarget];
|
|
13839
|
+
if (!isSourceDef3(targetStruct)) {
|
|
13840
|
+
classification = {
|
|
13841
|
+
shape: "rejected",
|
|
13842
|
+
cause: "given_usage_unresolvable",
|
|
13843
|
+
detail: "this gate's graft target does not resolve to a source on this model"
|
|
13844
|
+
};
|
|
13845
|
+
} else {
|
|
13846
|
+
const expansion = expandRefSummaryGivenIds(targetStruct, condition.refSummary);
|
|
13847
|
+
if (!expansion.ok) {
|
|
13848
|
+
classification = {
|
|
13849
|
+
shape: "rejected",
|
|
13850
|
+
cause: "given_usage_unresolvable",
|
|
13851
|
+
detail: `this gate references \`${expansion.unresolvedPath}\`, which could not be resolved on the graft target`
|
|
13852
|
+
};
|
|
13853
|
+
} else {
|
|
13854
|
+
const givenNames = Array.from(expansion.givenIds).map((id) => graftScope.modelDef.givens?.[id]?.name).filter((name) => !!name);
|
|
13855
|
+
const literalNames = referencedGivenNames(filterText);
|
|
13856
|
+
const accountedFor = new Set(givenNames);
|
|
13857
|
+
const unaccounted = literalNames.filter((name) => !accountedFor.has(name));
|
|
13858
|
+
if (givenNames.length !== expansion.givenIds.size || unaccounted.length > 0) {
|
|
13859
|
+
classification = {
|
|
13860
|
+
shape: "rejected",
|
|
13861
|
+
cause: "unreachable_given",
|
|
13862
|
+
detail: "this gate references a given id that does not resolve to a name on this model"
|
|
13863
|
+
};
|
|
13864
|
+
} else {
|
|
13865
|
+
classification = { shape: "row_level", givenNames };
|
|
13866
|
+
}
|
|
13867
|
+
}
|
|
13868
|
+
}
|
|
13869
|
+
}
|
|
13870
|
+
if (classification.shape === "row_level") {
|
|
13871
|
+
const unreachable = classification.givenNames.find((name) => !deps.givenDeclaredTypes.has(name));
|
|
13872
|
+
if (unreachable !== undefined) {
|
|
13873
|
+
logger.warn("Gate accepted a given off the model surface; denying", {
|
|
13874
|
+
modelPath: deps.modelPath,
|
|
13875
|
+
graftTarget,
|
|
13876
|
+
givenName: unreachable
|
|
13877
|
+
});
|
|
13878
|
+
classification = {
|
|
13879
|
+
shape: "rejected",
|
|
13880
|
+
cause: "unreachable_given",
|
|
13881
|
+
detail: `\`$${unreachable}\` is not on this model's given surface`
|
|
13882
|
+
};
|
|
13883
|
+
}
|
|
13884
|
+
}
|
|
13885
|
+
cached = { classification, condition };
|
|
13886
|
+
deps.gateShapeCache.set(cacheKey, cached);
|
|
13887
|
+
}
|
|
13888
|
+
if (cached.classification.shape === "rejected") {
|
|
13889
|
+
return { shape: "rejected", cause: cached.classification.cause };
|
|
13890
|
+
}
|
|
13891
|
+
return {
|
|
13892
|
+
shape: "row_level",
|
|
13893
|
+
graftTarget,
|
|
13894
|
+
filterText,
|
|
13895
|
+
condition: cached.condition,
|
|
13896
|
+
givenNames: cached.classification.givenNames
|
|
13897
|
+
};
|
|
13516
13898
|
}
|
|
13517
|
-
|
|
13899
|
+
function resolveGraftTarget(struct, originModelDef, graftModelDef) {
|
|
13900
|
+
const direct = findContentsKey(struct, graftModelDef);
|
|
13901
|
+
if (direct)
|
|
13902
|
+
return direct;
|
|
13903
|
+
let current = struct;
|
|
13904
|
+
const seen = new Set([struct]);
|
|
13905
|
+
for (let depth = 0;depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
13906
|
+
const declared = resolveDeclaredSource(current, originModelDef);
|
|
13907
|
+
let next;
|
|
13908
|
+
if (declared.kind === "resolved" && !seen.has(declared.source)) {
|
|
13909
|
+
next = declared.source;
|
|
13910
|
+
} else if (declared.kind === "none") {
|
|
13911
|
+
next = findSourceByOwnAnnotationIdentity(current, graftModelDef, seen);
|
|
13912
|
+
}
|
|
13913
|
+
if (!next)
|
|
13914
|
+
return;
|
|
13915
|
+
const key = findContentsKey(next, graftModelDef);
|
|
13916
|
+
if (key)
|
|
13917
|
+
return key;
|
|
13918
|
+
seen.add(next);
|
|
13919
|
+
current = next;
|
|
13920
|
+
}
|
|
13921
|
+
return;
|
|
13922
|
+
}
|
|
13923
|
+
function findContentsKey(struct, modelDef) {
|
|
13924
|
+
for (const [key, value] of Object.entries(modelDef.contents)) {
|
|
13925
|
+
if (value === struct)
|
|
13926
|
+
return key;
|
|
13927
|
+
}
|
|
13928
|
+
if (struct.sourceID) {
|
|
13929
|
+
for (const [key, value] of Object.entries(modelDef.contents)) {
|
|
13930
|
+
if (isSourceDef3(value) && value.sourceID === struct.sourceID) {
|
|
13931
|
+
return key;
|
|
13932
|
+
}
|
|
13933
|
+
}
|
|
13934
|
+
}
|
|
13935
|
+
return;
|
|
13936
|
+
}
|
|
13937
|
+
function findSourceByOwnAnnotationIdentity(struct, modelDef, exclude) {
|
|
13938
|
+
const ownNotes = [
|
|
13939
|
+
...struct.annotations?.blockNotes ?? [],
|
|
13940
|
+
...struct.annotations?.notes ?? []
|
|
13941
|
+
];
|
|
13942
|
+
if (ownNotes.length === 0)
|
|
13943
|
+
return;
|
|
13944
|
+
for (const value of Object.values(modelDef.contents)) {
|
|
13945
|
+
if (!isSourceDef3(value) || value === struct || exclude.has(value)) {
|
|
13946
|
+
continue;
|
|
13947
|
+
}
|
|
13948
|
+
const candidateNotes = [
|
|
13949
|
+
...value.annotations?.blockNotes ?? [],
|
|
13950
|
+
...value.annotations?.notes ?? []
|
|
13951
|
+
];
|
|
13952
|
+
if (candidateNotes.some((note) => ownNotes.includes(note))) {
|
|
13953
|
+
return value;
|
|
13954
|
+
}
|
|
13955
|
+
}
|
|
13956
|
+
return;
|
|
13957
|
+
}
|
|
13958
|
+
async function liftGateCondition(graftTarget, filterText, materializer) {
|
|
13959
|
+
const probe = materializer.loadQuery(buildRowLevelProbe(graftTarget, filterText));
|
|
13960
|
+
const prepared = await probe.getPreparedQuery();
|
|
13961
|
+
return liftProbeFilterCondition(prepared, `lifted probe for "${graftTarget}"`, filterText);
|
|
13962
|
+
}
|
|
13963
|
+
function isBareFalseLiteral(expr) {
|
|
13964
|
+
let node = expr;
|
|
13965
|
+
while (node.node === "()" && node.e && typeof node.e === "object") {
|
|
13966
|
+
node = node.e;
|
|
13967
|
+
}
|
|
13968
|
+
return node.node === "false";
|
|
13969
|
+
}
|
|
13970
|
+
function computeGivenDeclaredTypes(givens) {
|
|
13971
|
+
return new Map((givens ?? []).filter((g) => g.name != null && g.type != null).map((g) => [g.name, g.type]));
|
|
13972
|
+
}
|
|
13973
|
+
function resolveEntryPointPartitions(struct, modelDef, seen = new Set) {
|
|
13974
|
+
if (!struct || !modelDef || seen.has(struct))
|
|
13975
|
+
return [];
|
|
13976
|
+
seen.add(struct);
|
|
13977
|
+
const label = struct.as ?? struct.name;
|
|
13978
|
+
const own = collectPartitionPairs(label, ownLevelNotes(struct.annotations).map((note) => note.text));
|
|
13979
|
+
if (own.length > 0)
|
|
13980
|
+
return own;
|
|
13981
|
+
let inherited = struct.annotations?.inherits;
|
|
13982
|
+
for (let depth = 0;inherited && depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
13983
|
+
const pairs = collectPartitionPairs(label, ownLevelNoteTexts(inherited));
|
|
13984
|
+
if (pairs.length > 0)
|
|
13985
|
+
return pairs;
|
|
13986
|
+
inherited = inherited.inherits;
|
|
13987
|
+
}
|
|
13988
|
+
if (inherited)
|
|
13989
|
+
throw unresolvableAncestry(label);
|
|
13990
|
+
const declared = resolveDeclaredSource(struct, modelDef);
|
|
13991
|
+
if (declared.kind === "unresolvable")
|
|
13992
|
+
throw unresolvableAncestry(label);
|
|
13993
|
+
if (declared.kind === "resolved") {
|
|
13994
|
+
const pairs = resolveEntryPointPartitions(declared.source, modelDef, seen);
|
|
13995
|
+
if (pairs.length > 0)
|
|
13996
|
+
return pairs;
|
|
13997
|
+
}
|
|
13998
|
+
const queryBase = resolveQuerySourceBase(struct, modelDef);
|
|
13999
|
+
if (queryBase)
|
|
14000
|
+
return resolveEntryPointPartitions(queryBase, modelDef, seen);
|
|
14001
|
+
return [];
|
|
14002
|
+
}
|
|
14003
|
+
function unresolvableAncestry(label) {
|
|
14004
|
+
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.`);
|
|
14005
|
+
}
|
|
14006
|
+
function assertPartitionAnnotationsValid(modelDef) {
|
|
14007
|
+
if (!modelDef)
|
|
14008
|
+
return;
|
|
14009
|
+
for (const [key, obj] of Object.entries(modelDef.contents)) {
|
|
14010
|
+
if (!isSourceDef3(obj))
|
|
14011
|
+
continue;
|
|
14012
|
+
const label = obj.as ?? obj.name ?? key;
|
|
14013
|
+
if (obj.type !== "composite") {
|
|
14014
|
+
let resolved = [];
|
|
14015
|
+
try {
|
|
14016
|
+
resolved = resolveEntryPointPartitions(obj, modelDef);
|
|
14017
|
+
} catch (err) {
|
|
14018
|
+
if (!(err instanceof PartitionAnnotationError) || err.rejectionCause !== "ancestry_unresolvable") {
|
|
14019
|
+
throw err;
|
|
14020
|
+
}
|
|
14021
|
+
continue;
|
|
14022
|
+
}
|
|
14023
|
+
assertNoUnreachableMarker(obj, label, resolved);
|
|
14024
|
+
continue;
|
|
14025
|
+
}
|
|
14026
|
+
if (resolveEntryPointPartitions(obj, modelDef).length > 0) {
|
|
14027
|
+
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.`);
|
|
14028
|
+
}
|
|
14029
|
+
const marked = partitionedMemberLabel(obj, modelDef);
|
|
14030
|
+
if (marked !== undefined) {
|
|
14031
|
+
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.`);
|
|
14032
|
+
}
|
|
14033
|
+
}
|
|
14034
|
+
}
|
|
14035
|
+
function assertNoUnreachableMarker(struct, label, resolved) {
|
|
14036
|
+
if (resolved.length > 0)
|
|
14037
|
+
return;
|
|
14038
|
+
let reachesBelow;
|
|
14039
|
+
try {
|
|
14040
|
+
reachesBelow = reachesPartitionTagBelow(struct);
|
|
14041
|
+
} catch {
|
|
14042
|
+
reachesBelow = true;
|
|
14043
|
+
}
|
|
14044
|
+
if (!reachesBelow)
|
|
14045
|
+
return;
|
|
14046
|
+
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.`);
|
|
14047
|
+
}
|
|
14048
|
+
function partitionedMemberLabel(composite, modelDef, seen = new Set) {
|
|
14049
|
+
if (seen.has(composite))
|
|
14050
|
+
return;
|
|
14051
|
+
seen.add(composite);
|
|
14052
|
+
for (const member of composite.sources ?? []) {
|
|
14053
|
+
const label = member.as ?? member.name;
|
|
14054
|
+
if (resolveEntryPointPartitions(member, modelDef).length > 0) {
|
|
14055
|
+
return label;
|
|
14056
|
+
}
|
|
14057
|
+
if (member.type === "composite") {
|
|
14058
|
+
const nested = partitionedMemberLabel(member, modelDef, seen);
|
|
14059
|
+
if (nested !== undefined)
|
|
14060
|
+
return nested;
|
|
14061
|
+
}
|
|
14062
|
+
}
|
|
14063
|
+
return;
|
|
14064
|
+
}
|
|
14065
|
+
async function resolvePartitionGraftEntries(struct, originModelDef, graftScope, deps) {
|
|
14066
|
+
if (!struct || !originModelDef)
|
|
14067
|
+
return [];
|
|
14068
|
+
const pairs = resolveEntryPointPartitions(struct, originModelDef);
|
|
14069
|
+
if (pairs.length === 0)
|
|
14070
|
+
return [];
|
|
14071
|
+
const label = struct.as ?? struct.name;
|
|
14072
|
+
if (!graftScope) {
|
|
14073
|
+
logger.debug("Partition filter has no graft scope to attach to; denying", { modelPath: deps.modelPath, label });
|
|
14074
|
+
throw new Error(`partition on "${label}" has no graft scope`);
|
|
14075
|
+
}
|
|
14076
|
+
const graftTarget = resolveGraftTarget(struct, originModelDef, graftScope.modelDef);
|
|
14077
|
+
if (!graftTarget) {
|
|
14078
|
+
logger.debug("Partition filter resolved to no graft target; denying", {
|
|
14079
|
+
modelPath: deps.modelPath,
|
|
14080
|
+
label
|
|
14081
|
+
});
|
|
14082
|
+
throw new Error(`partition on "${label}" resolved to no graft target`);
|
|
14083
|
+
}
|
|
14084
|
+
const entries = [];
|
|
14085
|
+
for (const pair of pairs) {
|
|
14086
|
+
if (!deps.givenDeclaredTypes.has(pair.given)) {
|
|
14087
|
+
logger.warn("Partition references a given off the model surface; denying", { modelPath: deps.modelPath, graftTarget, givenName: pair.given });
|
|
14088
|
+
throw new Error(`partition on "${label}" references \`$${pair.given}\`, which is not on this model's given surface`);
|
|
14089
|
+
}
|
|
14090
|
+
const filterText = `${pair.column} = $${pair.given}`;
|
|
14091
|
+
const condition = await liftGateCondition(graftTarget, filterText, graftScope.materializer);
|
|
14092
|
+
entries.push({
|
|
14093
|
+
label,
|
|
14094
|
+
graftTarget,
|
|
14095
|
+
filterText,
|
|
14096
|
+
condition,
|
|
14097
|
+
givenNames: [pair.given]
|
|
14098
|
+
});
|
|
14099
|
+
}
|
|
14100
|
+
return entries;
|
|
14101
|
+
}
|
|
14102
|
+
var init_gate_classification = __esm(() => {
|
|
14103
|
+
init_logger();
|
|
14104
|
+
init_annotations();
|
|
14105
|
+
init_authorize();
|
|
14106
|
+
init_gate_dimension();
|
|
14107
|
+
init_gate_registry_walk();
|
|
14108
|
+
init_partition_annotation();
|
|
14109
|
+
});
|
|
13518
14110
|
|
|
13519
14111
|
// src/service/filter.ts
|
|
13520
14112
|
function parseFilterAnnotation(annotation) {
|
|
@@ -13708,115 +14300,10 @@ var init_filter = __esm(() => {
|
|
|
13708
14300
|
};
|
|
13709
14301
|
});
|
|
13710
14302
|
|
|
13711
|
-
// src/service/gate_registry_walk.ts
|
|
13712
|
-
import { isSourceDef as isSourceDef2 } from "@malloydata/malloy";
|
|
13713
|
-
function resolveDeclaredSource(struct, modelDef) {
|
|
13714
|
-
if (!modelDef)
|
|
13715
|
-
return { kind: "none" };
|
|
13716
|
-
let sawBrokenEntry = false;
|
|
13717
|
-
for (const id of [struct.referenceID, struct.sourceID]) {
|
|
13718
|
-
const entry = id ? modelDef.sourceRegistry?.[id]?.entry : undefined;
|
|
13719
|
-
if (!entry)
|
|
13720
|
-
continue;
|
|
13721
|
-
const declared = entry.type === "source_registry_reference" ? modelDef.contents[entry.name] : entry;
|
|
13722
|
-
if (declared === struct)
|
|
13723
|
-
continue;
|
|
13724
|
-
if (!declared || !isSourceDef2(declared)) {
|
|
13725
|
-
sawBrokenEntry = true;
|
|
13726
|
-
continue;
|
|
13727
|
-
}
|
|
13728
|
-
return { kind: "resolved", source: declared };
|
|
13729
|
-
}
|
|
13730
|
-
return sawBrokenEntry ? { kind: "unresolvable" } : { kind: "none" };
|
|
13731
|
-
}
|
|
13732
|
-
function ancestorGateExprs(struct, modelDef, seen = new Set) {
|
|
13733
|
-
let inherited = struct.annotations?.inherits;
|
|
13734
|
-
for (let depth = 0;inherited && depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
13735
|
-
const exprs2 = collectAuthorizeExprs(ownLevelNoteTexts(inherited));
|
|
13736
|
-
if (exprs2.length > 0)
|
|
13737
|
-
return exprs2;
|
|
13738
|
-
inherited = inherited.inherits;
|
|
13739
|
-
}
|
|
13740
|
-
if (inherited)
|
|
13741
|
-
return ["false"];
|
|
13742
|
-
seen.add(struct);
|
|
13743
|
-
if (seen.size > ANCESTOR_WALK_MAX_DEPTH)
|
|
13744
|
-
return ["false"];
|
|
13745
|
-
const declared = resolveDeclaredSource(struct, modelDef);
|
|
13746
|
-
if (declared.kind === "unresolvable")
|
|
13747
|
-
return ["false"];
|
|
13748
|
-
if (declared.kind === "none" || seen.has(declared.source))
|
|
13749
|
-
return [];
|
|
13750
|
-
const exprs = collectAuthorizeExprs(ownLevelNoteTexts(declared.source.annotations));
|
|
13751
|
-
return exprs.length > 0 ? exprs : ancestorGateExprs(declared.source, modelDef, seen);
|
|
13752
|
-
}
|
|
13753
|
-
function resolveQuerySourceBase(struct, modelDef) {
|
|
13754
|
-
const duck = struct;
|
|
13755
|
-
if (duck.type !== "query_source")
|
|
13756
|
-
return;
|
|
13757
|
-
const ref = duck.query?.structRef;
|
|
13758
|
-
const base = typeof ref === "string" ? modelDef?.contents[ref] : ref;
|
|
13759
|
-
return base && isSourceDef2(base) ? base : undefined;
|
|
13760
|
-
}
|
|
13761
|
-
function resolveCompositeResolvedBase(struct) {
|
|
13762
|
-
const duck = struct;
|
|
13763
|
-
return duck.type === "query_source" ? duck.query?.compositeResolvedSourceDef : undefined;
|
|
13764
|
-
}
|
|
13765
|
-
function effectiveAncestorGateExprs(struct, modelDef, seen = new Set) {
|
|
13766
|
-
const direct = ancestorGateExprs(struct, modelDef, new Set(seen));
|
|
13767
|
-
if (direct.length > 0)
|
|
13768
|
-
return [direct];
|
|
13769
|
-
if (seen.has(struct))
|
|
13770
|
-
return [];
|
|
13771
|
-
seen.add(struct);
|
|
13772
|
-
const groups = [];
|
|
13773
|
-
const base = resolveQuerySourceBase(struct, modelDef);
|
|
13774
|
-
if (!base) {
|
|
13775
|
-
const duck = struct;
|
|
13776
|
-
if (duck.type === "query_source")
|
|
13777
|
-
groups.push(["false"]);
|
|
13778
|
-
} else if (!seen.has(base)) {
|
|
13779
|
-
const ownExprs = collectAuthorizeExprs(ownLevelNoteTexts(base.annotations));
|
|
13780
|
-
groups.push(...ownExprs.length > 0 ? [ownExprs] : effectiveAncestorGateExprs(base, modelDef, seen));
|
|
13781
|
-
}
|
|
13782
|
-
const composite = resolveCompositeResolvedBase(struct);
|
|
13783
|
-
if (composite && !seen.has(composite)) {
|
|
13784
|
-
const parentOwnNotes = base ? ownLevelNotes(base.annotations) : [];
|
|
13785
|
-
const compositeOwnNotes = ownLevelNotes(composite.annotations).filter((note) => !parentOwnNotes.includes(note));
|
|
13786
|
-
const compositeOwn = collectAuthorizeExprs(compositeOwnNotes.map((note) => note.text));
|
|
13787
|
-
groups.push(...compositeOwn.length > 0 ? [compositeOwn] : effectiveAncestorGateExprs(composite, modelDef, seen));
|
|
13788
|
-
}
|
|
13789
|
-
return groups;
|
|
13790
|
-
}
|
|
13791
|
-
function derivedStructsReachable(roots, modelDef) {
|
|
13792
|
-
const seen = new Set(roots);
|
|
13793
|
-
const found = [];
|
|
13794
|
-
const worklist = [...roots];
|
|
13795
|
-
for (let i = 0;i < worklist.length; i++) {
|
|
13796
|
-
const struct = worklist[i];
|
|
13797
|
-
for (const next of [
|
|
13798
|
-
resolveQuerySourceBase(struct, modelDef),
|
|
13799
|
-
resolveCompositeResolvedBase(struct)
|
|
13800
|
-
]) {
|
|
13801
|
-
if (!next || seen.has(next))
|
|
13802
|
-
continue;
|
|
13803
|
-
seen.add(next);
|
|
13804
|
-
found.push(next);
|
|
13805
|
-
worklist.push(next);
|
|
13806
|
-
}
|
|
13807
|
-
}
|
|
13808
|
-
return found;
|
|
13809
|
-
}
|
|
13810
|
-
var ANCESTOR_WALK_MAX_DEPTH = 32;
|
|
13811
|
-
var init_gate_registry_walk = __esm(() => {
|
|
13812
|
-
init_annotations();
|
|
13813
|
-
init_authorize();
|
|
13814
|
-
});
|
|
13815
|
-
|
|
13816
14303
|
// src/service/source_extraction.ts
|
|
13817
14304
|
import {
|
|
13818
14305
|
isJoined as isJoined2,
|
|
13819
|
-
isSourceDef as
|
|
14306
|
+
isSourceDef as isSourceDef4
|
|
13820
14307
|
} from "@malloydata/malloy";
|
|
13821
14308
|
function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
|
|
13822
14309
|
const ids = [field.referenceID, field.sourceID].filter((id) => !!id);
|
|
@@ -13827,7 +14314,7 @@ function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
|
|
|
13827
14314
|
if (!entry)
|
|
13828
14315
|
continue;
|
|
13829
14316
|
const declared = entry.type === "source_registry_reference" ? modelDef.contents[entry.name] : entry;
|
|
13830
|
-
if (declared &&
|
|
14317
|
+
if (declared && isSourceDef4(declared))
|
|
13831
14318
|
return false;
|
|
13832
14319
|
}
|
|
13833
14320
|
return true;
|
|
@@ -13853,7 +14340,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
13853
14340
|
const nearMissAuthorize = [];
|
|
13854
14341
|
const sweptStructs = [];
|
|
13855
14342
|
for (const obj of Object.values(modelDef.contents)) {
|
|
13856
|
-
if (!
|
|
14343
|
+
if (!isSourceDef4(obj))
|
|
13857
14344
|
continue;
|
|
13858
14345
|
const struct = obj;
|
|
13859
14346
|
sweptStructs.push(obj);
|
|
@@ -13872,7 +14359,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
13872
14359
|
const entry = value.entry;
|
|
13873
14360
|
if (entry.type === "source_registry_reference")
|
|
13874
14361
|
continue;
|
|
13875
|
-
if (!
|
|
14362
|
+
if (!isSourceDef4(entry))
|
|
13876
14363
|
continue;
|
|
13877
14364
|
sweptStructs.push(entry);
|
|
13878
14365
|
for (const note of ownLevelNotes(entry.annotations)) {
|
|
@@ -13899,7 +14386,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
13899
14386
|
if (containsAuthorizeAnnotationTag((modelAnnotations(modelDef).notes ?? []).map((note) => note.text))) {
|
|
13900
14387
|
misplacedAuthorize.push({ kind: "file" });
|
|
13901
14388
|
}
|
|
13902
|
-
const sources = Object.values(modelDef.contents).filter((obj) =>
|
|
14389
|
+
const sources = Object.values(modelDef.contents).filter((obj) => isSourceDef4(obj)).map((sourceObj) => {
|
|
13903
14390
|
const struct = sourceObj;
|
|
13904
14391
|
const sourceName = struct.as || struct.name;
|
|
13905
14392
|
const annotations = annotationTexts(struct.annotations);
|
|
@@ -13959,7 +14446,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
13959
14446
|
continue;
|
|
13960
14447
|
}
|
|
13961
14448
|
const fieldName = field.as || field.name;
|
|
13962
|
-
if (isJoined2(field) &&
|
|
14449
|
+
if (isJoined2(field) && isSourceDef4(field)) {
|
|
13963
14450
|
const joinedStruct = field;
|
|
13964
14451
|
if (joinFieldNamesUnresolvableDeclaration(joinedStruct, modelDef)) {
|
|
13965
14452
|
continue;
|
|
@@ -14393,11 +14880,12 @@ init_authorize_metrics();
|
|
|
14393
14880
|
init_data_styles();
|
|
14394
14881
|
init_errors();
|
|
14395
14882
|
init_authorize();
|
|
14883
|
+
init_gate_classification();
|
|
14396
14884
|
init_gate_dimension();
|
|
14397
14885
|
var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
|
|
14398
14886
|
import {
|
|
14399
14887
|
contextOverlay,
|
|
14400
|
-
isSourceDef as
|
|
14888
|
+
isSourceDef as isSourceDef5,
|
|
14401
14889
|
MalloyConfig,
|
|
14402
14890
|
MalloyError as MalloyError2,
|
|
14403
14891
|
modelDefToModelInfo,
|
|
@@ -14600,10 +15088,10 @@ function newRpcId() {
|
|
|
14600
15088
|
}
|
|
14601
15089
|
function callMain(send) {
|
|
14602
15090
|
const requestId = newRpcId();
|
|
14603
|
-
return new Promise((resolve2,
|
|
15091
|
+
return new Promise((resolve2, reject2) => {
|
|
14604
15092
|
pendingRpc.set(requestId, {
|
|
14605
15093
|
resolve: (value) => resolve2(value),
|
|
14606
|
-
reject
|
|
15094
|
+
reject: reject2
|
|
14607
15095
|
});
|
|
14608
15096
|
send(requestId);
|
|
14609
15097
|
});
|
|
@@ -14900,6 +15388,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
|
|
|
14900
15388
|
} = extractSources(modelDef, givens);
|
|
14901
15389
|
const queryResult = extractQueries(modelDef);
|
|
14902
15390
|
const queries = queryResult.queries;
|
|
15391
|
+
assertPartitionAnnotationsValid(modelDef);
|
|
14903
15392
|
assertNoMisplacedAuthorizeAnnotations([
|
|
14904
15393
|
...misplacedAuthorize,
|
|
14905
15394
|
...queryResult.misplacedAuthorize
|
|
@@ -14916,7 +15405,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
|
|
|
14916
15405
|
onRowLevelGateUnexpressible: authorizeWarningCollection.onRowLevelGateUnexpressible,
|
|
14917
15406
|
onOwnRowLevelConditionCompiled: (sourceName, condition) => {
|
|
14918
15407
|
const struct = modelDef.contents[sourceName];
|
|
14919
|
-
if (!struct || !
|
|
15408
|
+
if (!struct || !isSourceDef5(struct))
|
|
14920
15409
|
return;
|
|
14921
15410
|
validateSourceLineGateGivenUsage(sourceName, struct, condition.refSummary, condition.e, modelDef, (cause, detail) => {
|
|
14922
15411
|
recordRowLevelGateRejected(cause);
|
|
@@ -15038,6 +15527,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
|
|
|
15038
15527
|
finalFilterMap = extracted.filterMap;
|
|
15039
15528
|
const finalQueryResult = extractQueries(finalModelDef);
|
|
15040
15529
|
finalQueries = finalQueryResult.queries;
|
|
15530
|
+
assertPartitionAnnotationsValid(finalModelDef);
|
|
15041
15531
|
assertNoMisplacedAuthorizeAnnotations([
|
|
15042
15532
|
...extracted.misplacedAuthorize,
|
|
15043
15533
|
...finalQueryResult.misplacedAuthorize
|
|
@@ -15054,7 +15544,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
|
|
|
15054
15544
|
onRowLevelGateUnexpressible: authorizeWarningCollection.onRowLevelGateUnexpressible,
|
|
15055
15545
|
onOwnRowLevelConditionCompiled: (sourceName, condition) => {
|
|
15056
15546
|
const struct = finalCompiledModelDef.contents[sourceName];
|
|
15057
|
-
if (!struct || !
|
|
15547
|
+
if (!struct || !isSourceDef5(struct))
|
|
15058
15548
|
return;
|
|
15059
15549
|
validateSourceLineGateGivenUsage(sourceName, struct, condition.refSummary, condition.e, finalCompiledModelDef, (cause, detail) => {
|
|
15060
15550
|
recordRowLevelGateRejected(cause);
|