@malloy-publisher/server 0.2.4 → 0.2.6
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 +40 -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 +671 -180
- package/dist/server.mjs +437 -53
- package/package.json +1 -1
|
@@ -2375,6 +2375,8 @@ function internalErrorToHttpError(error) {
|
|
|
2375
2375
|
return httpError(404, error.message);
|
|
2376
2376
|
} else if (error instanceof MalloyError) {
|
|
2377
2377
|
return httpError(400, error.message);
|
|
2378
|
+
} else if (error instanceof TableNotFoundError) {
|
|
2379
|
+
return httpError(404, error.message, "TABLE_NOT_FOUND");
|
|
2378
2380
|
} else if (error instanceof ConnectionNotFoundError) {
|
|
2379
2381
|
return httpError(404, error.message);
|
|
2380
2382
|
} else if (error instanceof DestinationNotFoundError) {
|
|
@@ -2407,16 +2409,17 @@ function internalErrorToHttpError(error) {
|
|
|
2407
2409
|
return httpError(500, error.message);
|
|
2408
2410
|
}
|
|
2409
2411
|
}
|
|
2410
|
-
function httpError(code, message) {
|
|
2412
|
+
function httpError(code, message, reason) {
|
|
2411
2413
|
return {
|
|
2412
2414
|
status: code,
|
|
2413
2415
|
json: {
|
|
2414
2416
|
code,
|
|
2415
|
-
message
|
|
2417
|
+
message,
|
|
2418
|
+
...reason ? { reason } : {}
|
|
2416
2419
|
}
|
|
2417
2420
|
};
|
|
2418
2421
|
}
|
|
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;
|
|
2422
|
+
var NotImplementedError, BadRequestError, InvalidArgumentError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, MaterializationEligibilityError, PublisherConfigError, FrozenConfigError, AccessDeniedError, NotQueryableError, MaterializationNotFoundError, MaterializationConflictError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
|
|
2420
2423
|
var init_errors = __esm(() => {
|
|
2421
2424
|
init_constants();
|
|
2422
2425
|
NotImplementedError = class NotImplementedError extends Error {
|
|
@@ -2456,6 +2459,11 @@ var init_errors = __esm(() => {
|
|
|
2456
2459
|
super(message);
|
|
2457
2460
|
}
|
|
2458
2461
|
};
|
|
2462
|
+
TableNotFoundError = class TableNotFoundError extends Error {
|
|
2463
|
+
constructor(message) {
|
|
2464
|
+
super(message);
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2459
2467
|
ConnectionError = class ConnectionError extends Error {
|
|
2460
2468
|
constructor(message) {
|
|
2461
2469
|
super(message);
|
|
@@ -13359,6 +13367,79 @@ var init_data_styles = __esm(() => {
|
|
|
13359
13367
|
init_logger();
|
|
13360
13368
|
});
|
|
13361
13369
|
|
|
13370
|
+
// src/service/annotations.ts
|
|
13371
|
+
import { Annotations } from "@malloydata/malloy";
|
|
13372
|
+
function isReservedRoute(route) {
|
|
13373
|
+
return route === "" || !/[\p{L}\p{N}]/u.test(route);
|
|
13374
|
+
}
|
|
13375
|
+
function ownModelAnnotations(modelDef) {
|
|
13376
|
+
return foldModelAnnotations(modelDef, (id) => id === modelDef.modelID || id.startsWith("internal://"));
|
|
13377
|
+
}
|
|
13378
|
+
function modelAnnotations(modelDef) {
|
|
13379
|
+
return foldModelAnnotations(modelDef, () => true);
|
|
13380
|
+
}
|
|
13381
|
+
function foldModelAnnotations(modelDef, admits) {
|
|
13382
|
+
const registry = modelDef.modelAnnotations ?? {};
|
|
13383
|
+
const visited = new Set;
|
|
13384
|
+
const order = [];
|
|
13385
|
+
const visit = (id) => {
|
|
13386
|
+
if (!admits(id))
|
|
13387
|
+
return;
|
|
13388
|
+
if (visited.has(id))
|
|
13389
|
+
return;
|
|
13390
|
+
visited.add(id);
|
|
13391
|
+
const entry = registry[id];
|
|
13392
|
+
if (!entry)
|
|
13393
|
+
return;
|
|
13394
|
+
for (const dep of entry.inheritsFrom)
|
|
13395
|
+
visit(dep);
|
|
13396
|
+
order.push(id);
|
|
13397
|
+
};
|
|
13398
|
+
visit(modelDef.modelID);
|
|
13399
|
+
let folded;
|
|
13400
|
+
for (const id of order) {
|
|
13401
|
+
const own = registry[id].ownNotes;
|
|
13402
|
+
if (!own.notes?.length && !own.blockNotes?.length)
|
|
13403
|
+
continue;
|
|
13404
|
+
folded = {
|
|
13405
|
+
notes: own.notes,
|
|
13406
|
+
blockNotes: own.blockNotes,
|
|
13407
|
+
inherits: folded
|
|
13408
|
+
};
|
|
13409
|
+
}
|
|
13410
|
+
return folded ?? {};
|
|
13411
|
+
}
|
|
13412
|
+
function ownModelNotes(modelDef) {
|
|
13413
|
+
const registry = modelDef.modelAnnotations ?? {};
|
|
13414
|
+
const isSameDocument = (id) => id === modelDef.modelID || id.startsWith("internal://");
|
|
13415
|
+
const seen = new Set;
|
|
13416
|
+
const texts = [];
|
|
13417
|
+
const visit = (id) => {
|
|
13418
|
+
if (seen.has(id) || !isSameDocument(id))
|
|
13419
|
+
return;
|
|
13420
|
+
seen.add(id);
|
|
13421
|
+
const entry = registry[id];
|
|
13422
|
+
if (!entry)
|
|
13423
|
+
return;
|
|
13424
|
+
for (const dep of entry.inheritsFrom)
|
|
13425
|
+
visit(dep);
|
|
13426
|
+
texts.push(...ownLevelNoteTexts(entry.ownNotes));
|
|
13427
|
+
};
|
|
13428
|
+
visit(modelDef.modelID);
|
|
13429
|
+
return texts;
|
|
13430
|
+
}
|
|
13431
|
+
function annotationTexts(annote) {
|
|
13432
|
+
const texts = new Annotations(annote).texts();
|
|
13433
|
+
return texts.length > 0 ? texts : undefined;
|
|
13434
|
+
}
|
|
13435
|
+
function ownLevelNoteTexts(annote) {
|
|
13436
|
+
return ownLevelNotes(annote).map((note) => note.text);
|
|
13437
|
+
}
|
|
13438
|
+
function ownLevelNotes(annote) {
|
|
13439
|
+
return [...annote?.blockNotes ?? [], ...annote?.notes ?? []];
|
|
13440
|
+
}
|
|
13441
|
+
var init_annotations = () => {};
|
|
13442
|
+
|
|
13362
13443
|
// src/service/gate_dimension.ts
|
|
13363
13444
|
import {
|
|
13364
13445
|
isJoined,
|
|
@@ -13450,78 +13531,590 @@ var init_gate_dimension = __esm(() => {
|
|
|
13450
13531
|
init_errors();
|
|
13451
13532
|
});
|
|
13452
13533
|
|
|
13453
|
-
// src/service/
|
|
13454
|
-
import {
|
|
13455
|
-
function
|
|
13456
|
-
|
|
13534
|
+
// src/service/gate_registry_walk.ts
|
|
13535
|
+
import { isSourceDef as isSourceDef2 } from "@malloydata/malloy";
|
|
13536
|
+
function resolveDeclaredSource(struct, modelDef) {
|
|
13537
|
+
if (!modelDef)
|
|
13538
|
+
return { kind: "none" };
|
|
13539
|
+
let sawBrokenEntry = false;
|
|
13540
|
+
for (const id of [struct.referenceID, struct.sourceID]) {
|
|
13541
|
+
const entry = id ? modelDef.sourceRegistry?.[id]?.entry : undefined;
|
|
13542
|
+
if (!entry)
|
|
13543
|
+
continue;
|
|
13544
|
+
const declared = entry.type === "source_registry_reference" ? modelDef.contents[entry.name] : entry;
|
|
13545
|
+
if (declared === struct)
|
|
13546
|
+
continue;
|
|
13547
|
+
if (!declared || !isSourceDef2(declared)) {
|
|
13548
|
+
sawBrokenEntry = true;
|
|
13549
|
+
continue;
|
|
13550
|
+
}
|
|
13551
|
+
return { kind: "resolved", source: declared };
|
|
13552
|
+
}
|
|
13553
|
+
return sawBrokenEntry ? { kind: "unresolvable" } : { kind: "none" };
|
|
13457
13554
|
}
|
|
13458
|
-
function
|
|
13459
|
-
|
|
13555
|
+
function ancestorGateExprs(struct, modelDef, seen = new Set) {
|
|
13556
|
+
let inherited = struct.annotations?.inherits;
|
|
13557
|
+
for (let depth = 0;inherited && depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
13558
|
+
const exprs2 = collectAuthorizeExprs(ownLevelNoteTexts(inherited));
|
|
13559
|
+
if (exprs2.length > 0)
|
|
13560
|
+
return exprs2;
|
|
13561
|
+
inherited = inherited.inherits;
|
|
13562
|
+
}
|
|
13563
|
+
if (inherited)
|
|
13564
|
+
return ["false"];
|
|
13565
|
+
seen.add(struct);
|
|
13566
|
+
if (seen.size > ANCESTOR_WALK_MAX_DEPTH)
|
|
13567
|
+
return ["false"];
|
|
13568
|
+
const declared = resolveDeclaredSource(struct, modelDef);
|
|
13569
|
+
if (declared.kind === "unresolvable")
|
|
13570
|
+
return ["false"];
|
|
13571
|
+
if (declared.kind === "none" || seen.has(declared.source))
|
|
13572
|
+
return [];
|
|
13573
|
+
const exprs = collectAuthorizeExprs(ownLevelNoteTexts(declared.source.annotations));
|
|
13574
|
+
return exprs.length > 0 ? exprs : ancestorGateExprs(declared.source, modelDef, seen);
|
|
13460
13575
|
}
|
|
13461
|
-
function
|
|
13462
|
-
|
|
13576
|
+
function resolveQuerySourceBase(struct, modelDef) {
|
|
13577
|
+
const duck = struct;
|
|
13578
|
+
if (duck.type !== "query_source")
|
|
13579
|
+
return;
|
|
13580
|
+
const ref = duck.query?.structRef;
|
|
13581
|
+
const base = typeof ref === "string" ? modelDef?.contents[ref] : ref;
|
|
13582
|
+
return base && isSourceDef2(base) ? base : undefined;
|
|
13463
13583
|
}
|
|
13464
|
-
function
|
|
13465
|
-
const
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13584
|
+
function resolveCompositeResolvedBase(struct) {
|
|
13585
|
+
const duck = struct;
|
|
13586
|
+
return duck.type === "query_source" ? duck.query?.compositeResolvedSourceDef : undefined;
|
|
13587
|
+
}
|
|
13588
|
+
function effectiveAncestorGateExprs(struct, modelDef, seen = new Set) {
|
|
13589
|
+
const direct = ancestorGateExprs(struct, modelDef, new Set(seen));
|
|
13590
|
+
if (direct.length > 0)
|
|
13591
|
+
return [direct];
|
|
13592
|
+
if (seen.has(struct))
|
|
13593
|
+
return [];
|
|
13594
|
+
seen.add(struct);
|
|
13595
|
+
const groups = [];
|
|
13596
|
+
const base = resolveQuerySourceBase(struct, modelDef);
|
|
13597
|
+
if (!base) {
|
|
13598
|
+
const duck = struct;
|
|
13599
|
+
if (duck.type === "query_source")
|
|
13600
|
+
groups.push(["false"]);
|
|
13601
|
+
} else if (!seen.has(base)) {
|
|
13602
|
+
const ownExprs = collectAuthorizeExprs(ownLevelNoteTexts(base.annotations));
|
|
13603
|
+
groups.push(...ownExprs.length > 0 ? [ownExprs] : effectiveAncestorGateExprs(base, modelDef, seen));
|
|
13604
|
+
}
|
|
13605
|
+
const composite = resolveCompositeResolvedBase(struct);
|
|
13606
|
+
if (composite && !seen.has(composite)) {
|
|
13607
|
+
const parentOwnNotes = base ? ownLevelNotes(base.annotations) : [];
|
|
13608
|
+
const compositeOwnNotes = ownLevelNotes(composite.annotations).filter((note) => !parentOwnNotes.includes(note));
|
|
13609
|
+
const compositeOwn = collectAuthorizeExprs(compositeOwnNotes.map((note) => note.text));
|
|
13610
|
+
groups.push(...compositeOwn.length > 0 ? [compositeOwn] : effectiveAncestorGateExprs(composite, modelDef, seen));
|
|
13611
|
+
}
|
|
13612
|
+
return groups;
|
|
13613
|
+
}
|
|
13614
|
+
function derivedStructsReachable(roots, modelDef) {
|
|
13615
|
+
const seen = new Set(roots);
|
|
13616
|
+
const found = [];
|
|
13617
|
+
const worklist = [...roots];
|
|
13618
|
+
for (let i = 0;i < worklist.length; i++) {
|
|
13619
|
+
const struct = worklist[i];
|
|
13620
|
+
for (const next of [
|
|
13621
|
+
resolveQuerySourceBase(struct, modelDef),
|
|
13622
|
+
resolveCompositeResolvedBase(struct)
|
|
13623
|
+
]) {
|
|
13624
|
+
if (!next || seen.has(next))
|
|
13625
|
+
continue;
|
|
13626
|
+
seen.add(next);
|
|
13627
|
+
found.push(next);
|
|
13628
|
+
worklist.push(next);
|
|
13629
|
+
}
|
|
13630
|
+
}
|
|
13631
|
+
return found;
|
|
13632
|
+
}
|
|
13633
|
+
var ANCESTOR_WALK_MAX_DEPTH = 32;
|
|
13634
|
+
var init_gate_registry_walk = __esm(() => {
|
|
13635
|
+
init_annotations();
|
|
13636
|
+
init_authorize();
|
|
13637
|
+
});
|
|
13638
|
+
|
|
13639
|
+
// src/service/partition_annotation.ts
|
|
13640
|
+
import { payloadOf as payloadOf2, routeOf as routeOf2 } from "@malloydata/malloy";
|
|
13641
|
+
function noteRoute2(text) {
|
|
13642
|
+
return routeOf2({ value: text.trimStart() });
|
|
13643
|
+
}
|
|
13644
|
+
function containsPartitionAnnotationTag(texts) {
|
|
13645
|
+
return texts.some((text) => noteRoute2(text) === PARTITION_ROUTE);
|
|
13646
|
+
}
|
|
13647
|
+
function reachesPartitionTagBelow(node, seen = new WeakSet, depth = 0) {
|
|
13648
|
+
if (depth > MAX_PARTITION_IR_WALK_DEPTH) {
|
|
13649
|
+
throw new Error("partition-marker IR walk exceeded max depth");
|
|
13650
|
+
}
|
|
13651
|
+
if (node === null || typeof node !== "object")
|
|
13652
|
+
return false;
|
|
13653
|
+
if (seen.has(node))
|
|
13654
|
+
return false;
|
|
13655
|
+
seen.add(node);
|
|
13656
|
+
if (Array.isArray(node)) {
|
|
13657
|
+
return node.some((item) => reachesPartitionTagBelow(item, seen, depth + 1));
|
|
13658
|
+
}
|
|
13659
|
+
const record = node;
|
|
13660
|
+
if (depth > 0 && record.join !== undefined)
|
|
13661
|
+
return false;
|
|
13662
|
+
if (depth > 0) {
|
|
13663
|
+
for (const key of ["blockNotes", "notes"]) {
|
|
13664
|
+
const arr = record[key];
|
|
13665
|
+
if (!Array.isArray(arr))
|
|
13666
|
+
continue;
|
|
13667
|
+
const texts = arr.map((n) => typeof n === "string" ? n : n && typeof n === "object" && typeof n.text === "string" ? n.text : undefined).filter((text) => text !== undefined);
|
|
13668
|
+
if (containsPartitionAnnotationTag(texts))
|
|
13669
|
+
return true;
|
|
13670
|
+
}
|
|
13671
|
+
}
|
|
13672
|
+
return Object.entries(record).some(([key, value]) => depth === 0 && key === "annotations" ? false : reachesPartitionTagBelow(value, seen, depth + 1));
|
|
13673
|
+
}
|
|
13674
|
+
function notePayload2(text) {
|
|
13675
|
+
return payloadOf2({ value: text.trimStart() }) ?? "";
|
|
13676
|
+
}
|
|
13677
|
+
function rejectionMessage(sourceName, body, detail) {
|
|
13678
|
+
return `Source "${sourceName}" declares \`#(partition) ${body}\`: ${detail} ` + `#(partition) only accepts \`<column> = $GIVEN\`, where <column> is a ` + `single field or a dotted join path.`;
|
|
13679
|
+
}
|
|
13680
|
+
function reject(sourceName, body, cause, detail) {
|
|
13681
|
+
throw new PartitionAnnotationError(cause, rejectionMessage(sourceName, body, detail));
|
|
13682
|
+
}
|
|
13683
|
+
function parsePartitionAnnotation(sourceName, annotationText) {
|
|
13684
|
+
if (noteRoute2(annotationText) !== PARTITION_ROUTE)
|
|
13685
|
+
return null;
|
|
13686
|
+
const body = notePayload2(annotationText).trim();
|
|
13687
|
+
if (body.length === 0) {
|
|
13688
|
+
reject(sourceName, body, "empty_body", "the expression body is empty.");
|
|
13689
|
+
}
|
|
13690
|
+
if (COMPOUND_BOOLEAN_RE.test(body)) {
|
|
13691
|
+
reject(sourceName, body, "compound_boolean", "a compound boolean (`and`/`or`/`not`) is not allowed — declare one " + "`#(partition)` marker per column.");
|
|
13692
|
+
}
|
|
13693
|
+
if (IN_OPERATOR_RE.test(body)) {
|
|
13694
|
+
reject(sourceName, body, "in_operator", "the `in` operator is not allowed.");
|
|
13695
|
+
}
|
|
13696
|
+
if (NEGATED_OPERATOR_RE.test(body)) {
|
|
13697
|
+
reject(sourceName, body, "negated_operator", "`!=` is not allowed.");
|
|
13698
|
+
}
|
|
13699
|
+
if (COMPARISON_OPERATOR_RE.test(body)) {
|
|
13700
|
+
reject(sourceName, body, "comparison_operator", "only `=` is allowed, not `<`/`>`/`<=`/`>=`.");
|
|
13701
|
+
}
|
|
13702
|
+
const eq = body.indexOf("=");
|
|
13703
|
+
if (eq === -1) {
|
|
13704
|
+
reject(sourceName, body, "malformed_body", "no `=` was found.");
|
|
13705
|
+
}
|
|
13706
|
+
const left = body.slice(0, eq).trim();
|
|
13707
|
+
const right = body.slice(eq + 1).trim();
|
|
13708
|
+
if (!FIELD_PATH_RE.test(left)) {
|
|
13709
|
+
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.");
|
|
13710
|
+
}
|
|
13711
|
+
const givenMatch = GIVEN_REF_RE.exec(right);
|
|
13712
|
+
if (!givenMatch) {
|
|
13713
|
+
reject(sourceName, body, "missing_given_reference", `\`${right}\` is not a given reference — the right side must be ` + "`$NAME`.");
|
|
13714
|
+
}
|
|
13715
|
+
return { column: left, given: givenMatch[1] };
|
|
13716
|
+
}
|
|
13717
|
+
function collectPartitionPairs(sourceName, annotationTexts2) {
|
|
13718
|
+
const pairs = [];
|
|
13719
|
+
const seenGivens = new Map;
|
|
13720
|
+
for (const text of annotationTexts2) {
|
|
13721
|
+
const pair = parsePartitionAnnotation(sourceName, text);
|
|
13722
|
+
if (pair === null)
|
|
13486
13723
|
continue;
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13724
|
+
const priorColumn = seenGivens.get(pair.given);
|
|
13725
|
+
if (priorColumn !== undefined) {
|
|
13726
|
+
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.`);
|
|
13727
|
+
}
|
|
13728
|
+
seenGivens.set(pair.given, pair.column);
|
|
13729
|
+
pairs.push(pair);
|
|
13492
13730
|
}
|
|
13493
|
-
return
|
|
13731
|
+
return pairs;
|
|
13494
13732
|
}
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13733
|
+
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;
|
|
13734
|
+
var init_partition_annotation = __esm(() => {
|
|
13735
|
+
init_errors();
|
|
13736
|
+
PartitionAnnotationError = class PartitionAnnotationError extends ModelCompilationError {
|
|
13737
|
+
rejectionCause;
|
|
13738
|
+
constructor(rejectionCause, message) {
|
|
13739
|
+
super({ message });
|
|
13740
|
+
this.rejectionCause = rejectionCause;
|
|
13741
|
+
}
|
|
13742
|
+
};
|
|
13743
|
+
FIELD_PATH_RE = new RegExp(`^${IDENT}(?:\\.${IDENT})*$`);
|
|
13744
|
+
GIVEN_REF_RE = new RegExp(`^\\$(${IDENT})$`);
|
|
13745
|
+
COMPOUND_BOOLEAN_RE = /\b(and|or|not)\b/i;
|
|
13746
|
+
IN_OPERATOR_RE = /\bin\b/i;
|
|
13747
|
+
NEGATED_OPERATOR_RE = /!=/;
|
|
13748
|
+
COMPARISON_OPERATOR_RE = /(>=|<=|>|<)/;
|
|
13749
|
+
});
|
|
13750
|
+
|
|
13751
|
+
// src/service/gate_classification.ts
|
|
13752
|
+
import {
|
|
13753
|
+
isSourceDef as isSourceDef3
|
|
13754
|
+
} from "@malloydata/malloy";
|
|
13755
|
+
function createGateClassificationDeps(givens, modelPath) {
|
|
13756
|
+
return {
|
|
13757
|
+
gateShapeCache: new Map,
|
|
13758
|
+
givenDeclaredTypes: computeGivenDeclaredTypes(givens),
|
|
13759
|
+
modelPath
|
|
13510
13760
|
};
|
|
13511
|
-
visit(modelDef.modelID);
|
|
13512
|
-
return texts;
|
|
13513
13761
|
}
|
|
13514
|
-
function
|
|
13515
|
-
const
|
|
13516
|
-
|
|
13762
|
+
function gateExprsForOwnAnnotations(struct, modelDef, excludeNotes = []) {
|
|
13763
|
+
const ownNotes = ownLevelNotes(struct.annotations).filter((note) => !excludeNotes.includes(note));
|
|
13764
|
+
try {
|
|
13765
|
+
const own = collectAuthorizeExprs(ownNotes.map((note) => note.text));
|
|
13766
|
+
if (own.length > 0) {
|
|
13767
|
+
return { exprs: own, fromAncestor: false };
|
|
13768
|
+
}
|
|
13769
|
+
const ancestor = ancestorGateExprs(struct, modelDef);
|
|
13770
|
+
return { exprs: ancestor, fromAncestor: ancestor.length > 0 };
|
|
13771
|
+
} catch {
|
|
13772
|
+
return { exprs: ["false"], fromAncestor: false };
|
|
13773
|
+
}
|
|
13517
13774
|
}
|
|
13518
|
-
function
|
|
13519
|
-
|
|
13775
|
+
function collectEntryPointGates(struct, modelDef, seen = new Set, treatAsOwnGate = false, entryPointStruct = struct, excludeNotes = []) {
|
|
13776
|
+
if (!struct || !modelDef || seen.has(struct))
|
|
13777
|
+
return [];
|
|
13778
|
+
seen.add(struct);
|
|
13779
|
+
const results = [];
|
|
13780
|
+
const label = struct.as ?? struct.name;
|
|
13781
|
+
const { exprs: ownExprs, fromAncestor } = gateExprsForOwnAnnotations(struct, modelDef, excludeNotes);
|
|
13782
|
+
if (ownExprs.length > 0) {
|
|
13783
|
+
results.push({
|
|
13784
|
+
label,
|
|
13785
|
+
exprs: ownExprs,
|
|
13786
|
+
selfContained: fromAncestor || !treatAsOwnGate,
|
|
13787
|
+
struct: entryPointStruct
|
|
13788
|
+
});
|
|
13789
|
+
}
|
|
13790
|
+
const duck = struct;
|
|
13791
|
+
if (duck.type === "query_source") {
|
|
13792
|
+
const base = resolveQuerySourceBase(struct, modelDef);
|
|
13793
|
+
if (base) {
|
|
13794
|
+
results.push(...collectEntryPointGates(base, modelDef, seen, false, entryPointStruct));
|
|
13795
|
+
} else {
|
|
13796
|
+
results.push({
|
|
13797
|
+
label,
|
|
13798
|
+
exprs: ["false"],
|
|
13799
|
+
selfContained: true
|
|
13800
|
+
});
|
|
13801
|
+
}
|
|
13802
|
+
const resolved = duck.query?.compositeResolvedSourceDef;
|
|
13803
|
+
if (resolved) {
|
|
13804
|
+
results.push(...collectEntryPointGates(resolved, modelDef, seen, false, entryPointStruct, base ? ownLevelNotes(base.annotations) : []));
|
|
13805
|
+
}
|
|
13806
|
+
}
|
|
13807
|
+
return results;
|
|
13520
13808
|
}
|
|
13521
|
-
function
|
|
13522
|
-
|
|
13809
|
+
async function resolveGateShape(entry, originModelDef, graftScope, deps) {
|
|
13810
|
+
if (!entry.struct)
|
|
13811
|
+
return { shape: "rejected" };
|
|
13812
|
+
if (!graftScope) {
|
|
13813
|
+
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 });
|
|
13814
|
+
return { shape: "rejected" };
|
|
13815
|
+
}
|
|
13816
|
+
const graftTarget = resolveGraftTarget(entry.struct, originModelDef, graftScope.modelDef);
|
|
13817
|
+
if (!graftTarget) {
|
|
13818
|
+
return { shape: "rejected" };
|
|
13819
|
+
}
|
|
13820
|
+
const filterText = gateFilterText(entry.exprs);
|
|
13821
|
+
const cacheKey = `${graftScope.cacheScope}\x00${graftTarget}\x00${filterText}`;
|
|
13822
|
+
let cached = deps.gateShapeCache.get(cacheKey);
|
|
13823
|
+
if (!cached) {
|
|
13824
|
+
let condition;
|
|
13825
|
+
try {
|
|
13826
|
+
condition = await liftGateCondition(graftTarget, filterText, graftScope.materializer);
|
|
13827
|
+
} catch (err) {
|
|
13828
|
+
logger.debug("Row-level gate condition failed to lift; denying", {
|
|
13829
|
+
modelPath: deps.modelPath,
|
|
13830
|
+
graftTarget,
|
|
13831
|
+
error: err instanceof Error ? err.message : String(err)
|
|
13832
|
+
});
|
|
13833
|
+
return { shape: "rejected" };
|
|
13834
|
+
}
|
|
13835
|
+
let classification;
|
|
13836
|
+
const hasUsableExpr = condition.e !== undefined && condition.e !== null && typeof condition.e === "object" && typeof condition.e.node === "string";
|
|
13837
|
+
if (!hasUsableExpr) {
|
|
13838
|
+
classification = {
|
|
13839
|
+
shape: "rejected",
|
|
13840
|
+
cause: "unclassifiable_condition",
|
|
13841
|
+
detail: "this entry's lifted condition carries no usable expression"
|
|
13842
|
+
};
|
|
13843
|
+
} else if (isBareFalseLiteral(condition.e)) {
|
|
13844
|
+
classification = { shape: "row_level", givenNames: [] };
|
|
13845
|
+
} else {
|
|
13846
|
+
const targetStruct = graftScope.modelDef.contents[graftTarget];
|
|
13847
|
+
if (!isSourceDef3(targetStruct)) {
|
|
13848
|
+
classification = {
|
|
13849
|
+
shape: "rejected",
|
|
13850
|
+
cause: "given_usage_unresolvable",
|
|
13851
|
+
detail: "this gate's graft target does not resolve to a source on this model"
|
|
13852
|
+
};
|
|
13853
|
+
} else {
|
|
13854
|
+
const expansion = expandRefSummaryGivenIds(targetStruct, condition.refSummary);
|
|
13855
|
+
if (!expansion.ok) {
|
|
13856
|
+
classification = {
|
|
13857
|
+
shape: "rejected",
|
|
13858
|
+
cause: "given_usage_unresolvable",
|
|
13859
|
+
detail: `this gate references \`${expansion.unresolvedPath}\`, which could not be resolved on the graft target`
|
|
13860
|
+
};
|
|
13861
|
+
} else {
|
|
13862
|
+
const givenNames = Array.from(expansion.givenIds).map((id) => graftScope.modelDef.givens?.[id]?.name).filter((name) => !!name);
|
|
13863
|
+
const literalNames = referencedGivenNames(filterText);
|
|
13864
|
+
const accountedFor = new Set(givenNames);
|
|
13865
|
+
const unaccounted = literalNames.filter((name) => !accountedFor.has(name));
|
|
13866
|
+
if (givenNames.length !== expansion.givenIds.size || unaccounted.length > 0) {
|
|
13867
|
+
classification = {
|
|
13868
|
+
shape: "rejected",
|
|
13869
|
+
cause: "unreachable_given",
|
|
13870
|
+
detail: "this gate references a given id that does not resolve to a name on this model"
|
|
13871
|
+
};
|
|
13872
|
+
} else {
|
|
13873
|
+
classification = { shape: "row_level", givenNames };
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
}
|
|
13877
|
+
}
|
|
13878
|
+
if (classification.shape === "row_level") {
|
|
13879
|
+
const unreachable = classification.givenNames.find((name) => !deps.givenDeclaredTypes.has(name));
|
|
13880
|
+
if (unreachable !== undefined) {
|
|
13881
|
+
logger.warn("Gate accepted a given off the model surface; denying", {
|
|
13882
|
+
modelPath: deps.modelPath,
|
|
13883
|
+
graftTarget,
|
|
13884
|
+
givenName: unreachable
|
|
13885
|
+
});
|
|
13886
|
+
classification = {
|
|
13887
|
+
shape: "rejected",
|
|
13888
|
+
cause: "unreachable_given",
|
|
13889
|
+
detail: `\`$${unreachable}\` is not on this model's given surface`
|
|
13890
|
+
};
|
|
13891
|
+
}
|
|
13892
|
+
}
|
|
13893
|
+
cached = { classification, condition };
|
|
13894
|
+
deps.gateShapeCache.set(cacheKey, cached);
|
|
13895
|
+
}
|
|
13896
|
+
if (cached.classification.shape === "rejected") {
|
|
13897
|
+
return { shape: "rejected", cause: cached.classification.cause };
|
|
13898
|
+
}
|
|
13899
|
+
return {
|
|
13900
|
+
shape: "row_level",
|
|
13901
|
+
graftTarget,
|
|
13902
|
+
filterText,
|
|
13903
|
+
condition: cached.condition,
|
|
13904
|
+
givenNames: cached.classification.givenNames
|
|
13905
|
+
};
|
|
13523
13906
|
}
|
|
13524
|
-
|
|
13907
|
+
function resolveGraftTarget(struct, originModelDef, graftModelDef) {
|
|
13908
|
+
const direct = findContentsKey(struct, graftModelDef);
|
|
13909
|
+
if (direct)
|
|
13910
|
+
return direct;
|
|
13911
|
+
let current = struct;
|
|
13912
|
+
const seen = new Set([struct]);
|
|
13913
|
+
for (let depth = 0;depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
13914
|
+
const declared = resolveDeclaredSource(current, originModelDef);
|
|
13915
|
+
let next;
|
|
13916
|
+
if (declared.kind === "resolved" && !seen.has(declared.source)) {
|
|
13917
|
+
next = declared.source;
|
|
13918
|
+
} else if (declared.kind === "none") {
|
|
13919
|
+
next = findSourceByOwnAnnotationIdentity(current, graftModelDef, seen);
|
|
13920
|
+
}
|
|
13921
|
+
if (!next)
|
|
13922
|
+
return;
|
|
13923
|
+
const key = findContentsKey(next, graftModelDef);
|
|
13924
|
+
if (key)
|
|
13925
|
+
return key;
|
|
13926
|
+
seen.add(next);
|
|
13927
|
+
current = next;
|
|
13928
|
+
}
|
|
13929
|
+
return;
|
|
13930
|
+
}
|
|
13931
|
+
function findContentsKey(struct, modelDef) {
|
|
13932
|
+
for (const [key, value] of Object.entries(modelDef.contents)) {
|
|
13933
|
+
if (value === struct)
|
|
13934
|
+
return key;
|
|
13935
|
+
}
|
|
13936
|
+
if (struct.sourceID) {
|
|
13937
|
+
for (const [key, value] of Object.entries(modelDef.contents)) {
|
|
13938
|
+
if (isSourceDef3(value) && value.sourceID === struct.sourceID) {
|
|
13939
|
+
return key;
|
|
13940
|
+
}
|
|
13941
|
+
}
|
|
13942
|
+
}
|
|
13943
|
+
return;
|
|
13944
|
+
}
|
|
13945
|
+
function findSourceByOwnAnnotationIdentity(struct, modelDef, exclude) {
|
|
13946
|
+
const ownNotes = [
|
|
13947
|
+
...struct.annotations?.blockNotes ?? [],
|
|
13948
|
+
...struct.annotations?.notes ?? []
|
|
13949
|
+
];
|
|
13950
|
+
if (ownNotes.length === 0)
|
|
13951
|
+
return;
|
|
13952
|
+
for (const value of Object.values(modelDef.contents)) {
|
|
13953
|
+
if (!isSourceDef3(value) || value === struct || exclude.has(value)) {
|
|
13954
|
+
continue;
|
|
13955
|
+
}
|
|
13956
|
+
const candidateNotes = [
|
|
13957
|
+
...value.annotations?.blockNotes ?? [],
|
|
13958
|
+
...value.annotations?.notes ?? []
|
|
13959
|
+
];
|
|
13960
|
+
if (candidateNotes.some((note) => ownNotes.includes(note))) {
|
|
13961
|
+
return value;
|
|
13962
|
+
}
|
|
13963
|
+
}
|
|
13964
|
+
return;
|
|
13965
|
+
}
|
|
13966
|
+
async function liftGateCondition(graftTarget, filterText, materializer) {
|
|
13967
|
+
const probe = materializer.loadQuery(buildRowLevelProbe(graftTarget, filterText));
|
|
13968
|
+
const prepared = await probe.getPreparedQuery();
|
|
13969
|
+
return liftProbeFilterCondition(prepared, `lifted probe for "${graftTarget}"`, filterText);
|
|
13970
|
+
}
|
|
13971
|
+
function isBareFalseLiteral(expr) {
|
|
13972
|
+
let node = expr;
|
|
13973
|
+
while (node.node === "()" && node.e && typeof node.e === "object") {
|
|
13974
|
+
node = node.e;
|
|
13975
|
+
}
|
|
13976
|
+
return node.node === "false";
|
|
13977
|
+
}
|
|
13978
|
+
function computeGivenDeclaredTypes(givens) {
|
|
13979
|
+
return new Map((givens ?? []).filter((g) => g.name != null && g.type != null).map((g) => [g.name, g.type]));
|
|
13980
|
+
}
|
|
13981
|
+
function resolveEntryPointPartitions(struct, modelDef, seen = new Set) {
|
|
13982
|
+
if (!struct || !modelDef || seen.has(struct))
|
|
13983
|
+
return [];
|
|
13984
|
+
seen.add(struct);
|
|
13985
|
+
const label = struct.as ?? struct.name;
|
|
13986
|
+
const own = collectPartitionPairs(label, ownLevelNotes(struct.annotations).map((note) => note.text));
|
|
13987
|
+
if (own.length > 0)
|
|
13988
|
+
return own;
|
|
13989
|
+
let inherited = struct.annotations?.inherits;
|
|
13990
|
+
for (let depth = 0;inherited && depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
13991
|
+
const pairs = collectPartitionPairs(label, ownLevelNoteTexts(inherited));
|
|
13992
|
+
if (pairs.length > 0)
|
|
13993
|
+
return pairs;
|
|
13994
|
+
inherited = inherited.inherits;
|
|
13995
|
+
}
|
|
13996
|
+
if (inherited)
|
|
13997
|
+
throw unresolvableAncestry(label);
|
|
13998
|
+
const declared = resolveDeclaredSource(struct, modelDef);
|
|
13999
|
+
if (declared.kind === "unresolvable")
|
|
14000
|
+
throw unresolvableAncestry(label);
|
|
14001
|
+
if (declared.kind === "resolved") {
|
|
14002
|
+
const pairs = resolveEntryPointPartitions(declared.source, modelDef, seen);
|
|
14003
|
+
if (pairs.length > 0)
|
|
14004
|
+
return pairs;
|
|
14005
|
+
}
|
|
14006
|
+
const queryBase = resolveQuerySourceBase(struct, modelDef);
|
|
14007
|
+
if (queryBase)
|
|
14008
|
+
return resolveEntryPointPartitions(queryBase, modelDef, seen);
|
|
14009
|
+
return [];
|
|
14010
|
+
}
|
|
14011
|
+
function unresolvableAncestry(label) {
|
|
14012
|
+
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.`);
|
|
14013
|
+
}
|
|
14014
|
+
function assertPartitionAnnotationsValid(modelDef) {
|
|
14015
|
+
if (!modelDef)
|
|
14016
|
+
return;
|
|
14017
|
+
for (const [key, obj] of Object.entries(modelDef.contents)) {
|
|
14018
|
+
if (!isSourceDef3(obj))
|
|
14019
|
+
continue;
|
|
14020
|
+
const label = obj.as ?? obj.name ?? key;
|
|
14021
|
+
if (obj.type !== "composite") {
|
|
14022
|
+
let resolved = [];
|
|
14023
|
+
try {
|
|
14024
|
+
resolved = resolveEntryPointPartitions(obj, modelDef);
|
|
14025
|
+
} catch (err) {
|
|
14026
|
+
if (!(err instanceof PartitionAnnotationError) || err.rejectionCause !== "ancestry_unresolvable") {
|
|
14027
|
+
throw err;
|
|
14028
|
+
}
|
|
14029
|
+
continue;
|
|
14030
|
+
}
|
|
14031
|
+
assertNoUnreachableMarker(obj, label, resolved);
|
|
14032
|
+
continue;
|
|
14033
|
+
}
|
|
14034
|
+
if (resolveEntryPointPartitions(obj, modelDef).length > 0) {
|
|
14035
|
+
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.`);
|
|
14036
|
+
}
|
|
14037
|
+
const marked = partitionedMemberLabel(obj, modelDef);
|
|
14038
|
+
if (marked !== undefined) {
|
|
14039
|
+
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.`);
|
|
14040
|
+
}
|
|
14041
|
+
}
|
|
14042
|
+
}
|
|
14043
|
+
function assertNoUnreachableMarker(struct, label, resolved) {
|
|
14044
|
+
if (resolved.length > 0)
|
|
14045
|
+
return;
|
|
14046
|
+
let reachesBelow;
|
|
14047
|
+
try {
|
|
14048
|
+
reachesBelow = reachesPartitionTagBelow(struct);
|
|
14049
|
+
} catch {
|
|
14050
|
+
reachesBelow = true;
|
|
14051
|
+
}
|
|
14052
|
+
if (!reachesBelow)
|
|
14053
|
+
return;
|
|
14054
|
+
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.`);
|
|
14055
|
+
}
|
|
14056
|
+
function partitionedMemberLabel(composite, modelDef, seen = new Set) {
|
|
14057
|
+
if (seen.has(composite))
|
|
14058
|
+
return;
|
|
14059
|
+
seen.add(composite);
|
|
14060
|
+
for (const member of composite.sources ?? []) {
|
|
14061
|
+
const label = member.as ?? member.name;
|
|
14062
|
+
if (resolveEntryPointPartitions(member, modelDef).length > 0) {
|
|
14063
|
+
return label;
|
|
14064
|
+
}
|
|
14065
|
+
if (member.type === "composite") {
|
|
14066
|
+
const nested = partitionedMemberLabel(member, modelDef, seen);
|
|
14067
|
+
if (nested !== undefined)
|
|
14068
|
+
return nested;
|
|
14069
|
+
}
|
|
14070
|
+
}
|
|
14071
|
+
return;
|
|
14072
|
+
}
|
|
14073
|
+
async function resolvePartitionGraftEntries(struct, originModelDef, graftScope, deps) {
|
|
14074
|
+
if (!struct || !originModelDef)
|
|
14075
|
+
return [];
|
|
14076
|
+
const pairs = resolveEntryPointPartitions(struct, originModelDef);
|
|
14077
|
+
if (pairs.length === 0)
|
|
14078
|
+
return [];
|
|
14079
|
+
const label = struct.as ?? struct.name;
|
|
14080
|
+
if (!graftScope) {
|
|
14081
|
+
logger.debug("Partition filter has no graft scope to attach to; denying", { modelPath: deps.modelPath, label });
|
|
14082
|
+
throw new Error(`partition on "${label}" has no graft scope`);
|
|
14083
|
+
}
|
|
14084
|
+
const graftTarget = resolveGraftTarget(struct, originModelDef, graftScope.modelDef);
|
|
14085
|
+
if (!graftTarget) {
|
|
14086
|
+
logger.debug("Partition filter resolved to no graft target; denying", {
|
|
14087
|
+
modelPath: deps.modelPath,
|
|
14088
|
+
label
|
|
14089
|
+
});
|
|
14090
|
+
throw new Error(`partition on "${label}" resolved to no graft target`);
|
|
14091
|
+
}
|
|
14092
|
+
const entries = [];
|
|
14093
|
+
for (const pair of pairs) {
|
|
14094
|
+
if (!deps.givenDeclaredTypes.has(pair.given)) {
|
|
14095
|
+
logger.warn("Partition references a given off the model surface; denying", { modelPath: deps.modelPath, graftTarget, givenName: pair.given });
|
|
14096
|
+
throw new Error(`partition on "${label}" references \`$${pair.given}\`, which is not on this model's given surface`);
|
|
14097
|
+
}
|
|
14098
|
+
const filterText = `${pair.column} = $${pair.given}`;
|
|
14099
|
+
const condition = await liftGateCondition(graftTarget, filterText, graftScope.materializer);
|
|
14100
|
+
entries.push({
|
|
14101
|
+
label,
|
|
14102
|
+
graftTarget,
|
|
14103
|
+
filterText,
|
|
14104
|
+
condition,
|
|
14105
|
+
givenNames: [pair.given]
|
|
14106
|
+
});
|
|
14107
|
+
}
|
|
14108
|
+
return entries;
|
|
14109
|
+
}
|
|
14110
|
+
var init_gate_classification = __esm(() => {
|
|
14111
|
+
init_logger();
|
|
14112
|
+
init_annotations();
|
|
14113
|
+
init_authorize();
|
|
14114
|
+
init_gate_dimension();
|
|
14115
|
+
init_gate_registry_walk();
|
|
14116
|
+
init_partition_annotation();
|
|
14117
|
+
});
|
|
13525
14118
|
|
|
13526
14119
|
// src/service/filter.ts
|
|
13527
14120
|
function parseFilterAnnotation(annotation) {
|
|
@@ -13715,115 +14308,10 @@ var init_filter = __esm(() => {
|
|
|
13715
14308
|
};
|
|
13716
14309
|
});
|
|
13717
14310
|
|
|
13718
|
-
// src/service/gate_registry_walk.ts
|
|
13719
|
-
import { isSourceDef as isSourceDef2 } from "@malloydata/malloy";
|
|
13720
|
-
function resolveDeclaredSource(struct, modelDef) {
|
|
13721
|
-
if (!modelDef)
|
|
13722
|
-
return { kind: "none" };
|
|
13723
|
-
let sawBrokenEntry = false;
|
|
13724
|
-
for (const id of [struct.referenceID, struct.sourceID]) {
|
|
13725
|
-
const entry = id ? modelDef.sourceRegistry?.[id]?.entry : undefined;
|
|
13726
|
-
if (!entry)
|
|
13727
|
-
continue;
|
|
13728
|
-
const declared = entry.type === "source_registry_reference" ? modelDef.contents[entry.name] : entry;
|
|
13729
|
-
if (declared === struct)
|
|
13730
|
-
continue;
|
|
13731
|
-
if (!declared || !isSourceDef2(declared)) {
|
|
13732
|
-
sawBrokenEntry = true;
|
|
13733
|
-
continue;
|
|
13734
|
-
}
|
|
13735
|
-
return { kind: "resolved", source: declared };
|
|
13736
|
-
}
|
|
13737
|
-
return sawBrokenEntry ? { kind: "unresolvable" } : { kind: "none" };
|
|
13738
|
-
}
|
|
13739
|
-
function ancestorGateExprs(struct, modelDef, seen = new Set) {
|
|
13740
|
-
let inherited = struct.annotations?.inherits;
|
|
13741
|
-
for (let depth = 0;inherited && depth < ANCESTOR_WALK_MAX_DEPTH; depth++) {
|
|
13742
|
-
const exprs2 = collectAuthorizeExprs(ownLevelNoteTexts(inherited));
|
|
13743
|
-
if (exprs2.length > 0)
|
|
13744
|
-
return exprs2;
|
|
13745
|
-
inherited = inherited.inherits;
|
|
13746
|
-
}
|
|
13747
|
-
if (inherited)
|
|
13748
|
-
return ["false"];
|
|
13749
|
-
seen.add(struct);
|
|
13750
|
-
if (seen.size > ANCESTOR_WALK_MAX_DEPTH)
|
|
13751
|
-
return ["false"];
|
|
13752
|
-
const declared = resolveDeclaredSource(struct, modelDef);
|
|
13753
|
-
if (declared.kind === "unresolvable")
|
|
13754
|
-
return ["false"];
|
|
13755
|
-
if (declared.kind === "none" || seen.has(declared.source))
|
|
13756
|
-
return [];
|
|
13757
|
-
const exprs = collectAuthorizeExprs(ownLevelNoteTexts(declared.source.annotations));
|
|
13758
|
-
return exprs.length > 0 ? exprs : ancestorGateExprs(declared.source, modelDef, seen);
|
|
13759
|
-
}
|
|
13760
|
-
function resolveQuerySourceBase(struct, modelDef) {
|
|
13761
|
-
const duck = struct;
|
|
13762
|
-
if (duck.type !== "query_source")
|
|
13763
|
-
return;
|
|
13764
|
-
const ref = duck.query?.structRef;
|
|
13765
|
-
const base = typeof ref === "string" ? modelDef?.contents[ref] : ref;
|
|
13766
|
-
return base && isSourceDef2(base) ? base : undefined;
|
|
13767
|
-
}
|
|
13768
|
-
function resolveCompositeResolvedBase(struct) {
|
|
13769
|
-
const duck = struct;
|
|
13770
|
-
return duck.type === "query_source" ? duck.query?.compositeResolvedSourceDef : undefined;
|
|
13771
|
-
}
|
|
13772
|
-
function effectiveAncestorGateExprs(struct, modelDef, seen = new Set) {
|
|
13773
|
-
const direct = ancestorGateExprs(struct, modelDef, new Set(seen));
|
|
13774
|
-
if (direct.length > 0)
|
|
13775
|
-
return [direct];
|
|
13776
|
-
if (seen.has(struct))
|
|
13777
|
-
return [];
|
|
13778
|
-
seen.add(struct);
|
|
13779
|
-
const groups = [];
|
|
13780
|
-
const base = resolveQuerySourceBase(struct, modelDef);
|
|
13781
|
-
if (!base) {
|
|
13782
|
-
const duck = struct;
|
|
13783
|
-
if (duck.type === "query_source")
|
|
13784
|
-
groups.push(["false"]);
|
|
13785
|
-
} else if (!seen.has(base)) {
|
|
13786
|
-
const ownExprs = collectAuthorizeExprs(ownLevelNoteTexts(base.annotations));
|
|
13787
|
-
groups.push(...ownExprs.length > 0 ? [ownExprs] : effectiveAncestorGateExprs(base, modelDef, seen));
|
|
13788
|
-
}
|
|
13789
|
-
const composite = resolveCompositeResolvedBase(struct);
|
|
13790
|
-
if (composite && !seen.has(composite)) {
|
|
13791
|
-
const parentOwnNotes = base ? ownLevelNotes(base.annotations) : [];
|
|
13792
|
-
const compositeOwnNotes = ownLevelNotes(composite.annotations).filter((note) => !parentOwnNotes.includes(note));
|
|
13793
|
-
const compositeOwn = collectAuthorizeExprs(compositeOwnNotes.map((note) => note.text));
|
|
13794
|
-
groups.push(...compositeOwn.length > 0 ? [compositeOwn] : effectiveAncestorGateExprs(composite, modelDef, seen));
|
|
13795
|
-
}
|
|
13796
|
-
return groups;
|
|
13797
|
-
}
|
|
13798
|
-
function derivedStructsReachable(roots, modelDef) {
|
|
13799
|
-
const seen = new Set(roots);
|
|
13800
|
-
const found = [];
|
|
13801
|
-
const worklist = [...roots];
|
|
13802
|
-
for (let i = 0;i < worklist.length; i++) {
|
|
13803
|
-
const struct = worklist[i];
|
|
13804
|
-
for (const next of [
|
|
13805
|
-
resolveQuerySourceBase(struct, modelDef),
|
|
13806
|
-
resolveCompositeResolvedBase(struct)
|
|
13807
|
-
]) {
|
|
13808
|
-
if (!next || seen.has(next))
|
|
13809
|
-
continue;
|
|
13810
|
-
seen.add(next);
|
|
13811
|
-
found.push(next);
|
|
13812
|
-
worklist.push(next);
|
|
13813
|
-
}
|
|
13814
|
-
}
|
|
13815
|
-
return found;
|
|
13816
|
-
}
|
|
13817
|
-
var ANCESTOR_WALK_MAX_DEPTH = 32;
|
|
13818
|
-
var init_gate_registry_walk = __esm(() => {
|
|
13819
|
-
init_annotations();
|
|
13820
|
-
init_authorize();
|
|
13821
|
-
});
|
|
13822
|
-
|
|
13823
14311
|
// src/service/source_extraction.ts
|
|
13824
14312
|
import {
|
|
13825
14313
|
isJoined as isJoined2,
|
|
13826
|
-
isSourceDef as
|
|
14314
|
+
isSourceDef as isSourceDef4
|
|
13827
14315
|
} from "@malloydata/malloy";
|
|
13828
14316
|
function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
|
|
13829
14317
|
const ids = [field.referenceID, field.sourceID].filter((id) => !!id);
|
|
@@ -13834,7 +14322,7 @@ function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
|
|
|
13834
14322
|
if (!entry)
|
|
13835
14323
|
continue;
|
|
13836
14324
|
const declared = entry.type === "source_registry_reference" ? modelDef.contents[entry.name] : entry;
|
|
13837
|
-
if (declared &&
|
|
14325
|
+
if (declared && isSourceDef4(declared))
|
|
13838
14326
|
return false;
|
|
13839
14327
|
}
|
|
13840
14328
|
return true;
|
|
@@ -13860,7 +14348,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
13860
14348
|
const nearMissAuthorize = [];
|
|
13861
14349
|
const sweptStructs = [];
|
|
13862
14350
|
for (const obj of Object.values(modelDef.contents)) {
|
|
13863
|
-
if (!
|
|
14351
|
+
if (!isSourceDef4(obj))
|
|
13864
14352
|
continue;
|
|
13865
14353
|
const struct = obj;
|
|
13866
14354
|
sweptStructs.push(obj);
|
|
@@ -13879,7 +14367,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
13879
14367
|
const entry = value.entry;
|
|
13880
14368
|
if (entry.type === "source_registry_reference")
|
|
13881
14369
|
continue;
|
|
13882
|
-
if (!
|
|
14370
|
+
if (!isSourceDef4(entry))
|
|
13883
14371
|
continue;
|
|
13884
14372
|
sweptStructs.push(entry);
|
|
13885
14373
|
for (const note of ownLevelNotes(entry.annotations)) {
|
|
@@ -13906,7 +14394,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
13906
14394
|
if (containsAuthorizeAnnotationTag((modelAnnotations(modelDef).notes ?? []).map((note) => note.text))) {
|
|
13907
14395
|
misplacedAuthorize.push({ kind: "file" });
|
|
13908
14396
|
}
|
|
13909
|
-
const sources = Object.values(modelDef.contents).filter((obj) =>
|
|
14397
|
+
const sources = Object.values(modelDef.contents).filter((obj) => isSourceDef4(obj)).map((sourceObj) => {
|
|
13910
14398
|
const struct = sourceObj;
|
|
13911
14399
|
const sourceName = struct.as || struct.name;
|
|
13912
14400
|
const annotations = annotationTexts(struct.annotations);
|
|
@@ -13966,7 +14454,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
13966
14454
|
continue;
|
|
13967
14455
|
}
|
|
13968
14456
|
const fieldName = field.as || field.name;
|
|
13969
|
-
if (isJoined2(field) &&
|
|
14457
|
+
if (isJoined2(field) && isSourceDef4(field)) {
|
|
13970
14458
|
const joinedStruct = field;
|
|
13971
14459
|
if (joinFieldNamesUnresolvableDeclaration(joinedStruct, modelDef)) {
|
|
13972
14460
|
continue;
|
|
@@ -14400,11 +14888,12 @@ init_authorize_metrics();
|
|
|
14400
14888
|
init_data_styles();
|
|
14401
14889
|
init_errors();
|
|
14402
14890
|
init_authorize();
|
|
14891
|
+
init_gate_classification();
|
|
14403
14892
|
init_gate_dimension();
|
|
14404
14893
|
var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
|
|
14405
14894
|
import {
|
|
14406
14895
|
contextOverlay,
|
|
14407
|
-
isSourceDef as
|
|
14896
|
+
isSourceDef as isSourceDef5,
|
|
14408
14897
|
MalloyConfig,
|
|
14409
14898
|
MalloyError as MalloyError2,
|
|
14410
14899
|
modelDefToModelInfo,
|
|
@@ -14607,10 +15096,10 @@ function newRpcId() {
|
|
|
14607
15096
|
}
|
|
14608
15097
|
function callMain(send) {
|
|
14609
15098
|
const requestId = newRpcId();
|
|
14610
|
-
return new Promise((resolve2,
|
|
15099
|
+
return new Promise((resolve2, reject2) => {
|
|
14611
15100
|
pendingRpc.set(requestId, {
|
|
14612
15101
|
resolve: (value) => resolve2(value),
|
|
14613
|
-
reject
|
|
15102
|
+
reject: reject2
|
|
14614
15103
|
});
|
|
14615
15104
|
send(requestId);
|
|
14616
15105
|
});
|
|
@@ -14907,6 +15396,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
|
|
|
14907
15396
|
} = extractSources(modelDef, givens);
|
|
14908
15397
|
const queryResult = extractQueries(modelDef);
|
|
14909
15398
|
const queries = queryResult.queries;
|
|
15399
|
+
assertPartitionAnnotationsValid(modelDef);
|
|
14910
15400
|
assertNoMisplacedAuthorizeAnnotations([
|
|
14911
15401
|
...misplacedAuthorize,
|
|
14912
15402
|
...queryResult.misplacedAuthorize
|
|
@@ -14923,7 +15413,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
|
|
|
14923
15413
|
onRowLevelGateUnexpressible: authorizeWarningCollection.onRowLevelGateUnexpressible,
|
|
14924
15414
|
onOwnRowLevelConditionCompiled: (sourceName, condition) => {
|
|
14925
15415
|
const struct = modelDef.contents[sourceName];
|
|
14926
|
-
if (!struct || !
|
|
15416
|
+
if (!struct || !isSourceDef5(struct))
|
|
14927
15417
|
return;
|
|
14928
15418
|
validateSourceLineGateGivenUsage(sourceName, struct, condition.refSummary, condition.e, modelDef, (cause, detail) => {
|
|
14929
15419
|
recordRowLevelGateRejected(cause);
|
|
@@ -15045,6 +15535,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
|
|
|
15045
15535
|
finalFilterMap = extracted.filterMap;
|
|
15046
15536
|
const finalQueryResult = extractQueries(finalModelDef);
|
|
15047
15537
|
finalQueries = finalQueryResult.queries;
|
|
15538
|
+
assertPartitionAnnotationsValid(finalModelDef);
|
|
15048
15539
|
assertNoMisplacedAuthorizeAnnotations([
|
|
15049
15540
|
...extracted.misplacedAuthorize,
|
|
15050
15541
|
...finalQueryResult.misplacedAuthorize
|
|
@@ -15061,7 +15552,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
|
|
|
15061
15552
|
onRowLevelGateUnexpressible: authorizeWarningCollection.onRowLevelGateUnexpressible,
|
|
15062
15553
|
onOwnRowLevelConditionCompiled: (sourceName, condition) => {
|
|
15063
15554
|
const struct = finalCompiledModelDef.contents[sourceName];
|
|
15064
|
-
if (!struct || !
|
|
15555
|
+
if (!struct || !isSourceDef5(struct))
|
|
15065
15556
|
return;
|
|
15066
15557
|
validateSourceLineGateGivenUsage(sourceName, struct, condition.refSummary, condition.e, finalCompiledModelDef, (cause, detail) => {
|
|
15067
15558
|
recordRowLevelGateRejected(cause);
|