@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,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Resolves entity-level inheritance (`extends`) into self-contained entities.
|
|
3
|
+
//
|
|
4
|
+
// The IR records inheritance AS DECLARED: an entity's `extends` names its
|
|
5
|
+
// direct supertypes, and its `fields` are only the ones it declares itself (see
|
|
6
|
+
// ir.Entity.extends). That is faithful but not directly consumable -- an
|
|
7
|
+
// emitter that wants to publish a subclass as a first-class node needs the
|
|
8
|
+
// supertype's fields present on the child, and the full ancestor set (not just
|
|
9
|
+
// the direct parents) to label it. This pass performs that resolution,
|
|
10
|
+
// mirroring the shape of ./transpile: it `structuredClone`s the model and NEVER
|
|
11
|
+
// mutates its input, returning a resolved clone plus warnings.
|
|
12
|
+
//
|
|
13
|
+
// What it does, per entity:
|
|
14
|
+
// - Fields FLOW DOWN (flattening). The entity's own fields come first
|
|
15
|
+
// (declared order), then every field it inherits. An inherited field is
|
|
16
|
+
// MERGED down the chain that declares it: the farthest declaration supplies
|
|
17
|
+
// the definition (type, label, description, ai_context, ...), and each
|
|
18
|
+
// nearer redeclaration, the entity's own included, supplies only its
|
|
19
|
+
// binding. A subtype that rebinds a field to another column therefore keeps
|
|
20
|
+
// everything its parent said about it.
|
|
21
|
+
// - `extends` is expanded from the direct parents to the full, de-duplicated
|
|
22
|
+
// TRANSITIVE ancestor set, ordered nearest-first (a diamond lists each
|
|
23
|
+
// ancestor once). An emitter reads labels straight off this list.
|
|
24
|
+
//
|
|
25
|
+
// What it deliberately does NOT do:
|
|
26
|
+
// - Keys are NOT inherited: each entity keeps its own KEY (a node table is
|
|
27
|
+
// identified by its own grain, not its supertype's).
|
|
28
|
+
// - Relationships (edges) are NOT inherited: a subclass does not gain its
|
|
29
|
+
// supertype's edges. Only node properties flow down.
|
|
30
|
+
// - It does not classify abstract vs concrete or drop anything -- that is the
|
|
31
|
+
// consuming leg's concern (see graph_ddl.ts). This pass is leg-agnostic.
|
|
32
|
+
//
|
|
33
|
+
// Errors, all thrown: a cycle in `extends`; a parent that is not an entity in
|
|
34
|
+
// the model; a field inherited from two ancestors that each declare it with
|
|
35
|
+
// neither extending the other, since nothing says which definition wins; and
|
|
36
|
+
// two fields of one entity, declared or inherited, whose names differ only in
|
|
37
|
+
// case. A diamond is not the third case: two paths reaching ONE declaration
|
|
38
|
+
// resolve to it, and an exact redeclaration of an inherited field rebinds it.
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.InheritanceError = void 0;
|
|
41
|
+
exports.declaredConceptFields = declaredConceptFields;
|
|
42
|
+
exports.resolveInheritance = resolveInheritance;
|
|
43
|
+
const ir_1 = require("./ir");
|
|
44
|
+
const sql_expr_utils_1 = require("./sql_expr_utils");
|
|
45
|
+
/**
|
|
46
|
+
* Why inheritance cannot be resolved. `unknown-parent` can be a pruning
|
|
47
|
+
* artefact, since a profile can drop a supertype whole; a cycle or an
|
|
48
|
+
* ambiguously inherited field cannot, because pruning only removes things.
|
|
49
|
+
*/
|
|
50
|
+
class InheritanceError extends Error {
|
|
51
|
+
kind;
|
|
52
|
+
constructor(kind, message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.kind = kind;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.InheritanceError = InheritanceError;
|
|
58
|
+
function declaredConceptFields(model) {
|
|
59
|
+
const inherits = (model.entities ?? []).some(e => e.extends?.length);
|
|
60
|
+
const entities = (inherits ? resolveInheritance(model).model : model).entities ?? [];
|
|
61
|
+
const concepts = new Map();
|
|
62
|
+
for (const e of entities) {
|
|
63
|
+
concepts.set(e.name, {
|
|
64
|
+
kind: 'entity',
|
|
65
|
+
fields: new Map((e.fields ?? []).map(f => [f.name, f])),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
for (const r of model.relationships ?? []) {
|
|
69
|
+
if (concepts.has(r.name))
|
|
70
|
+
continue;
|
|
71
|
+
concepts.set(r.name, {
|
|
72
|
+
kind: 'relationship',
|
|
73
|
+
fields: new Map((r.association?.fields ?? []).map(f => [f.name, f])),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return concepts;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns a clone of `model` with every entity's `extends` expanded to its full
|
|
80
|
+
* transitive ancestor set and its `fields` flattened to include inherited
|
|
81
|
+
* fields, less any a profile excludes on that entity. The input is never
|
|
82
|
+
* mutated. An entity with no `extends` keeps its own fields, so a model with no
|
|
83
|
+
* inheritance and no exclusions resolves to an equivalent model.
|
|
84
|
+
* `targetDialect` is the SQL that an inherited field's target text and its
|
|
85
|
+
* ANSI_SQL entries are read in to find their comments. It is BigQuery unless
|
|
86
|
+
* given, as the graph generators read the target text.
|
|
87
|
+
*
|
|
88
|
+
* Reading an inherited field's SQL needs the SQL parser, so call
|
|
89
|
+
* `loadSqlEngine()` first. Without it, this throws the parser's plain Error.
|
|
90
|
+
*/
|
|
91
|
+
function resolveInheritance(model, targetDialect) {
|
|
92
|
+
const clone = structuredClone(model);
|
|
93
|
+
const warnings = [];
|
|
94
|
+
const entities = clone.entities ?? [];
|
|
95
|
+
const byName = new Map(entities.map(e => [e.name, e]));
|
|
96
|
+
// Snapshot the AS-DECLARED direct parents and own fields BEFORE mutating any
|
|
97
|
+
// entity, so resolution reads a stable view regardless of iteration order
|
|
98
|
+
// (each entity is flattened from originals, not from an already-flattened
|
|
99
|
+
// ancestor).
|
|
100
|
+
const directParents = new Map();
|
|
101
|
+
const ownFields = new Map();
|
|
102
|
+
for (const e of entities) {
|
|
103
|
+
directParents.set(e.name, [...(e.extends ?? [])]);
|
|
104
|
+
ownFields.set(e.name, [...e.fields]);
|
|
105
|
+
}
|
|
106
|
+
// Every entity's ancestors, nearest-first, computed once: the merge below
|
|
107
|
+
// needs to know whether one declaring ancestor descends from another.
|
|
108
|
+
const ancestorsOf = new Map();
|
|
109
|
+
for (const e of entities) {
|
|
110
|
+
ancestorsOf.set(e.name, transitiveAncestors(e.name, byName, directParents));
|
|
111
|
+
}
|
|
112
|
+
for (const entity of entities) {
|
|
113
|
+
const ancestors = ancestorsOf.get(entity.name) ?? [];
|
|
114
|
+
// Which ancestors declare each inherited field name, nearest-first. An
|
|
115
|
+
// ancestor that redeclares a field it inherits and sets nothing on it
|
|
116
|
+
// changes nothing, so it is not a declarer. Counting it would make a
|
|
117
|
+
// diamond ambiguous over a line that has no effect.
|
|
118
|
+
const declarers = new Map();
|
|
119
|
+
const inheritedByLower = new Map();
|
|
120
|
+
for (const anc of ancestors) {
|
|
121
|
+
for (const f of ownFields.get(anc) ?? []) {
|
|
122
|
+
const lower = f.name.toLowerCase();
|
|
123
|
+
const prior = inheritedByLower.get(lower);
|
|
124
|
+
if (prior && prior.name !== f.name) {
|
|
125
|
+
throw new InheritanceError('ambiguous-field', `entity '${entity.name}' inherits field '${prior.name}' from '${prior.from}' and field '${f.name}' from '${anc}' (case-insensitive collision); field names on an ` +
|
|
126
|
+
`entity must be unique ignoring case`);
|
|
127
|
+
}
|
|
128
|
+
if (setsNothing(f) &&
|
|
129
|
+
(ancestorsOf.get(anc) ?? [])
|
|
130
|
+
.some(a => ownFields.get(a)?.some(g => g.name === f.name))) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (!prior)
|
|
134
|
+
inheritedByLower.set(lower, { name: f.name, from: anc });
|
|
135
|
+
const list = declarers.get(f.name) ?? [];
|
|
136
|
+
if (!list.includes(anc))
|
|
137
|
+
list.push(anc);
|
|
138
|
+
declarers.set(f.name, list);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const [name, list] of declarers) {
|
|
142
|
+
requireOneChain(entity.name, name, list, ancestorsOf);
|
|
143
|
+
}
|
|
144
|
+
// Own fields first, then every inherited field, each merged down its
|
|
145
|
+
// declaring chain. A subtype's redeclaration of an inherited field merges
|
|
146
|
+
// over that chain too, contributing only its binding.
|
|
147
|
+
const seenField = new Set();
|
|
148
|
+
const flattened = [];
|
|
149
|
+
for (const f of ownFields.get(entity.name) ?? []) {
|
|
150
|
+
const inherited = inheritedByLower.get(f.name.toLowerCase());
|
|
151
|
+
if (inherited && inherited.name !== f.name) {
|
|
152
|
+
throw new InheritanceError('ambiguous-field', `entity '${entity.name}' declares field '${f.name}', which collides ignoring case with field '${inherited.name}' inherited from '${inherited.from}'; field names on an entity must be unique ` +
|
|
153
|
+
`ignoring case`);
|
|
154
|
+
}
|
|
155
|
+
if (seenField.has(f.name))
|
|
156
|
+
continue; // a self-duplicate; keep the first
|
|
157
|
+
seenField.add(f.name);
|
|
158
|
+
const chain = declarers.get(f.name);
|
|
159
|
+
flattened.push(chain ? mergeBinding(mergeChain(f.name, chain, ownFields, ancestorsOf, targetDialect), f) :
|
|
160
|
+
f);
|
|
161
|
+
}
|
|
162
|
+
for (const [name, chain] of declarers) {
|
|
163
|
+
if (seenField.has(name))
|
|
164
|
+
continue;
|
|
165
|
+
seenField.add(name);
|
|
166
|
+
flattened.push(mergeChain(name, chain, ownFields, ancestorsOf, targetDialect));
|
|
167
|
+
}
|
|
168
|
+
// A field the binding profile excludes on this entity is left off it.
|
|
169
|
+
// Descendants read the snapshot of declared fields, so they still inherit
|
|
170
|
+
// it.
|
|
171
|
+
const excluded = new Set(entity.excludedFields ?? []);
|
|
172
|
+
entity.fields = excluded.size ?
|
|
173
|
+
flattened.filter(f => !excluded.has(f.name)) :
|
|
174
|
+
flattened;
|
|
175
|
+
// Expand `extends` to the resolved ancestor list. Drop the key when the
|
|
176
|
+
// entity has no parents, so a consumer reads `extends` as the exact label
|
|
177
|
+
// set.
|
|
178
|
+
if (ancestors.length) {
|
|
179
|
+
entity.extends = ancestors;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
delete entity.extends;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { model: clone, warnings: [...new Set(warnings)] };
|
|
186
|
+
}
|
|
187
|
+
// Clones an ancestor's field for a descendant, rewriting its expression to be
|
|
188
|
+
// TABLE-LOCAL. A field expression written on the ancestor as `<Ancestor>.col`
|
|
189
|
+
// means "the `col` column of the ancestor's own table"; inherited onto a
|
|
190
|
+
// descendant -- whose backing table carries the same column, since inherited
|
|
191
|
+
// fields must physically exist on the child -- it must reference the
|
|
192
|
+
// DESCENDANT's column, so the ancestor's own-name qualifier is stripped.
|
|
193
|
+
// Without this the descendant would render `<Ancestor>.col AS col` while the
|
|
194
|
+
// ancestor renders `col`, and an emitter that reuses one label across both
|
|
195
|
+
// tables (e.g. BigQuery's shared labels) would see two different definitions of
|
|
196
|
+
// the same property and reject the graph.
|
|
197
|
+
//
|
|
198
|
+
// This composes with (does not duplicate) the emitter's own qualifier
|
|
199
|
+
// stripping: this pass normalizes an inherited expression INTO the child's
|
|
200
|
+
// frame once here, and the emitter's table-local renderer then strips the
|
|
201
|
+
// child's own qualifier uniformly for every field (see
|
|
202
|
+
// graph_ddl.renderFieldPropertyCore). Both route through the same
|
|
203
|
+
// `stripQualifier` primitive; they differ only in which qualifier they remove.
|
|
204
|
+
//
|
|
205
|
+
// Comments come out first. A quote inside a comment would otherwise pair with a
|
|
206
|
+
// string literal's, and the qualifier between them would stay. The target text
|
|
207
|
+
// is read in `targetDialect`, BigQuery unless given, and so are the ANSI_SQL
|
|
208
|
+
// entries, which a database of that dialect runs as its own SQL. The imported
|
|
209
|
+
// text is read in its imported dialect, and every other dialect entry in its
|
|
210
|
+
// own. A vendor entry that holds the same text as the target does not change
|
|
211
|
+
// how the target is read.
|
|
212
|
+
function localizeInheritedField(field, ancestor, targetDialect) {
|
|
213
|
+
const clone = structuredClone(field);
|
|
214
|
+
const local = (text, dialect) => (0, sql_expr_utils_1.stripQualifier)((0, sql_expr_utils_1.sqlWithoutComments)(text, dialect), ancestor);
|
|
215
|
+
if (clone.expression !== undefined) {
|
|
216
|
+
clone.expression = local(clone.expression, targetDialect);
|
|
217
|
+
}
|
|
218
|
+
if (clone.importedExpression !== undefined) {
|
|
219
|
+
clone.importedExpression =
|
|
220
|
+
local(clone.importedExpression, clone.importedDialect);
|
|
221
|
+
}
|
|
222
|
+
if (clone.dialects) {
|
|
223
|
+
const read = (d) => d === 'ANSI_SQL' ? targetDialect : d;
|
|
224
|
+
clone.dialects = clone.dialects.map(d => ({ ...d, expression: local(d.expression, read(d.dialect)) }));
|
|
225
|
+
}
|
|
226
|
+
return clone;
|
|
227
|
+
}
|
|
228
|
+
// `over` laid onto `base`: every property `over` sets wins, and every one it
|
|
229
|
+
// leaves unset comes from `base`. When `over` carries SQL, `base`'s binding is
|
|
230
|
+
// dropped whole, so an expression and a dialect list from two different
|
|
231
|
+
// declarations never end up on one field. When it carries none, its binding
|
|
232
|
+
// properties (a lone `stringForm` or `importedDialect`) are ignored and
|
|
233
|
+
// `base`'s binding stands.
|
|
234
|
+
function mergeBinding(base, over) {
|
|
235
|
+
const merged = structuredClone(base);
|
|
236
|
+
const bound = (0, ir_1.isFieldBound)(over);
|
|
237
|
+
if (bound) {
|
|
238
|
+
for (const k of ir_1.FIELD_BINDING_KEYS)
|
|
239
|
+
delete merged[k];
|
|
240
|
+
}
|
|
241
|
+
const binding = new Set(ir_1.FIELD_BINDING_KEYS);
|
|
242
|
+
for (const [k, v] of Object.entries(over)) {
|
|
243
|
+
if (v === undefined || (!bound && binding.has(k)))
|
|
244
|
+
continue;
|
|
245
|
+
merged[k] = structuredClone(v);
|
|
246
|
+
}
|
|
247
|
+
return merged;
|
|
248
|
+
}
|
|
249
|
+
// The field `name` as inherited down `chain`, the ancestors that declare it.
|
|
250
|
+
// The chain is folded in order of descent, root first: the root's declaration
|
|
251
|
+
// is the definition, and each descendant's lays its binding over it.
|
|
252
|
+
// Breadth-first
|
|
253
|
+
// distance is not that order when two paths to the entity differ in length, so
|
|
254
|
+
// the chain is sorted by how many ancestors each member has; requireOneChain
|
|
255
|
+
// has already checked that every member descends from the next. Every
|
|
256
|
+
// expression is localized to the declaring ancestor's frame first, its target
|
|
257
|
+
// text read in `targetDialect`.
|
|
258
|
+
function mergeChain(name, chain, ownFields, ancestorsOf, targetDialect) {
|
|
259
|
+
const depth = (n) => (ancestorsOf.get(n) ?? []).length;
|
|
260
|
+
let merged;
|
|
261
|
+
for (const anc of [...chain].sort((a, b) => depth(a) - depth(b))) {
|
|
262
|
+
const own = (ownFields.get(anc) ?? []).find(f => f.name === name);
|
|
263
|
+
const local = localizeInheritedField(own, anc, targetDialect);
|
|
264
|
+
merged = merged ? mergeBinding(merged, local) : local;
|
|
265
|
+
}
|
|
266
|
+
return merged;
|
|
267
|
+
}
|
|
268
|
+
// Whether a field declaration carries neither a binding nor any part of a
|
|
269
|
+
// definition. Redeclaring an inherited field this way is a no-op.
|
|
270
|
+
function setsNothing(field) {
|
|
271
|
+
return !(0, ir_1.isFieldBound)(field) &&
|
|
272
|
+
ir_1.FIELD_DEFINITION_KEYS.every(k => field[k] === undefined);
|
|
273
|
+
}
|
|
274
|
+
// Throws unless every ancestor in `declaring` lies on one line of descent, so
|
|
275
|
+
// that one declaration plainly overrides another. Two ancestors that each
|
|
276
|
+
// declare the field, with neither extending the other, leave its definition
|
|
277
|
+
// ambiguous.
|
|
278
|
+
function requireOneChain(entity, field, declaring, ancestorsOf) {
|
|
279
|
+
for (let i = 0; i < declaring.length; i++) {
|
|
280
|
+
for (let j = i + 1; j < declaring.length; j++) {
|
|
281
|
+
const a = declaring[i], b = declaring[j];
|
|
282
|
+
const related = (ancestorsOf.get(a) ?? []).includes(b) ||
|
|
283
|
+
(ancestorsOf.get(b) ?? []).includes(a);
|
|
284
|
+
if (!related) {
|
|
285
|
+
throw new InheritanceError('ambiguous-field', `entity '${entity}' inherits field '${field}' from '${a}' and ` +
|
|
286
|
+
`from '${b}', and neither extends the other, so nothing says ` +
|
|
287
|
+
`which wins; declare or rebind '${field}' on only one of them`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Computes the de-duplicated transitive ancestor set for `start`, ORDERED
|
|
293
|
+
// NEAREST-FIRST (breadth-first by distance): direct parents in declared order,
|
|
294
|
+
// then grandparents, and so on. Nearest-first is what makes field flattening's
|
|
295
|
+
// "nearest definition wins" correct -- a field defined by both a direct parent
|
|
296
|
+
// and a grandparent must resolve to the direct parent's. Direct parents are
|
|
297
|
+
// read from the pre-mutation snapshot so resolution is order-independent.
|
|
298
|
+
//
|
|
299
|
+
// A parent edge that points back to `start` is a cycle, and a parent that is
|
|
300
|
+
// not an entity in the model is a typo; both are hard errors. A cycle that does
|
|
301
|
+
// not pass through `start` is caught when an entity on it is resolved, and
|
|
302
|
+
// every entity is. A node re-reached through a second path (a diamond) is
|
|
303
|
+
// simply skipped -- it is already included at its nearest distance.
|
|
304
|
+
function transitiveAncestors(start, byName, directParents) {
|
|
305
|
+
const result = [];
|
|
306
|
+
const seen = new Set([start]);
|
|
307
|
+
// Queue of (child that declared the edge, parent) so a cycle error can name
|
|
308
|
+
// the offending child. Seeded with `start`'s direct parents in declared
|
|
309
|
+
// order.
|
|
310
|
+
const queue = (directParents.get(start) ?? []).map(name => ({ from: start, name }));
|
|
311
|
+
while (queue.length) {
|
|
312
|
+
const { from, name } = queue.shift();
|
|
313
|
+
if (name === start) {
|
|
314
|
+
throw new InheritanceError('cycle', `entity '${from}' extends '${name}', which is already a supertype ` +
|
|
315
|
+
`on this chain; 'extends' must not form a cycle`);
|
|
316
|
+
}
|
|
317
|
+
if (!byName.has(name)) {
|
|
318
|
+
throw new InheritanceError('unknown-parent', `entity '${from}' extends unknown entity '${name}'; it is not ` +
|
|
319
|
+
`defined in the model`);
|
|
320
|
+
}
|
|
321
|
+
if (seen.has(name))
|
|
322
|
+
continue; // diamond: already included at its nearest
|
|
323
|
+
seen.add(name);
|
|
324
|
+
result.push(name);
|
|
325
|
+
for (const gp of directParents.get(name) ?? []) {
|
|
326
|
+
queue.push({ from: name, name: gp });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { Entity, Field, ProfileSpec, SemanticModel } from './ir';
|
|
2
|
+
import { LoadOptions } from './loader';
|
|
3
|
+
export declare const DEFAULT_PROFILE = "default";
|
|
4
|
+
export declare function plainCopy(value: unknown): any;
|
|
5
|
+
export interface AvailabilityReport {
|
|
6
|
+
profile: string;
|
|
7
|
+
unboundFields: string[];
|
|
8
|
+
excludedFields: string[];
|
|
9
|
+
droppedEntities: {
|
|
10
|
+
name: string;
|
|
11
|
+
reason: string;
|
|
12
|
+
}[];
|
|
13
|
+
droppedMetrics: {
|
|
14
|
+
name: string;
|
|
15
|
+
reason: string;
|
|
16
|
+
field?: string;
|
|
17
|
+
}[];
|
|
18
|
+
droppedRelationships: {
|
|
19
|
+
name: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
}[];
|
|
22
|
+
droppedActions: {
|
|
23
|
+
name: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
}[];
|
|
26
|
+
droppedFields: {
|
|
27
|
+
name: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
}[];
|
|
30
|
+
warnings: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns a clone of `model` reduced to what `profileName` can answer: unbound
|
|
34
|
+
* and excluded fields taken off their entities, with every field that reads
|
|
35
|
+
* one of them, directly or through other fields; every excluded metric and
|
|
36
|
+
* every metric that reads an unavailable field dropped; and every action with
|
|
37
|
+
* no executor dropped. A field counts whether the entity declares or inherits
|
|
38
|
+
* it. What a field or metric reads comes from the text `dialect` evaluates
|
|
39
|
+
* (see textsFor). `dialect` defaults to the dialect the model's sources
|
|
40
|
+
* imply. When the sources are in systems of different dialects, or one names
|
|
41
|
+
* no system, there is no dialect and every text counts.
|
|
42
|
+
* The input is never mutated. The report names each dropped block
|
|
43
|
+
* and the unbound field that stops it, so a caller can state the withheld
|
|
44
|
+
* coverage. A model with nothing unbound resolves unchanged.
|
|
45
|
+
*/
|
|
46
|
+
export declare function pruneUnavailable(model: SemanticModel, profileName: string, dialect?: string | undefined): {
|
|
47
|
+
model: SemanticModel;
|
|
48
|
+
report: AvailabilityReport;
|
|
49
|
+
};
|
|
50
|
+
export declare function withoutExclusions(model: SemanticModel): SemanticModel;
|
|
51
|
+
export declare function resolvedEntities(model: SemanticModel): Entity[];
|
|
52
|
+
export declare function inheritedNamesOf(model: SemanticModel, entityName: string, byName?: Map<string, Entity>): Set<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Reads a profile file into the IR's `ProfileSpec`. Throws on anything a
|
|
55
|
+
* profile may not say: an unknown key at any level, a value of the wrong shape,
|
|
56
|
+
* a field in both `fields` and `fields_exclude`, `"*"` inside a
|
|
57
|
+
* `metrics_exclude` list, or a `name` other than `profileName`. A field
|
|
58
|
+
* expression and an action's executor are read by the loader's own readers, so
|
|
59
|
+
* each follows the rules it follows in a model file. A source is kept as
|
|
60
|
+
* written, and applyProfile reads it with the loader's reader too. A field
|
|
61
|
+
* expression's short form means `ANSI_SQL` in either flavor. Actions are out of
|
|
62
|
+
* scope for the preview, so nothing more is checked. With `skipActions`, the
|
|
63
|
+
* profile's actions are not read.
|
|
64
|
+
*/
|
|
65
|
+
export declare function loadProfileFile(text: string, profileName: string, opts?: {
|
|
66
|
+
skipActions?: boolean;
|
|
67
|
+
}): ProfileSpec;
|
|
68
|
+
export type DialectLoad = Pick<LoadOptions, 'sourceDialects'> & {
|
|
69
|
+
sourceDatabases?: ReadonlyMap<string, string>;
|
|
70
|
+
};
|
|
71
|
+
/** How a profile's values are read, matching the load of the model. */
|
|
72
|
+
export interface ApplyProfileOptions {
|
|
73
|
+
load?: Pick<LoadOptions, 'dialect' | 'sourceDialects'>;
|
|
74
|
+
warnings?: string[];
|
|
75
|
+
from?: {
|
|
76
|
+
document: string;
|
|
77
|
+
warnings: string[];
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returns a copy of `model` with `profile` applied: each named entity's
|
|
82
|
+
* source, its keys where the profile states them, its field bindings, as a
|
|
83
|
+
* binding-only redeclaration for a field the entity inherits, and its
|
|
84
|
+
* `fields_exclude`; each relationship's join columns; `metrics_exclude`; and
|
|
85
|
+
* each action's executor, which is taken as given, since actions are out of
|
|
86
|
+
* preview scope. A field a profile binds keeps every dialect text the profile
|
|
87
|
+
* wrote. An excluded field is marked in `excludedFields`, and loses its
|
|
88
|
+
* binding when no entity inherits it. The input is never mutated.
|
|
89
|
+
*
|
|
90
|
+
* Throws, naming the profile, when the profile repeats a name, leaves a field
|
|
91
|
+
* entry with nothing to bind, both binds and excludes a field, puts "*" inside
|
|
92
|
+
* a list, names something the model does not declare, binds an abstract
|
|
93
|
+
* entity, states one of a relationship's join column lists without the
|
|
94
|
+
* other or gives both lists with different lengths, gives a source or
|
|
95
|
+
* expression the loader's readers reject, or is applied to a model that
|
|
96
|
+
* declares a `sql` executor, which belongs in each profile.
|
|
97
|
+
*/
|
|
98
|
+
export declare function applyProfile(model: SemanticModel, profile: ProfileSpec, opts?: Omit<ApplyProfileOptions, 'from'>): SemanticModel;
|
|
99
|
+
/**
|
|
100
|
+
* Reads the profile file `text` and applies it to `model`, or returns the
|
|
101
|
+
* error that stops it, naming the profile. The warnings are those of the
|
|
102
|
+
* model file's load (`opts.from`), less the ones about values the profile
|
|
103
|
+
* replaces, followed by those for the values it sets, each prefixed with the
|
|
104
|
+
* document name.
|
|
105
|
+
*/
|
|
106
|
+
export declare function applyProfileFile(model: SemanticModel, text: string, profileName: string, opts?: Omit<ApplyProfileOptions, 'warnings'>): {
|
|
107
|
+
model: SemanticModel;
|
|
108
|
+
warnings: string[];
|
|
109
|
+
} | {
|
|
110
|
+
error: string;
|
|
111
|
+
};
|
|
112
|
+
/** A profile applied to a model document loaded on its own. */
|
|
113
|
+
export interface AppliedProfile {
|
|
114
|
+
model: SemanticModel;
|
|
115
|
+
warnings: string[];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A profile a model declares: the text of a profile file, or a profile already
|
|
119
|
+
* read, such as one a vanilla model carries in its GOOGLE block.
|
|
120
|
+
*/
|
|
121
|
+
export type DeclaredProfile = {
|
|
122
|
+
name: string;
|
|
123
|
+
text: string;
|
|
124
|
+
} | {
|
|
125
|
+
name: string;
|
|
126
|
+
spec: ProfileSpec;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* The profiles the model document `doc` declares: `files`, its profile files,
|
|
130
|
+
* then the profiles a vanilla model carries in its GOOGLE block, by name, as
|
|
131
|
+
* the layout orders profile files. Push, `kcmd profiles` and the runtime all
|
|
132
|
+
* read them here, so they agree. Throws SemanticModelError, naming the
|
|
133
|
+
* document as a load does, when googleBlockProfiles throws, such as on block
|
|
134
|
+
* profiles a load rejects.
|
|
135
|
+
*/
|
|
136
|
+
export declare function declaredProfiles(doc: {
|
|
137
|
+
name: string;
|
|
138
|
+
text: string;
|
|
139
|
+
}, files: Array<{
|
|
140
|
+
name: string;
|
|
141
|
+
text: string;
|
|
142
|
+
}>): DeclaredProfile[];
|
|
143
|
+
/** A profile applied to one model document. */
|
|
144
|
+
export type DocumentProfile = ({
|
|
145
|
+
kind: 'file';
|
|
146
|
+
} & AppliedProfile) | {
|
|
147
|
+
kind: 'error';
|
|
148
|
+
error: string;
|
|
149
|
+
stage: 'load' | 'apply';
|
|
150
|
+
} | {
|
|
151
|
+
kind: 'missing';
|
|
152
|
+
declared: string[];
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Finds the profile `profileName` among `profiles`, the ones the model
|
|
156
|
+
* document `doc` declares, and applies it, as push, `kcmd profiles` and the
|
|
157
|
+
* runtime do. It applies to the document loaded on its own.
|
|
158
|
+
*/
|
|
159
|
+
export declare function profileForDocument(doc: {
|
|
160
|
+
name: string;
|
|
161
|
+
text: string;
|
|
162
|
+
}, profiles: DeclaredProfile[], profileName: string, load?: DialectLoad): DocumentProfile;
|
|
163
|
+
/**
|
|
164
|
+
* The error a command gives when a profile cannot be applied to the model
|
|
165
|
+
* document `docName`, whose profile files are `profileFiles`. An error applying
|
|
166
|
+
* a profile file names that file, as the profile checks do. An error applying
|
|
167
|
+
* a profile from a GOOGLE block names the model document.
|
|
168
|
+
*/
|
|
169
|
+
export declare function profileErrorMessage(docName: string, res: Extract<DocumentProfile, {
|
|
170
|
+
kind: 'error';
|
|
171
|
+
}>, profileName: string, profileFiles: Array<{
|
|
172
|
+
name: string;
|
|
173
|
+
text: string;
|
|
174
|
+
fileName: string;
|
|
175
|
+
}>): string;
|
|
176
|
+
/** The error for a profile name a model document does not declare. */
|
|
177
|
+
export declare function unknownProfileMessage(profileName: string, model: string, declared: string[]): string;
|
|
178
|
+
/**
|
|
179
|
+
* The error for a model document `docName` whose `profiles` include one named
|
|
180
|
+
* `default` in any letter case. Push refuses every graph push of such a
|
|
181
|
+
* model, and the runtime refuses the model too. Undefined when no profile has
|
|
182
|
+
* that name.
|
|
183
|
+
*/
|
|
184
|
+
export declare function reservedProfileNameError(docName: string, profiles: {
|
|
185
|
+
name: string;
|
|
186
|
+
}[]): string | undefined;
|
|
187
|
+
export declare function profileSpecOf(doc: any, profileName: string): ProfileSpec;
|
|
188
|
+
export declare function expressionSources(f: Pick<Field, 'expression' | 'dialects' | 'importedExpression' | 'importedDialect'>): Array<{
|
|
189
|
+
text: string;
|
|
190
|
+
dialect: string;
|
|
191
|
+
}>;
|
|
192
|
+
declare function textsFor(f: Pick<Field, 'expression' | 'dialects' | 'importedExpression' | 'importedDialect'>, dialect: string | undefined): Array<{
|
|
193
|
+
text: string;
|
|
194
|
+
dialect: string;
|
|
195
|
+
}>;
|
|
196
|
+
export declare function noTextFor(f: Parameters<typeof textsFor>[0], dialects: string[]): boolean;
|
|
197
|
+
export interface TableDialects {
|
|
198
|
+
name: string;
|
|
199
|
+
dialects: string[];
|
|
200
|
+
unread?: boolean;
|
|
201
|
+
}
|
|
202
|
+
export declare function tableDialects(entity: Pick<Entity, 'abstract' | 'authoredSource' | 'dataSource' | 'sourceDialect'>, withUnread: boolean): TableDialects | undefined;
|
|
203
|
+
export declare function fieldsOnTables(model: SemanticModel, withUnread?: boolean): Array<{
|
|
204
|
+
entity: Entity;
|
|
205
|
+
field: Field;
|
|
206
|
+
table: TableDialects;
|
|
207
|
+
}>;
|
|
208
|
+
export declare const IMPORT_TRANSPILE_COMMAND = "kcmd import <file> --input first-party --flavor <ossie|google> --transpile";
|
|
209
|
+
export declare function importTranspiles(f: Parameters<typeof textsFor>[0], dialect: string | undefined): boolean;
|
|
210
|
+
export declare function sourcesDialect(model: {
|
|
211
|
+
entities?: ReadonlyArray<Pick<Entity, 'abstract' | 'authoredSource' | 'dataSource' | 'sourceDialect'>>;
|
|
212
|
+
}): string | undefined;
|
|
213
|
+
/** What the caller of validateProfileCompleteness checks itself. */
|
|
214
|
+
export interface CompletenessOptions extends DialectLoad {
|
|
215
|
+
ownSqlOnly?: {
|
|
216
|
+
dialect?: string;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Returns one message per reason `profile` cannot be deployed against
|
|
221
|
+
* `baseModel`, or an empty list. Checks a single profile: nothing unknown,
|
|
222
|
+
* every field it binds holding SQL on each entity that inherits it, every
|
|
223
|
+
* concrete entity bound and all in one database, its own keys where the model
|
|
224
|
+
* file states keys, every relationship bound, every field the model file
|
|
225
|
+
* leaves unbound accounted for, every field whose expression reads a column
|
|
226
|
+
* restated or excluded, and exclusions closed under dependency, metrics
|
|
227
|
+
* included. The check that join columns cover a key of the target is in
|
|
228
|
+
* validateProfileConsistency. An expression the SQL parser cannot read
|
|
229
|
+
* is skipped by the rules that read references, with a message in `warnings`.
|
|
230
|
+
* `opts` says what the caller checks itself, so that nothing is reported
|
|
231
|
+
* twice.
|
|
232
|
+
*/
|
|
233
|
+
export declare function validateProfileCompleteness(baseModel: SemanticModel, profile: ProfileSpec, warnings?: string[], opts?: CompletenessOptions): string[];
|
|
234
|
+
/**
|
|
235
|
+
* Returns one message for each binding, the model file's included, whose
|
|
236
|
+
* `to_columns` for a relationship cover no primary or unique key of its
|
|
237
|
+
* target, or an empty list. A superset of a key covers it (see coversKey).
|
|
238
|
+
* Keys and join columns are compared as written. A binding whose key or join
|
|
239
|
+
* columns are not all column names (isColumnName) is left out. `applied` names
|
|
240
|
+
* the profile the push applies. The "covers no key" check then leaves out that
|
|
241
|
+
* profile's join, or the model file's when the profile states none.
|
|
242
|
+
*/
|
|
243
|
+
export declare function validateProfileConsistency(baseModel: SemanticModel, profiles: ProfileSpec[], applied?: string): string[];
|
|
244
|
+
export declare function inheritanceErrorOf(model: SemanticModel): string | undefined;
|
|
245
|
+
export declare function columnBinding(model: SemanticModel, entityName: string, field: string, dialect?: string): {
|
|
246
|
+
owner: string;
|
|
247
|
+
column: string;
|
|
248
|
+
} | undefined;
|
|
249
|
+
export declare function statesKey(primaryKey: string[] | undefined, uniqueKeys: string[][] | undefined): boolean;
|
|
250
|
+
export {};
|