@malloy-publisher/server 0.0.210 → 0.0.211
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 +20 -3
- package/dist/app/assets/{EnvironmentPage-BRMCY9d8.js → EnvironmentPage-CuO6sty5.js} +1 -1
- package/dist/app/assets/HomePage-u3vxROIF.js +1 -0
- package/dist/app/assets/{MainPage-sZdUjUcu.js → MainPage-Bt2sYLbr.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-C_jQQUCJ.js → MaterializationsPage-B1rj61zs.js} +1 -1
- package/dist/app/assets/{ModelPage-Bh62OIEq.js → ModelPage-BpvONvSR.js} +1 -1
- package/dist/app/assets/{PackagePage-DJW4xjLp.js → PackagePage-DHNcwboW.js} +1 -1
- package/dist/app/assets/{RouteError-Vi5yGs_F.js → RouteError-D1vhLJvr.js} +1 -1
- package/dist/app/assets/{WorkbookPage-BvJMi21d.js → WorkbookPage-CZmud-yI.js} +1 -1
- package/dist/app/assets/{core-BbW0t3RQ.es-L-mZcOk3.js → core-XtBSnW2U.es-DE88sVsZ.js} +1 -1
- package/dist/app/assets/{index-CCcHdeew.js → index-BQdOB6m9.js} +1 -1
- package/dist/app/assets/{index-DuqTjxM_.js → index-BlnQtDZj.js} +137 -137
- package/dist/app/assets/{index-CNFX-CGL.js → index-CYf2akGH.js} +1 -1
- package/dist/app/assets/{index.umd-GgEb4WfT.js → index.umd-BdQ0R4hx.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +3 -0
- package/dist/server.mjs +150 -140
- package/package.json +1 -1
- package/src/materialization_metrics.spec.ts +84 -0
- package/src/materialization_metrics.ts +78 -119
- package/src/service/build_plan.spec.ts +84 -0
- package/src/service/build_plan.ts +53 -2
- package/src/service/materialization_service.spec.ts +126 -8
- package/src/service/materialization_service.ts +96 -83
- package/src/service/materialization_test_fixtures.ts +2 -0
- package/src/service/package.ts +14 -2
- package/src/service/persist_annotation_validation.spec.ts +77 -0
- package/src/service/persist_annotation_validation.ts +54 -0
- package/src/service/quoting.spec.ts +50 -0
- package/src/service/quoting.ts +37 -16
- package/src/utils.ts +5 -0
- package/dist/app/assets/HomePage-DQzgkiMI.js +0 -1
package/dist/server.mjs
CHANGED
|
@@ -237699,97 +237699,52 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
237699
237699
|
|
|
237700
237700
|
// src/materialization_metrics.ts
|
|
237701
237701
|
var import_api4 = __toESM(require_src(), 1);
|
|
237702
|
-
var
|
|
237703
|
-
|
|
237704
|
-
|
|
237705
|
-
|
|
237706
|
-
|
|
237707
|
-
|
|
237708
|
-
|
|
237709
|
-
|
|
237710
|
-
|
|
237711
|
-
|
|
237712
|
-
|
|
237713
|
-
|
|
237714
|
-
|
|
237715
|
-
|
|
237716
|
-
|
|
237717
|
-
|
|
237718
|
-
|
|
237719
|
-
|
|
237720
|
-
|
|
237721
|
-
|
|
237722
|
-
runDuration = meter2.createHistogram("publisher_materialization_run_duration_ms", {
|
|
237723
|
-
description: "Wall-clock duration of a materialization build. Label: mode ('auto'|'orchestrated').",
|
|
237724
|
-
unit: "ms"
|
|
237725
|
-
});
|
|
237726
|
-
}
|
|
237727
|
-
return { counter: runCounter, duration: runDuration };
|
|
237728
|
-
}
|
|
237702
|
+
var resetHooks = [];
|
|
237703
|
+
function lazyCounter(name, description) {
|
|
237704
|
+
let instrument2 = null;
|
|
237705
|
+
resetHooks.push(() => instrument2 = null);
|
|
237706
|
+
return () => instrument2 ??= import_api4.metrics.getMeter("publisher").createCounter(name, { description });
|
|
237707
|
+
}
|
|
237708
|
+
function lazyHistogram(name, description, unit) {
|
|
237709
|
+
let instrument2 = null;
|
|
237710
|
+
resetHooks.push(() => instrument2 = null);
|
|
237711
|
+
return () => instrument2 ??= import_api4.metrics.getMeter("publisher").createHistogram(name, { description, unit });
|
|
237712
|
+
}
|
|
237713
|
+
var runCounter = lazyCounter("publisher_materialization_runs_total", "Materialization builds completed. Labels: mode ('auto'|'orchestrated'), outcome ('success'|'failed'|'cancelled').");
|
|
237714
|
+
var runDuration = lazyHistogram("publisher_materialization_run_duration_ms", "Wall-clock duration of a materialization build. Label: mode ('auto'|'orchestrated').", "ms");
|
|
237715
|
+
var sourcesCounter = lazyCounter("publisher_materialization_sources_total", "Persist sources processed by a materialization run. Label: outcome ('built'|'reused').");
|
|
237716
|
+
var buildPlanComputeDuration = lazyHistogram("publisher_materialization_build_plan_compute_duration_ms", "Wall-clock duration of compiling a package's build plan (Package.buildPlan).", "ms");
|
|
237717
|
+
var autoLoadCounter = lazyCounter("publisher_materialization_auto_load_total", "Auto-run manifest auto-load attempts. Label: outcome ('success'|'failure').");
|
|
237718
|
+
var connectionDigestSkipCounter = lazyCounter("publisher_materialization_connection_digest_skipped_total", "Connection digests skipped during build-plan compile because the connection did not resolve.");
|
|
237719
|
+
var manifestBindCounter = lazyCounter("publisher_materialization_manifest_bind_total", "Manifest bind attempts. Label: outcome ('success'|'failure'|'timeout').");
|
|
237720
|
+
var sourceBuildDuration = lazyHistogram("publisher_materialization_source_build_duration_ms", "Wall-clock duration of building a single persist source.", "ms");
|
|
237721
|
+
var dropTablesCounter = lazyCounter("publisher_materialization_drop_tables_total", "Physical tables dropped on delete. Label: outcome ('success'|'failure').");
|
|
237729
237722
|
function recordMaterializationRun(mode, outcome, durationMs) {
|
|
237730
|
-
|
|
237731
|
-
|
|
237732
|
-
duration.record(durationMs, { mode });
|
|
237723
|
+
runCounter().add(1, { mode, outcome });
|
|
237724
|
+
runDuration().record(durationMs, { mode });
|
|
237733
237725
|
}
|
|
237734
237726
|
function recordSourcesOutcome(outcome, count) {
|
|
237735
237727
|
if (count <= 0)
|
|
237736
237728
|
return;
|
|
237737
|
-
|
|
237738
|
-
sourcesCounter = import_api4.metrics.getMeter("publisher").createCounter("publisher_materialization_sources_total", {
|
|
237739
|
-
description: "Persist sources processed by a materialization run. Label: outcome ('built'|'reused')."
|
|
237740
|
-
});
|
|
237741
|
-
}
|
|
237742
|
-
sourcesCounter.add(count, { outcome });
|
|
237729
|
+
sourcesCounter().add(count, { outcome });
|
|
237743
237730
|
}
|
|
237744
237731
|
function recordBuildPlanComputeDuration(durationMs) {
|
|
237745
|
-
|
|
237746
|
-
buildPlanComputeDuration = import_api4.metrics.getMeter("publisher").createHistogram("publisher_materialization_build_plan_compute_duration_ms", {
|
|
237747
|
-
description: "Wall-clock duration of compiling a package's build plan (Package.buildPlan).",
|
|
237748
|
-
unit: "ms"
|
|
237749
|
-
});
|
|
237750
|
-
}
|
|
237751
|
-
buildPlanComputeDuration.record(durationMs);
|
|
237732
|
+
buildPlanComputeDuration().record(durationMs);
|
|
237752
237733
|
}
|
|
237753
237734
|
function recordAutoLoadOutcome(outcome) {
|
|
237754
|
-
|
|
237755
|
-
autoLoadCounter = import_api4.metrics.getMeter("publisher").createCounter("publisher_materialization_auto_load_total", {
|
|
237756
|
-
description: "Auto-run manifest auto-load attempts. Label: outcome ('success'|'failure')."
|
|
237757
|
-
});
|
|
237758
|
-
}
|
|
237759
|
-
autoLoadCounter.add(1, { outcome });
|
|
237735
|
+
autoLoadCounter().add(1, { outcome });
|
|
237760
237736
|
}
|
|
237761
237737
|
function recordConnectionDigestSkipped() {
|
|
237762
|
-
|
|
237763
|
-
connectionDigestSkipCounter = import_api4.metrics.getMeter("publisher").createCounter("publisher_materialization_connection_digest_skipped_total", {
|
|
237764
|
-
description: "Connection digests skipped during build-plan compile because the connection did not resolve."
|
|
237765
|
-
});
|
|
237766
|
-
}
|
|
237767
|
-
connectionDigestSkipCounter.add(1);
|
|
237738
|
+
connectionDigestSkipCounter().add(1);
|
|
237768
237739
|
}
|
|
237769
237740
|
function recordManifestBind(outcome) {
|
|
237770
|
-
|
|
237771
|
-
manifestBindCounter = import_api4.metrics.getMeter("publisher").createCounter("publisher_materialization_manifest_bind_total", {
|
|
237772
|
-
description: "Manifest bind attempts. Label: outcome ('success'|'failure'|'timeout')."
|
|
237773
|
-
});
|
|
237774
|
-
}
|
|
237775
|
-
manifestBindCounter.add(1, { outcome });
|
|
237741
|
+
manifestBindCounter().add(1, { outcome });
|
|
237776
237742
|
}
|
|
237777
237743
|
function recordSourceBuildDuration(durationMs) {
|
|
237778
|
-
|
|
237779
|
-
sourceBuildDuration = import_api4.metrics.getMeter("publisher").createHistogram("publisher_materialization_source_build_duration_ms", {
|
|
237780
|
-
description: "Wall-clock duration of building a single persist source.",
|
|
237781
|
-
unit: "ms"
|
|
237782
|
-
});
|
|
237783
|
-
}
|
|
237784
|
-
sourceBuildDuration.record(durationMs);
|
|
237744
|
+
sourceBuildDuration().record(durationMs);
|
|
237785
237745
|
}
|
|
237786
237746
|
function recordDropTables(outcome) {
|
|
237787
|
-
|
|
237788
|
-
dropTablesCounter = import_api4.metrics.getMeter("publisher").createCounter("publisher_materialization_drop_tables_total", {
|
|
237789
|
-
description: "Physical tables dropped on delete. Label: outcome ('success'|'failure')."
|
|
237790
|
-
});
|
|
237791
|
-
}
|
|
237792
|
-
dropTablesCounter.add(1, { outcome });
|
|
237747
|
+
dropTablesCounter().add(1, { outcome });
|
|
237793
237748
|
}
|
|
237794
237749
|
|
|
237795
237750
|
// src/utils.ts
|
|
@@ -237808,6 +237763,9 @@ var URL_READER = {
|
|
|
237808
237763
|
function ignoreDotfiles(file) {
|
|
237809
237764
|
return path5.basename(file).startsWith(".");
|
|
237810
237765
|
}
|
|
237766
|
+
function errMessage(err) {
|
|
237767
|
+
return err instanceof Error ? err.message : String(err);
|
|
237768
|
+
}
|
|
237811
237769
|
|
|
237812
237770
|
// src/service/manifest_loader.ts
|
|
237813
237771
|
init_logger();
|
|
@@ -237889,6 +237847,7 @@ import {
|
|
|
237889
237847
|
} from "@malloydata/malloy";
|
|
237890
237848
|
|
|
237891
237849
|
// src/service/build_plan.ts
|
|
237850
|
+
init_constants();
|
|
237892
237851
|
init_logger();
|
|
237893
237852
|
|
|
237894
237853
|
// src/service/model.ts
|
|
@@ -239307,14 +239266,35 @@ function deriveColumns(persistSource) {
|
|
|
239307
239266
|
} catch (err) {
|
|
239308
239267
|
logger.warn("Failed to derive columns for persist source", {
|
|
239309
239268
|
sourceID: persistSource.sourceID,
|
|
239310
|
-
error:
|
|
239269
|
+
error: errMessage(err)
|
|
239311
239270
|
});
|
|
239312
239271
|
return [];
|
|
239313
239272
|
}
|
|
239314
239273
|
}
|
|
239274
|
+
function deriveAnnotationFields(persistSource) {
|
|
239275
|
+
const out = {};
|
|
239276
|
+
try {
|
|
239277
|
+
const tag = persistSource.annotations.parseAsTag("@").tag;
|
|
239278
|
+
for (const [key, value] of tag.entries()) {
|
|
239279
|
+
const text = value.text();
|
|
239280
|
+
if (text !== undefined)
|
|
239281
|
+
out[key] = text;
|
|
239282
|
+
}
|
|
239283
|
+
} catch {}
|
|
239284
|
+
return out;
|
|
239285
|
+
}
|
|
239315
239286
|
function flattenDependsOn(node) {
|
|
239316
239287
|
return node.dependsOn.map((d) => d.sourceID);
|
|
239317
239288
|
}
|
|
239289
|
+
function* iterGraphSources(graph, sources) {
|
|
239290
|
+
for (const level of graph.nodes) {
|
|
239291
|
+
for (const node of level) {
|
|
239292
|
+
const source = sources[node.sourceID];
|
|
239293
|
+
if (source)
|
|
239294
|
+
yield source;
|
|
239295
|
+
}
|
|
239296
|
+
}
|
|
239297
|
+
}
|
|
239318
239298
|
function computeBuildId(source, connectionDigests) {
|
|
239319
239299
|
return source.makeBuildId(connectionDigests[source.connectionName], source.getSQL());
|
|
239320
239300
|
}
|
|
@@ -239329,7 +239309,7 @@ async function resolvePackageConnections(pkg, names) {
|
|
|
239329
239309
|
map.set(name, await pkg.getMalloyConnection(name));
|
|
239330
239310
|
} catch (err) {
|
|
239331
239311
|
logger.warn(`Failed to resolve connection ${name}`, {
|
|
239332
|
-
error:
|
|
239312
|
+
error: errMessage(err)
|
|
239333
239313
|
});
|
|
239334
239314
|
}
|
|
239335
239315
|
}
|
|
@@ -239339,6 +239319,8 @@ async function compilePackageBuildPlan(pkg, signal) {
|
|
|
239339
239319
|
const allGraphs = [];
|
|
239340
239320
|
const allSources = {};
|
|
239341
239321
|
for (const modelPath of pkg.getModelPaths()) {
|
|
239322
|
+
if (!modelPath.endsWith(MODEL_FILE_SUFFIX))
|
|
239323
|
+
continue;
|
|
239342
239324
|
if (signal?.aborted)
|
|
239343
239325
|
throw new Error("Build cancelled");
|
|
239344
239326
|
const { runtime, modelURL, importBaseURL } = await Model.getModelRuntime(pkg.getPackagePath(), modelPath, pkg.getMalloyConfig());
|
|
@@ -239400,7 +239382,8 @@ function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames) {
|
|
|
239400
239382
|
dialect: source.dialectName,
|
|
239401
239383
|
buildId: computeBuildId(source, connectionDigests),
|
|
239402
239384
|
sql: source.getSQL(),
|
|
239403
|
-
columns: deriveColumns(source)
|
|
239385
|
+
columns: deriveColumns(source),
|
|
239386
|
+
annotationFields: deriveAnnotationFields(source)
|
|
239404
239387
|
};
|
|
239405
239388
|
}
|
|
239406
239389
|
return { graphs: wireGraphs, sources: wireSources };
|
|
@@ -239413,6 +239396,27 @@ async function computePackageBuildPlan(pkg, signal) {
|
|
|
239413
239396
|
return deriveBuildPlan(compiled.graphs, compiled.sources, compiled.connectionDigests);
|
|
239414
239397
|
}
|
|
239415
239398
|
|
|
239399
|
+
// src/service/persist_annotation_validation.ts
|
|
239400
|
+
init_errors();
|
|
239401
|
+
var PERSIST_LINE_PATTERN = /^\s*#@\s+persist\b/;
|
|
239402
|
+
var UNQUOTED_NAME_PATTERN = /\bname\s*=\s*(?!["'])/;
|
|
239403
|
+
function assertPersistNamesQuoted(modelSource, modelPath) {
|
|
239404
|
+
const offenders = [];
|
|
239405
|
+
for (const rawLine of modelSource.split(`
|
|
239406
|
+
`)) {
|
|
239407
|
+
if (!PERSIST_LINE_PATTERN.test(rawLine))
|
|
239408
|
+
continue;
|
|
239409
|
+
if (UNQUOTED_NAME_PATTERN.test(rawLine)) {
|
|
239410
|
+
offenders.push(rawLine.trim());
|
|
239411
|
+
}
|
|
239412
|
+
}
|
|
239413
|
+
if (offenders.length > 0) {
|
|
239414
|
+
throw new ModelCompilationError({
|
|
239415
|
+
message: `${modelPath}: persist annotation name must be quoted. Write a quoted ` + `value like name="engaged_events" (or a dialect table path such as ` + `name="my_dataset.engaged_events"), not a bare value — an unquoted ` + `persist name is dropped from the build plan, so the source would ` + `publish but never materialize. Offending annotation(s): ` + `${offenders.join("; ")}.`
|
|
239416
|
+
});
|
|
239417
|
+
}
|
|
239418
|
+
}
|
|
239419
|
+
|
|
239416
239420
|
// src/service/package.ts
|
|
239417
239421
|
class Package {
|
|
239418
239422
|
environmentName;
|
|
@@ -239556,6 +239560,10 @@ class Package {
|
|
|
239556
239560
|
}
|
|
239557
239561
|
const model = Model.fromSerialized(packageName, packagePath, malloyConfig, sm);
|
|
239558
239562
|
await model.validateRenderTags();
|
|
239563
|
+
if (sm.modelPath.endsWith(MODEL_FILE_SUFFIX)) {
|
|
239564
|
+
const modelSource = await fs6.readFile(path7.join(packagePath, sm.modelPath), "utf-8");
|
|
239565
|
+
assertPersistNamesQuoted(modelSource, sm.modelPath);
|
|
239566
|
+
}
|
|
239559
239567
|
models.set(sm.modelPath, model);
|
|
239560
239568
|
}
|
|
239561
239569
|
const endTime = performance.now();
|
|
@@ -239576,7 +239584,7 @@ class Package {
|
|
|
239576
239584
|
} catch (err) {
|
|
239577
239585
|
logger.warn(`Failed to compute build plan for package ${packageName}`, {
|
|
239578
239586
|
packageName,
|
|
239579
|
-
error:
|
|
239587
|
+
error: errMessage(err)
|
|
239580
239588
|
});
|
|
239581
239589
|
}
|
|
239582
239590
|
const invalidMsg = pkg.formatInvalidExplores();
|
|
@@ -248569,15 +248577,19 @@ init_logger();
|
|
|
248569
248577
|
import { Manifest } from "@malloydata/malloy";
|
|
248570
248578
|
|
|
248571
248579
|
// src/service/quoting.ts
|
|
248572
|
-
function
|
|
248580
|
+
function bareTableName(tableName) {
|
|
248573
248581
|
const lastDot = tableName.lastIndexOf(".");
|
|
248574
|
-
|
|
248575
|
-
|
|
248576
|
-
|
|
248577
|
-
|
|
248578
|
-
|
|
248582
|
+
return lastDot >= 0 ? tableName.substring(lastDot + 1) : tableName;
|
|
248583
|
+
}
|
|
248584
|
+
var BACKTICK_DIALECTS = new Set(["standardsql", "mysql", "databricks"]);
|
|
248585
|
+
function quoteIdentifier(identifier, dialect) {
|
|
248586
|
+
if (BACKTICK_DIALECTS.has(dialect)) {
|
|
248587
|
+
return "`" + identifier.replace(/`/g, "``") + "`";
|
|
248579
248588
|
}
|
|
248580
|
-
return
|
|
248589
|
+
return '"' + identifier.replace(/"/g, '""') + '"';
|
|
248590
|
+
}
|
|
248591
|
+
function quoteTablePath(tableName, dialect) {
|
|
248592
|
+
return tableName.split(".").map((segment) => quoteIdentifier(segment, dialect)).join(".");
|
|
248581
248593
|
}
|
|
248582
248594
|
|
|
248583
248595
|
// src/service/resolve_environment.ts
|
|
@@ -248596,11 +248608,7 @@ function stagingSuffix(buildId) {
|
|
|
248596
248608
|
return `_${buildId.substring(0, STAGING_BUILD_ID_LEN)}`;
|
|
248597
248609
|
}
|
|
248598
248610
|
function selfAssignTableName(persistSource) {
|
|
248599
|
-
|
|
248600
|
-
return persistSource.annotations.parseAsTag("@").tag.text("name") || persistSource.name;
|
|
248601
|
-
} catch {
|
|
248602
|
-
return persistSource.name;
|
|
248603
|
-
}
|
|
248611
|
+
return deriveAnnotationFields(persistSource).name || persistSource.name;
|
|
248604
248612
|
}
|
|
248605
248613
|
function outcomeFor(_err, signal) {
|
|
248606
248614
|
return signal?.aborted ? "cancelled" : "failed";
|
|
@@ -248668,7 +248676,7 @@ class MaterializationService {
|
|
|
248668
248676
|
}
|
|
248669
248677
|
const active2 = await this.repository.getActiveMaterialization(environmentId, packageName);
|
|
248670
248678
|
if (active2) {
|
|
248671
|
-
throw
|
|
248679
|
+
throw this.activeConflict(packageName, active2.id);
|
|
248672
248680
|
}
|
|
248673
248681
|
const forceRefresh = options.forceRefresh ?? false;
|
|
248674
248682
|
const metadata = {
|
|
@@ -248682,7 +248690,7 @@ class MaterializationService {
|
|
|
248682
248690
|
} catch (err) {
|
|
248683
248691
|
if (err instanceof DuplicateActiveMaterializationError) {
|
|
248684
248692
|
const winner = await this.repository.getActiveMaterialization(environmentId, packageName);
|
|
248685
|
-
throw
|
|
248693
|
+
throw this.activeConflict(packageName, winner?.id);
|
|
248686
248694
|
}
|
|
248687
248695
|
throw err;
|
|
248688
248696
|
}
|
|
@@ -248703,6 +248711,9 @@ class MaterializationService {
|
|
|
248703
248711
|
});
|
|
248704
248712
|
const startedAt = Date.now();
|
|
248705
248713
|
try {
|
|
248714
|
+
await this.repository.updateMaterialization(id, {
|
|
248715
|
+
startedAt: new Date(startedAt)
|
|
248716
|
+
});
|
|
248706
248717
|
const environmentId = await this.resolveEnvironmentId(environmentName);
|
|
248707
248718
|
const environment = await this.environmentStore.getEnvironment(environmentName, false);
|
|
248708
248719
|
const pkg = await environment.getPackage(packageName, false);
|
|
@@ -248753,29 +248764,24 @@ class MaterializationService {
|
|
|
248753
248764
|
const carried = {};
|
|
248754
248765
|
const seen = new Set;
|
|
248755
248766
|
for (const graph of compiled.graphs) {
|
|
248756
|
-
for (const
|
|
248757
|
-
|
|
248758
|
-
|
|
248759
|
-
|
|
248760
|
-
|
|
248761
|
-
|
|
248762
|
-
|
|
248763
|
-
|
|
248764
|
-
|
|
248765
|
-
|
|
248766
|
-
|
|
248767
|
-
const prior = priorEntries[buildId];
|
|
248768
|
-
if (prior && prior.physicalTableName) {
|
|
248769
|
-
carried[buildId] = prior;
|
|
248770
|
-
continue;
|
|
248771
|
-
}
|
|
248772
|
-
instructions.push({
|
|
248773
|
-
buildId,
|
|
248774
|
-
materializedTableId: `local-${buildId.substring(0, STAGING_BUILD_ID_LEN)}`,
|
|
248775
|
-
physicalTableName: selfAssignTableName(persistSource),
|
|
248776
|
-
realization: "COPY"
|
|
248777
|
-
});
|
|
248767
|
+
for (const persistSource of iterGraphSources(graph, compiled.sources)) {
|
|
248768
|
+
if (include && !include.has(persistSource.name))
|
|
248769
|
+
continue;
|
|
248770
|
+
const buildId = computeBuildId(persistSource, compiled.connectionDigests);
|
|
248771
|
+
if (seen.has(buildId))
|
|
248772
|
+
continue;
|
|
248773
|
+
seen.add(buildId);
|
|
248774
|
+
const prior = priorEntries[buildId];
|
|
248775
|
+
if (prior && prior.physicalTableName) {
|
|
248776
|
+
carried[buildId] = prior;
|
|
248777
|
+
continue;
|
|
248778
248778
|
}
|
|
248779
|
+
instructions.push({
|
|
248780
|
+
buildId,
|
|
248781
|
+
materializedTableId: `local-${buildId.substring(0, STAGING_BUILD_ID_LEN)}`,
|
|
248782
|
+
physicalTableName: selfAssignTableName(persistSource),
|
|
248783
|
+
realization: "COPY"
|
|
248784
|
+
});
|
|
248779
248785
|
}
|
|
248780
248786
|
}
|
|
248781
248787
|
return { instructions, carried };
|
|
@@ -248849,20 +248855,15 @@ class MaterializationService {
|
|
|
248849
248855
|
if (!connection) {
|
|
248850
248856
|
throw new BadRequestError(`Connection '${graph.connectionName}' not found`);
|
|
248851
248857
|
}
|
|
248852
|
-
for (const
|
|
248853
|
-
|
|
248854
|
-
|
|
248855
|
-
|
|
248856
|
-
|
|
248857
|
-
|
|
248858
|
-
|
|
248859
|
-
|
|
248860
|
-
|
|
248861
|
-
if (!instruction)
|
|
248862
|
-
continue;
|
|
248863
|
-
const entry = await this.buildOneSource(persistSource, instruction, connection, connectionDigests, manifest);
|
|
248864
|
-
entries[buildId] = entry;
|
|
248865
|
-
}
|
|
248858
|
+
for (const persistSource of iterGraphSources(graph, sources)) {
|
|
248859
|
+
if (signal.aborted)
|
|
248860
|
+
throw new Error("Build cancelled");
|
|
248861
|
+
const buildId = computeBuildId(persistSource, connectionDigests);
|
|
248862
|
+
const instruction = byBuildId.get(buildId);
|
|
248863
|
+
if (!instruction)
|
|
248864
|
+
continue;
|
|
248865
|
+
const entry = await this.buildOneSource(persistSource, instruction, connection, connectionDigests, manifest);
|
|
248866
|
+
entries[buildId] = entry;
|
|
248866
248867
|
}
|
|
248867
248868
|
}
|
|
248868
248869
|
return entries;
|
|
@@ -248874,22 +248875,26 @@ class MaterializationService {
|
|
|
248874
248875
|
buildManifest: manifest.buildManifest,
|
|
248875
248876
|
connectionDigests
|
|
248876
248877
|
});
|
|
248877
|
-
const
|
|
248878
|
+
const bareName = bareTableName(physicalTableName);
|
|
248878
248879
|
const stagingTableName = `${physicalTableName}${stagingSuffix(buildId)}`;
|
|
248880
|
+
const dialect = persistSource.dialectName;
|
|
248881
|
+
const quotedStaging = quoteTablePath(stagingTableName, dialect);
|
|
248882
|
+
const quotedPhysical = quoteTablePath(physicalTableName, dialect);
|
|
248883
|
+
const quotedBareName = quoteIdentifier(bareName, dialect);
|
|
248879
248884
|
const startTime = performance.now();
|
|
248880
|
-
await connection.runSQL(`DROP TABLE IF EXISTS ${
|
|
248885
|
+
await connection.runSQL(`DROP TABLE IF EXISTS ${quotedStaging}`);
|
|
248881
248886
|
try {
|
|
248882
|
-
await connection.runSQL(`CREATE TABLE ${
|
|
248883
|
-
await connection.runSQL(`DROP TABLE IF EXISTS ${
|
|
248884
|
-
await connection.runSQL(`ALTER TABLE ${
|
|
248887
|
+
await connection.runSQL(`CREATE TABLE ${quotedStaging} AS (${buildSQL})`);
|
|
248888
|
+
await connection.runSQL(`DROP TABLE IF EXISTS ${quotedPhysical}`);
|
|
248889
|
+
await connection.runSQL(`ALTER TABLE ${quotedStaging} RENAME TO ${quotedBareName}`);
|
|
248885
248890
|
} catch (err) {
|
|
248886
248891
|
try {
|
|
248887
|
-
await connection.runSQL(`DROP TABLE IF EXISTS ${
|
|
248892
|
+
await connection.runSQL(`DROP TABLE IF EXISTS ${quotedStaging}`);
|
|
248888
248893
|
} catch (cleanupErr) {
|
|
248889
248894
|
logger.warn("Failed to clean up staging table after a failed build; physical leak", {
|
|
248890
248895
|
stagingTableName,
|
|
248891
248896
|
connectionName: persistSource.connectionName,
|
|
248892
|
-
cleanupError:
|
|
248897
|
+
cleanupError: errMessage(cleanupErr)
|
|
248893
248898
|
});
|
|
248894
248899
|
}
|
|
248895
248900
|
throw err;
|
|
@@ -248969,8 +248974,9 @@ class MaterializationService {
|
|
|
248969
248974
|
connection = await pkg.getMalloyConnection(connectionName);
|
|
248970
248975
|
connectionCache.set(connectionName, connection);
|
|
248971
248976
|
}
|
|
248972
|
-
|
|
248973
|
-
await connection.runSQL(`DROP TABLE IF EXISTS ${physicalTableName
|
|
248977
|
+
const dialect = connection.dialectName;
|
|
248978
|
+
await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(physicalTableName, dialect)}`);
|
|
248979
|
+
await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.buildId)}`, dialect)}`);
|
|
248974
248980
|
recordDropTables("success");
|
|
248975
248981
|
logger.info("Dropped materialized table on delete", {
|
|
248976
248982
|
materializationId: m.id,
|
|
@@ -248983,7 +248989,7 @@ class MaterializationService {
|
|
|
248983
248989
|
materializationId: m.id,
|
|
248984
248990
|
physicalTableName,
|
|
248985
248991
|
connectionName,
|
|
248986
|
-
error:
|
|
248992
|
+
error: errMessage(err)
|
|
248987
248993
|
});
|
|
248988
248994
|
}
|
|
248989
248995
|
}
|
|
@@ -249001,6 +249007,10 @@ class MaterializationService {
|
|
|
249001
249007
|
metadata
|
|
249002
249008
|
});
|
|
249003
249009
|
}
|
|
249010
|
+
activeConflict(packageName, activeId) {
|
|
249011
|
+
const suffix = activeId ? ` (${activeId})` : "";
|
|
249012
|
+
return new MaterializationConflictError(`Package ${packageName} already has an active materialization${suffix}`);
|
|
249013
|
+
}
|
|
249004
249014
|
recordRun(mode, outcome, startedAtMs) {
|
|
249005
249015
|
recordMaterializationRun(mode, outcome, Date.now() - startedAtMs);
|
|
249006
249016
|
}
|
|
@@ -249008,7 +249018,7 @@ class MaterializationService {
|
|
|
249008
249018
|
const abortController = new AbortController;
|
|
249009
249019
|
this.runningAbortControllers.set(id, abortController);
|
|
249010
249020
|
run(abortController.signal).catch(async (err) => {
|
|
249011
|
-
const message =
|
|
249021
|
+
const message = errMessage(err);
|
|
249012
249022
|
const next = abortController.signal.aborted ? "CANCELLED" : "FAILED";
|
|
249013
249023
|
try {
|
|
249014
249024
|
await this.repository.updateMaterialization(id, {
|
|
@@ -249020,7 +249030,7 @@ class MaterializationService {
|
|
|
249020
249030
|
logger.error("Failed to record materialization failure", {
|
|
249021
249031
|
materializationId: id,
|
|
249022
249032
|
originalError: message,
|
|
249023
|
-
transitionError:
|
|
249033
|
+
transitionError: errMessage(transitionErr)
|
|
249024
249034
|
});
|
|
249025
249035
|
}
|
|
249026
249036
|
}).finally(() => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
recordConnectionDigestSkipped,
|
|
5
|
+
recordDropTables,
|
|
6
|
+
recordMaterializationRun,
|
|
7
|
+
recordSourcesOutcome,
|
|
8
|
+
resetMaterializationTelemetryForTesting,
|
|
9
|
+
} from "./materialization_metrics";
|
|
10
|
+
import {
|
|
11
|
+
startMetricsHarness,
|
|
12
|
+
type MetricsHarness,
|
|
13
|
+
} from "./test_helpers/metrics_harness";
|
|
14
|
+
|
|
15
|
+
describe("materialization_metrics", () => {
|
|
16
|
+
let harness: MetricsHarness;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
harness = await startMetricsHarness();
|
|
20
|
+
// Drop cached instruments so they re-bind to this test's provider.
|
|
21
|
+
resetMaterializationTelemetryForTesting();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
resetMaterializationTelemetryForTesting();
|
|
26
|
+
await harness.shutdown();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("counts runs labeled by mode and outcome", async () => {
|
|
30
|
+
recordMaterializationRun("auto", "success", 10);
|
|
31
|
+
recordMaterializationRun("auto", "success", 20);
|
|
32
|
+
recordMaterializationRun("orchestrated", "failed", 5);
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
await harness.collectCounter("publisher_materialization_runs_total", {
|
|
36
|
+
mode: "auto",
|
|
37
|
+
outcome: "success",
|
|
38
|
+
}),
|
|
39
|
+
).toBe(2);
|
|
40
|
+
expect(
|
|
41
|
+
await harness.collectCounter("publisher_materialization_runs_total", {
|
|
42
|
+
mode: "orchestrated",
|
|
43
|
+
outcome: "failed",
|
|
44
|
+
}),
|
|
45
|
+
).toBe(1);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("adds the source count labeled by outcome, ignoring non-positive counts", async () => {
|
|
49
|
+
recordSourcesOutcome("built", 3);
|
|
50
|
+
recordSourcesOutcome("reused", 2);
|
|
51
|
+
recordSourcesOutcome("built", 0); // guarded: no emission
|
|
52
|
+
|
|
53
|
+
expect(
|
|
54
|
+
await harness.collectCounter(
|
|
55
|
+
"publisher_materialization_sources_total",
|
|
56
|
+
{ outcome: "built" },
|
|
57
|
+
),
|
|
58
|
+
).toBe(3);
|
|
59
|
+
expect(
|
|
60
|
+
await harness.collectCounter(
|
|
61
|
+
"publisher_materialization_sources_total",
|
|
62
|
+
{ outcome: "reused" },
|
|
63
|
+
),
|
|
64
|
+
).toBe(2);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("counts drop-table outcomes and connection-digest skips", async () => {
|
|
68
|
+
recordDropTables("success");
|
|
69
|
+
recordDropTables("failure");
|
|
70
|
+
recordConnectionDigestSkipped();
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
await harness.collectCounter(
|
|
74
|
+
"publisher_materialization_drop_tables_total",
|
|
75
|
+
{ outcome: "failure" },
|
|
76
|
+
),
|
|
77
|
+
).toBe(1);
|
|
78
|
+
expect(
|
|
79
|
+
await harness.collectCounter(
|
|
80
|
+
"publisher_materialization_connection_digest_skipped_total",
|
|
81
|
+
),
|
|
82
|
+
).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
});
|