@google-cloud/kcmd 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +203 -0
- package/README.md +266 -2
- package/build/ts/kcmd/cli.js +19578 -0
- package/build/ts/kcmd/gcp/alloydb.d.ts +12 -0
- package/build/ts/kcmd/gcp/alloydb.js +70 -0
- package/build/ts/kcmd/gcp/api.d.ts +29 -0
- package/build/ts/kcmd/gcp/api.js +162 -0
- package/build/ts/kcmd/gcp/biglake.d.ts +16 -0
- package/build/ts/kcmd/gcp/biglake.js +50 -0
- package/build/ts/kcmd/gcp/bigquery.d.ts +57 -0
- package/build/ts/kcmd/gcp/bigquery.js +134 -0
- package/build/ts/kcmd/gcp/context.d.ts +10 -0
- package/build/ts/kcmd/gcp/context.js +74 -0
- package/build/ts/kcmd/gcp/crm.d.ts +13 -0
- package/build/ts/kcmd/gcp/crm.js +91 -0
- package/build/ts/kcmd/gcp/dataplex.d.ts +147 -0
- package/build/ts/kcmd/gcp/dataplex.js +406 -0
- package/build/ts/kcmd/gcp/index.d.ts +3 -0
- package/build/ts/kcmd/gcp/index.js +21 -0
- package/build/ts/kcmd/gcp/spanner.d.ts +63 -0
- package/build/ts/kcmd/gcp/spanner.js +123 -0
- package/build/ts/kcmd/index.d.ts +7 -0
- package/build/ts/kcmd/index.js +46 -0
- package/build/ts/kcmd/layout.d.ts +16 -0
- package/build/ts/kcmd/layout.js +31 -0
- package/build/ts/kcmd/layouts/documents.d.ts +24 -0
- package/build/ts/kcmd/layouts/documents.js +264 -0
- package/build/ts/kcmd/layouts/okf.d.ts +31 -0
- package/build/ts/kcmd/layouts/okf.js +344 -0
- package/build/ts/kcmd/layouts/semantic-model.d.ts +61 -0
- package/build/ts/kcmd/layouts/semantic-model.js +542 -0
- package/build/ts/kcmd/layouts/standard.d.ts +13 -0
- package/build/ts/kcmd/layouts/standard.js +108 -0
- package/build/ts/kcmd/manifest.d.ts +34 -0
- package/build/ts/kcmd/manifest.js +262 -0
- package/build/ts/kcmd/metadata.d.ts +25 -0
- package/build/ts/kcmd/metadata.js +8 -0
- package/build/ts/kcmd/okf/catalog_residue.d.ts +14 -0
- package/build/ts/kcmd/okf/catalog_residue.js +124 -0
- package/build/ts/kcmd/okf/concept.d.ts +14 -0
- package/build/ts/kcmd/okf/concept.js +104 -0
- package/build/ts/kcmd/okf/concept_to_entry.d.ts +8 -0
- package/build/ts/kcmd/okf/concept_to_entry.js +254 -0
- package/build/ts/kcmd/okf/init_okf_workspace.d.ts +15 -0
- package/build/ts/kcmd/okf/init_okf_workspace.js +115 -0
- package/build/ts/kcmd/okf/links.d.ts +23 -0
- package/build/ts/kcmd/okf/links.js +468 -0
- package/build/ts/kcmd/okf/okf_metadata.d.ts +12 -0
- package/build/ts/kcmd/okf/okf_metadata.js +82 -0
- package/build/ts/kcmd/semantic/bigquery.d.ts +22 -0
- package/build/ts/kcmd/semantic/bigquery.js +525 -0
- package/build/ts/kcmd/semantic/binding.d.ts +3 -0
- package/build/ts/kcmd/semantic/binding.js +97 -0
- package/build/ts/kcmd/semantic/catalog_entry.d.ts +24 -0
- package/build/ts/kcmd/semantic/catalog_entry.js +35 -0
- package/build/ts/kcmd/semantic/converters/owl/convert.d.ts +26 -0
- package/build/ts/kcmd/semantic/converters/owl/convert.js +41 -0
- package/build/ts/kcmd/semantic/converters/owl/model.d.ts +98 -0
- package/build/ts/kcmd/semantic/converters/owl/model.js +30 -0
- package/build/ts/kcmd/semantic/converters/owl/parse.d.ts +11 -0
- package/build/ts/kcmd/semantic/converters/owl/parse.js +634 -0
- package/build/ts/kcmd/semantic/converters/owl/to_ir.d.ts +19 -0
- package/build/ts/kcmd/semantic/converters/owl/to_ir.js +390 -0
- package/build/ts/kcmd/semantic/deploy_bigquery.d.ts +20 -0
- package/build/ts/kcmd/semantic/deploy_bigquery.js +226 -0
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +129 -0
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +1396 -0
- package/build/ts/kcmd/semantic/deploy_spanner.d.ts +15 -0
- package/build/ts/kcmd/semantic/deploy_spanner.js +150 -0
- package/build/ts/kcmd/semantic/deployment_target.d.ts +93 -0
- package/build/ts/kcmd/semantic/deployment_target.js +379 -0
- package/build/ts/kcmd/semantic/graph_ddl.d.ts +34 -0
- package/build/ts/kcmd/semantic/graph_ddl.js +563 -0
- package/build/ts/kcmd/semantic/import_model.d.ts +50 -0
- package/build/ts/kcmd/semantic/import_model.js +1275 -0
- package/build/ts/kcmd/semantic/ir.d.ts +589 -0
- package/build/ts/kcmd/semantic/ir.js +247 -0
- package/build/ts/kcmd/semantic/kc_actions.d.ts +45 -0
- package/build/ts/kcmd/semantic/kc_actions.js +531 -0
- package/build/ts/kcmd/semantic/kc_constraints.d.ts +44 -0
- package/build/ts/kcmd/semantic/kc_constraints.js +279 -0
- package/build/ts/kcmd/semantic/kc_converter.d.ts +31 -0
- package/build/ts/kcmd/semantic/kc_converter.js +824 -0
- package/build/ts/kcmd/semantic/kc_custom_types.d.ts +70 -0
- package/build/ts/kcmd/semantic/kc_custom_types.js +828 -0
- package/build/ts/kcmd/semantic/kc_entries.d.ts +155 -0
- package/build/ts/kcmd/semantic/kc_entries.js +284 -0
- package/build/ts/kcmd/semantic/kc_ids.d.ts +103 -0
- package/build/ts/kcmd/semantic/kc_ids.js +191 -0
- package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +32 -0
- package/build/ts/kcmd/semantic/knowledge_catalog.js +1043 -0
- package/build/ts/kcmd/semantic/loader.d.ts +262 -0
- package/build/ts/kcmd/semantic/loader.js +3440 -0
- package/build/ts/kcmd/semantic/names.d.ts +10 -0
- package/build/ts/kcmd/semantic/names.js +85 -0
- package/build/ts/kcmd/semantic/osi_converter.d.ts +60 -0
- package/build/ts/kcmd/semantic/osi_converter.js +911 -0
- package/build/ts/kcmd/semantic/parameters.d.ts +18 -0
- package/build/ts/kcmd/semantic/parameters.js +152 -0
- package/build/ts/kcmd/semantic/pull_kc.d.ts +14 -0
- package/build/ts/kcmd/semantic/pull_kc.js +346 -0
- package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +54 -0
- package/build/ts/kcmd/semantic/resolve_inheritance.js +330 -0
- package/build/ts/kcmd/semantic/resolve_profiles.d.ts +250 -0
- package/build/ts/kcmd/semantic/resolve_profiles.js +1822 -0
- package/build/ts/kcmd/semantic/runtime/agent_tools.d.ts +93 -0
- package/build/ts/kcmd/semantic/runtime/agent_tools.js +243 -0
- package/build/ts/kcmd/semantic/runtime/dialect.d.ts +13 -0
- package/build/ts/kcmd/semantic/runtime/dialect.js +39 -0
- package/build/ts/kcmd/semantic/runtime/runtime.d.ts +94 -0
- package/build/ts/kcmd/semantic/runtime/runtime.js +318 -0
- package/build/ts/kcmd/semantic/runtime/store.d.ts +112 -0
- package/build/ts/kcmd/semantic/runtime/store.js +344 -0
- package/build/ts/kcmd/semantic/skills.d.ts +110 -0
- package/build/ts/kcmd/semantic/skills.js +697 -0
- package/build/ts/kcmd/semantic/source_dialect.d.ts +10 -0
- package/build/ts/kcmd/semantic/source_dialect.js +261 -0
- package/build/ts/kcmd/semantic/source_entries.d.ts +12 -0
- package/build/ts/kcmd/semantic/source_entries.js +282 -0
- package/build/ts/kcmd/semantic/spanner.d.ts +18 -0
- package/build/ts/kcmd/semantic/spanner.js +60 -0
- package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +17 -0
- package/build/ts/kcmd/semantic/sql_expr_utils.js +221 -0
- package/build/ts/kcmd/semantic/sql_identifiers.d.ts +6 -0
- package/build/ts/kcmd/semantic/sql_identifiers.js +267 -0
- package/build/ts/kcmd/semantic/sql_parser.d.ts +44 -0
- package/build/ts/kcmd/semantic/sql_parser.js +378 -0
- package/build/ts/kcmd/semantic/transpile.d.ts +74 -0
- package/build/ts/kcmd/semantic/transpile.js +563 -0
- package/build/ts/kcmd/semantic/validate.d.ts +35 -0
- package/build/ts/kcmd/semantic/validate.js +1899 -0
- package/build/ts/kcmd/semantic/yaml_options.d.ts +6 -0
- package/build/ts/kcmd/semantic/yaml_options.js +58 -0
- package/build/ts/kcmd/semantic/yaml_update.d.ts +26 -0
- package/build/ts/kcmd/semantic/yaml_update.js +782 -0
- package/build/ts/kcmd/snapshot.d.ts +33 -0
- package/build/ts/kcmd/snapshot.js +379 -0
- package/build/ts/kcmd/source.d.ts +31 -0
- package/build/ts/kcmd/source.js +119 -0
- package/build/ts/kcmd/sources/bq-dataset.d.ts +17 -0
- package/build/ts/kcmd/sources/bq-dataset.js +131 -0
- package/build/ts/kcmd/sources/entrygroup.d.ts +21 -0
- package/build/ts/kcmd/sources/entrygroup.js +83 -0
- package/build/ts/kcmd/sources/kb.d.ts +21 -0
- package/build/ts/kcmd/sources/kb.js +82 -0
- package/build/ts/kcmd/sources/semantic-model.d.ts +18 -0
- package/build/ts/kcmd/sources/semantic-model.js +48 -0
- package/build/ts/kcmd/sync.d.ts +35 -0
- package/build/ts/kcmd/sync.js +697 -0
- package/package.json +62 -4
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Creating a semantic runtime: the model as authored, merged with one binding
|
|
3
|
+
// profile, paired with the store the deployment that uses that profile names.
|
|
4
|
+
//
|
|
5
|
+
// A semantic model on disk is a declaration. A runtime is that declaration
|
|
6
|
+
// made operational -- bindings applied, inheritance resolved, a store to run
|
|
7
|
+
// against. Every caller that does anything with a model wants the pair, so the
|
|
8
|
+
// pair is what this hands back, rather than a model the caller then has to
|
|
9
|
+
// find a store for.
|
|
10
|
+
//
|
|
11
|
+
// Both halves were the CLI's private business until an agent needed them. An
|
|
12
|
+
// agent skill derives its tools from the same bound model `kcmd skills-generate`
|
|
13
|
+
// reads, against the same store, so it has to reach them the same way -- and a
|
|
14
|
+
// second loader that merges profiles slightly differently is a demo that passes
|
|
15
|
+
// while the product fails. The CLI calls this too, so there is one answer to
|
|
16
|
+
// "what does this scope say" rather than one per caller.
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.runtimeStoreError = runtimeStoreError;
|
|
52
|
+
exports.createSemanticRuntimes = createSemanticRuntimes;
|
|
53
|
+
exports.undeclaredProfileError = undeclaredProfileError;
|
|
54
|
+
const alloydb_1 = require("../../gcp/alloydb");
|
|
55
|
+
const context = __importStar(require("../../gcp/context"));
|
|
56
|
+
const dataplex_1 = require("../../gcp/dataplex");
|
|
57
|
+
const semantic_model_1 = require("../../layouts/semantic-model");
|
|
58
|
+
const snapshot_1 = require("../../snapshot");
|
|
59
|
+
const source_1 = require("../../source");
|
|
60
|
+
const loader_1 = require("../loader");
|
|
61
|
+
const resolve_inheritance_1 = require("../resolve_inheritance");
|
|
62
|
+
const resolve_profiles_1 = require("../resolve_profiles");
|
|
63
|
+
const source_dialect_1 = require("../source_dialect");
|
|
64
|
+
const sql_parser_1 = require("../sql_parser");
|
|
65
|
+
const store_1 = require("./store");
|
|
66
|
+
/**
|
|
67
|
+
* Why this runtime cannot execute SQL DML statements, or null when it can.
|
|
68
|
+
*
|
|
69
|
+
* Having a store is the whole requirement. Every backend a profile can deploy
|
|
70
|
+
* to -- Spanner, AlloyDB, BigQuery -- executes DML, so the kind of store is not
|
|
71
|
+
* a reason to refuse an action.
|
|
72
|
+
*/
|
|
73
|
+
function runtimeStoreError(runtime) {
|
|
74
|
+
if (!runtime.store) {
|
|
75
|
+
return runtime.storeError ??
|
|
76
|
+
`Model '${runtime.model.name}' has no store under profile '${runtime.profile}'.`;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* One runtime per model document in the scope, in discovery order.
|
|
82
|
+
*
|
|
83
|
+
* A lighter path than push's: nothing is pruned or validated for
|
|
84
|
+
* deployment, because running an action needs the model as authored rather
|
|
85
|
+
* than the subset a deployed graph can answer. Bindings stay optional, so a
|
|
86
|
+
* purely logical model yields a runtime with no store -- readable and
|
|
87
|
+
* inspectable, not runnable -- instead of an error that stops the scope.
|
|
88
|
+
*/
|
|
89
|
+
async function createSemanticRuntimes(options = {}) {
|
|
90
|
+
const base = options.path ?? '.';
|
|
91
|
+
const ctx = options.ctx ?? context.ApiContext.default();
|
|
92
|
+
const warn = options.onWarning ?? ((text) => console.warn(text));
|
|
93
|
+
let snapshot;
|
|
94
|
+
try {
|
|
95
|
+
snapshot = options.snapshot ?? await snapshot_1.CatalogSnapshot.fromPath(base, ctx);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return { error: `${err?.message ?? err}` };
|
|
99
|
+
}
|
|
100
|
+
if (snapshot.manifest.source.type !== source_1.Sources.SEMANTIC_MODEL) {
|
|
101
|
+
return { error: `'${base}' is not a semantic-model scope.` };
|
|
102
|
+
}
|
|
103
|
+
const layout = snapshot.layout;
|
|
104
|
+
const source = snapshot.manifest.source;
|
|
105
|
+
const profile = options.profile ?? resolve_profiles_1.DEFAULT_PROFILE;
|
|
106
|
+
const docs = layout.modelDocuments();
|
|
107
|
+
if (!docs.length)
|
|
108
|
+
return { error: 'no semantic model documents found.' };
|
|
109
|
+
// Reading a GOOGLE block profile checks its field bindings for SQL, which
|
|
110
|
+
// needs the SQL parser.
|
|
111
|
+
try {
|
|
112
|
+
await (0, sql_parser_1.loadSqlEngine)();
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
return { error: `${err?.message ?? err}` };
|
|
116
|
+
}
|
|
117
|
+
// The runtime reads every model's profiles first, as `kcmd push` does: its
|
|
118
|
+
// profile files, then the profiles a vanilla model carries in its GOOGLE
|
|
119
|
+
// block. It refuses the scope when
|
|
120
|
+
// profileDocuments rejects a file, such as a `<model>.profile.<name>.yaml`
|
|
121
|
+
// file whose `name:` does not match its file name, or when a GOOGLE block
|
|
122
|
+
// profile does not load. It also refuses it when a model declares a profile
|
|
123
|
+
// named `default` in any letter case.
|
|
124
|
+
const profilesByDoc = new Map();
|
|
125
|
+
// Each model's profile files alone, which an error applying one names.
|
|
126
|
+
const filesByDoc = new Map();
|
|
127
|
+
for (const doc of docs) {
|
|
128
|
+
let files;
|
|
129
|
+
let available;
|
|
130
|
+
try {
|
|
131
|
+
files = layout.profileDocuments(doc.name);
|
|
132
|
+
available = (0, resolve_profiles_1.declaredProfiles)(doc, files);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
return { error: err?.message ?? String(err) };
|
|
136
|
+
}
|
|
137
|
+
const reserved = (0, resolve_profiles_1.reservedProfileNameError)(doc.name, available);
|
|
138
|
+
if (reserved)
|
|
139
|
+
return { error: reserved };
|
|
140
|
+
profilesByDoc.set(doc.name, available);
|
|
141
|
+
filesByDoc.set(doc.name, files);
|
|
142
|
+
}
|
|
143
|
+
// A Cloud SQL table written as its resource name takes its dialect from its
|
|
144
|
+
// Knowledge Catalog entry, looked up once for the scope before anything
|
|
145
|
+
// loads. A table whose entry cannot be read stops the scope, as push does.
|
|
146
|
+
// A third-party table is not looked up, since no runtime reads one.
|
|
147
|
+
const lookup = await (0, source_dialect_1.sourceDialects)([...docs.map(d => d.text),
|
|
148
|
+
...[...filesByDoc.values()].flat().map(f => f.text)], new dataplex_1.CatalogClient(ctx), { thirdParty: false });
|
|
149
|
+
if (lookup.errors.length)
|
|
150
|
+
return { error: lookup.errors.join('\n') };
|
|
151
|
+
const load = { sourceDialects: lookup.dialects };
|
|
152
|
+
// The model files the inline bindings are used for, loaded together.
|
|
153
|
+
const inline = [];
|
|
154
|
+
// A profile applies to its model file loaded on its own, with its
|
|
155
|
+
// exclusions already marked.
|
|
156
|
+
const appliedByDoc = new Map();
|
|
157
|
+
// The warnings applying each profile gave.
|
|
158
|
+
const fileWarnings = new Map();
|
|
159
|
+
const defaultProject = source.project ?? ctx.project;
|
|
160
|
+
for (const doc of docs) {
|
|
161
|
+
if (profile === resolve_profiles_1.DEFAULT_PROFILE) {
|
|
162
|
+
inline.push({ name: doc.name, text: doc.text });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const available = profilesByDoc.get(doc.name) ?? [];
|
|
166
|
+
const res = (0, resolve_profiles_1.profileForDocument)(doc, available, profile, load);
|
|
167
|
+
if (res.kind === 'missing') {
|
|
168
|
+
return { error: (0, resolve_profiles_1.unknownProfileMessage)(profile, doc.name, res.declared) };
|
|
169
|
+
}
|
|
170
|
+
if (res.kind === 'error') {
|
|
171
|
+
const files = (filesByDoc.get(doc.name) ?? []).map(p => ({ ...p, fileName: (0, semantic_model_1.fileNameOf)(layout, doc.name, p.path) }));
|
|
172
|
+
return { error: (0, resolve_profiles_1.profileErrorMessage)(doc.name, res, profile, files) };
|
|
173
|
+
}
|
|
174
|
+
fileWarnings.set(doc.name, res.warnings);
|
|
175
|
+
appliedByDoc.set(doc.name, res.model);
|
|
176
|
+
}
|
|
177
|
+
const loaded = (0, loader_1.loadSemanticModels)(inline, { defaultProject, bindingOptional: true, ...load });
|
|
178
|
+
if (loaded.error)
|
|
179
|
+
return { error: loaded.error };
|
|
180
|
+
// In the order of the documents, as one load of them all gives. The load
|
|
181
|
+
// prefixes each warning with its document's name.
|
|
182
|
+
for (const doc of docs) {
|
|
183
|
+
const own = fileWarnings.get(doc.name) ??
|
|
184
|
+
loaded.warnings.filter(w => w.startsWith(`[${doc.name}] `));
|
|
185
|
+
for (const w of own)
|
|
186
|
+
warn(`Warning: ${w}`);
|
|
187
|
+
}
|
|
188
|
+
const loadedByDoc = new Map(loaded.models.map(m => [m.document, m.model]));
|
|
189
|
+
const models = docs.flatMap(doc => {
|
|
190
|
+
const model = appliedByDoc.get(doc.name) ?? loadedByDoc.get(doc.name);
|
|
191
|
+
return model ? [{ document: doc.name, model }] : [];
|
|
192
|
+
});
|
|
193
|
+
// A deployment that names a profile the model does not declare would be
|
|
194
|
+
// used by no profile. Push refuses it too.
|
|
195
|
+
for (const { document, model } of models) {
|
|
196
|
+
const declared = (profilesByDoc.get(document) ?? []).map(p => p.name);
|
|
197
|
+
const error = undeclaredProfileError(document, model, declared);
|
|
198
|
+
if (error)
|
|
199
|
+
return { error };
|
|
200
|
+
}
|
|
201
|
+
// Push then loads every profile file, whichever profile it deploys, and
|
|
202
|
+
// refuses one that does not load.
|
|
203
|
+
for (const doc of docs) {
|
|
204
|
+
for (const p of filesByDoc.get(doc.name) ?? []) {
|
|
205
|
+
try {
|
|
206
|
+
(0, resolve_profiles_1.loadProfileFile)(p.text, p.name, { skipActions: true });
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
if (!(err instanceof loader_1.SemanticModelError))
|
|
210
|
+
throw err;
|
|
211
|
+
const file = (0, semantic_model_1.fileNameOf)(layout, doc.name, p.path);
|
|
212
|
+
return { error: `[${file}] ${err.message}` };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// kcmd has no engine that reads a third-party table.
|
|
217
|
+
for (const { document, model } of models) {
|
|
218
|
+
const bound = (model.entities ?? []).find(loader_1.bindsThirdPartyTable);
|
|
219
|
+
if (bound) {
|
|
220
|
+
return {
|
|
221
|
+
error: `[${document}] model '${model.name}' binds entity '${bound.name}' to third-party table '${bound.authoredSource}', and kcmd has no engine for a ` +
|
|
222
|
+
`third-party table.`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (options.oneModel && models.length > 1) {
|
|
227
|
+
return {
|
|
228
|
+
error: typeof options.oneModel === 'function' ?
|
|
229
|
+
options.oneModel(models.length) :
|
|
230
|
+
`this scope holds ${models.length} models, which may name ` +
|
|
231
|
+
`different databases, so there is no single store. Narrow ` +
|
|
232
|
+
`the scope to one model.`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
// Inheritance is resolved for the same reason both push legs resolve it: an
|
|
236
|
+
// inherited field is a field, and every reader downstream reads
|
|
237
|
+
// `entity.fields`. The runtime is the reader where skipping it is unsafe
|
|
238
|
+
// rather than merely incomplete. It would not see a subtype's inherited
|
|
239
|
+
// `name`, so resolving a reference by name would report a row missing that
|
|
240
|
+
// is there; and it would not see an inherited key's TYPE, so the check that
|
|
241
|
+
// refuses a generated UUID for an Integer key would read the key as a
|
|
242
|
+
// String, pass, and let the store take the mismatch instead.
|
|
243
|
+
const runtimes = [];
|
|
244
|
+
const alloyDb = new alloydb_1.AlloyDbClient(ctx);
|
|
245
|
+
for (const { document, model: authored } of models) {
|
|
246
|
+
// An excluded metric is not available under the profile.
|
|
247
|
+
const excludedMetrics = new Set(authored.excludedMetrics ?? []);
|
|
248
|
+
if (excludedMetrics.size) {
|
|
249
|
+
authored.metrics =
|
|
250
|
+
(authored.metrics ?? []).filter(m => !excludedMetrics.has(m.name));
|
|
251
|
+
}
|
|
252
|
+
// The store is the one the profile's deployments name. Finding it reads
|
|
253
|
+
// only the deployment targets and the tables the model binds, which
|
|
254
|
+
// inheritance does not change.
|
|
255
|
+
const chosen = (0, store_1.resolveStore)(authored, profile);
|
|
256
|
+
// Resolving throws on an `extends` that names an entity the model does
|
|
257
|
+
// not declare, on a cycle, on a field inherited from two unrelated
|
|
258
|
+
// ancestors, and on two fields of one entity whose names differ only in
|
|
259
|
+
// case. The loader lets each through, and push reports it as an
|
|
260
|
+
// error. An AlloyDB database runs an inherited text as PostgreSQL, where
|
|
261
|
+
// `#` starts no comment.
|
|
262
|
+
let resolved;
|
|
263
|
+
try {
|
|
264
|
+
resolved = (0, resolve_inheritance_1.resolveInheritance)(authored, !('error' in chosen) && chosen.kind === 'alloydb' ? 'POSTGRES' :
|
|
265
|
+
undefined);
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
const message = `${err?.message ?? err}`;
|
|
269
|
+
return {
|
|
270
|
+
error: `[${document}] model '${authored.name}': ${message}${message.endsWith('.') ? '' : '.'}`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
for (const w of resolved.warnings)
|
|
274
|
+
warn(`Warning: ${w}`);
|
|
275
|
+
// An AlloyDB database is reached through its cluster's primary instance.
|
|
276
|
+
const runtime = {
|
|
277
|
+
model: resolved.model,
|
|
278
|
+
document,
|
|
279
|
+
profile,
|
|
280
|
+
entryGroup: source.entryGroup,
|
|
281
|
+
};
|
|
282
|
+
if ('error' in chosen) {
|
|
283
|
+
runtime.storeError = chosen.error;
|
|
284
|
+
if (chosen.named)
|
|
285
|
+
runtime.storeFailed = true;
|
|
286
|
+
if (chosen.rejected)
|
|
287
|
+
runtime.deploymentsRejected = true;
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
const store = await (0, store_1.withPrimaryInstance)(chosen, alloyDb);
|
|
291
|
+
if ('error' in store) {
|
|
292
|
+
runtime.storeError = store.error;
|
|
293
|
+
runtime.storeFailed = true;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
runtime.store = store;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
runtimes.push(runtime);
|
|
300
|
+
}
|
|
301
|
+
return runtimes;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Why `model` cannot be used because a deployment names a binding profile
|
|
305
|
+
* `declared` does not hold, or undefined when each names `default` or a
|
|
306
|
+
* declared profile. `document` is the model's document, for the message.
|
|
307
|
+
*/
|
|
308
|
+
function undeclaredProfileError(document, model, declared) {
|
|
309
|
+
for (const d of model.deployments ?? []) {
|
|
310
|
+
const profile = d.profile ?? resolve_profiles_1.DEFAULT_PROFILE;
|
|
311
|
+
if (profile === resolve_profiles_1.DEFAULT_PROFILE || declared.includes(profile))
|
|
312
|
+
continue;
|
|
313
|
+
return `[${document}] model '${model.name}': deployment '${d.name}' names unknown binding profile '${profile}'; ` +
|
|
314
|
+
(declared.length ? `defined profiles: ${declared.join(', ')}.` :
|
|
315
|
+
`no profiles are defined for this model.`);
|
|
316
|
+
}
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { AlloyDbClient } from '../../gcp/alloydb';
|
|
2
|
+
import { SemanticModel } from '../ir';
|
|
3
|
+
/** A Spanner database. An action's statements can write to it. */
|
|
4
|
+
export interface SpannerStore {
|
|
5
|
+
kind: 'spanner';
|
|
6
|
+
/** `projects/<p>/instances/<i>/databases/<d>`. The store's identity. */
|
|
7
|
+
name: string;
|
|
8
|
+
project: string;
|
|
9
|
+
instance: string;
|
|
10
|
+
database: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* An AlloyDB database. An action's statements can write to it. The instance a
|
|
14
|
+
* connection goes through may not be known yet.
|
|
15
|
+
*/
|
|
16
|
+
export interface AlloyDbDatabase {
|
|
17
|
+
kind: 'alloydb';
|
|
18
|
+
/**
|
|
19
|
+
* `projects/<p>/locations/<l>/clusters/<c>/databases/<d>`. The store's
|
|
20
|
+
* identity, which on AlloyDB reaches all the way down to the PostgreSQL
|
|
21
|
+
* database: one cluster holds several, and two of them are two stores.
|
|
22
|
+
*/
|
|
23
|
+
name: string;
|
|
24
|
+
project: string;
|
|
25
|
+
location: string;
|
|
26
|
+
cluster: string;
|
|
27
|
+
database: string;
|
|
28
|
+
}
|
|
29
|
+
/** An AlloyDB database, with the instance a connection goes through. */
|
|
30
|
+
export interface AlloyDbStore extends AlloyDbDatabase {
|
|
31
|
+
instance: string;
|
|
32
|
+
}
|
|
33
|
+
/** A BigQuery dataset. An action's statements can write to it. */
|
|
34
|
+
export interface BigQueryStore {
|
|
35
|
+
kind: 'bigquery';
|
|
36
|
+
/** `projects/<p>/datasets/<d>`. The store's identity. */
|
|
37
|
+
name: string;
|
|
38
|
+
project: string;
|
|
39
|
+
dataset: string;
|
|
40
|
+
}
|
|
41
|
+
export type Store = SpannerStore | AlloyDbStore | BigQueryStore;
|
|
42
|
+
/**
|
|
43
|
+
* The store resolveStore chooses. An AlloyDB database may still need its
|
|
44
|
+
* instance found with withPrimaryInstance.
|
|
45
|
+
*/
|
|
46
|
+
export type ChosenStore = SpannerStore | AlloyDbDatabase | BigQueryStore;
|
|
47
|
+
/**
|
|
48
|
+
* Why resolveStore gives no store.
|
|
49
|
+
*
|
|
50
|
+
* `named` is set when the model's deployments name a target and still no
|
|
51
|
+
* store results, so a caller that runs against the store fails. `rejected` is
|
|
52
|
+
* set with it when the deployments give no store whatever the model is used
|
|
53
|
+
* for: a target in no accepted form in any deployment, a GOOGLE block whose
|
|
54
|
+
* data is not valid JSON, or several targets for the profile. `named` alone
|
|
55
|
+
* means the model binds a table outside its Spanner or AlloyDB store.
|
|
56
|
+
*
|
|
57
|
+
* Neither is set when the profile names no store, unless a target is in no
|
|
58
|
+
* accepted form, or a GOOGLE block is not valid JSON. Neither is set either
|
|
59
|
+
* when a BigQuery model binds a table outside the graph's dataset.
|
|
60
|
+
*/
|
|
61
|
+
export interface StoreError {
|
|
62
|
+
error: string;
|
|
63
|
+
named?: boolean;
|
|
64
|
+
rejected?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The store a model runs against under binding profile `profile`, which
|
|
68
|
+
* defaults to `default`, the model file's own bindings. It is the one the
|
|
69
|
+
* deployments for that profile name: those that name it in `profile`, or that
|
|
70
|
+
* name none or `default` for `default`. A model with no `deployments` list
|
|
71
|
+
* names no profile for any of its targets, so they are all for `default`.
|
|
72
|
+
* With several targets, the store is the single operational one, Spanner or
|
|
73
|
+
* AlloyDB.
|
|
74
|
+
*
|
|
75
|
+
* Fails six ways:
|
|
76
|
+
*
|
|
77
|
+
* 1. A GOOGLE block's data is not valid JSON, so its targets cannot be read.
|
|
78
|
+
* 2. A target names no supported destination. Push checks the target of every
|
|
79
|
+
* deployment, so this fails the store under every profile.
|
|
80
|
+
* 3. No deployment uses the profile.
|
|
81
|
+
* 4. There are several targets and no single operational one, so which store
|
|
82
|
+
* is meant is ambiguous.
|
|
83
|
+
* 5. There is no target, so there is nothing to run against.
|
|
84
|
+
* 6. The model binds a table outside the store. For a Spanner or AlloyDB
|
|
85
|
+
* store, a write would land in the wrong place, and the error is named. A
|
|
86
|
+
* BigQuery graph may read tables in any dataset, so
|
|
87
|
+
* for a BigQuery store the error is not named.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolveStore(model: SemanticModel, profile?: string): ChosenStore | StoreError;
|
|
90
|
+
/**
|
|
91
|
+
* The targets binding profile `profile` uses, as resolveStore reads them: its
|
|
92
|
+
* deployments' targets, or every target in a GOOGLE block for `default` when
|
|
93
|
+
* the model has no `deployments` list. Empty when no deployment is for the
|
|
94
|
+
* profile. Throws when a GOOGLE block's data is not valid JSON.
|
|
95
|
+
*/
|
|
96
|
+
export declare function profileTargets(model: SemanticModel, profile: string): string[];
|
|
97
|
+
/**
|
|
98
|
+
* `store` with the instance a connection goes through. An AlloyDB target
|
|
99
|
+
* names a database in a cluster, so this asks the AlloyDB API for the
|
|
100
|
+
* cluster's primary instance. Fails, naming the cluster, when it has none,
|
|
101
|
+
* when the primary is not READY, or when the listing fails. Any other store
|
|
102
|
+
* comes back unchanged.
|
|
103
|
+
*/
|
|
104
|
+
export declare function withPrimaryInstance(store: ChosenStore, client: AlloyDbClient): Promise<Store | {
|
|
105
|
+
error: string;
|
|
106
|
+
}>;
|
|
107
|
+
/**
|
|
108
|
+
* How a store is written down for a reader: the resource it addresses, with
|
|
109
|
+
* the backend named ahead of it for everything but Spanner, which is the one
|
|
110
|
+
* an unprefixed line has always meant.
|
|
111
|
+
*/
|
|
112
|
+
export declare function storeLine(store: Store): string;
|