@google-cloud/kcmd 0.0.0-stage → 0.2.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 +264 -2
- package/build/ts/kcmd/cli.js +20252 -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 +18 -0
- package/build/ts/kcmd/gcp/biglake.js +59 -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 +73 -0
- package/build/ts/kcmd/gcp/spanner.js +126 -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 +386 -0
- package/build/ts/kcmd/layouts/semantic-model.d.ts +74 -0
- package/build/ts/kcmd/layouts/semantic-model.js +621 -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 +110 -0
- package/build/ts/kcmd/okf/concept_to_entry.d.ts +10 -0
- package/build/ts/kcmd/okf/concept_to_entry.js +281 -0
- package/build/ts/kcmd/okf/init_okf_workspace.d.ts +15 -0
- package/build/ts/kcmd/okf/init_okf_workspace.js +113 -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 +99 -0
- package/build/ts/kcmd/semantic/bigquery.d.ts +22 -0
- package/build/ts/kcmd/semantic/bigquery.js +469 -0
- package/build/ts/kcmd/semantic/binding.d.ts +3 -0
- package/build/ts/kcmd/semantic/binding.js +92 -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 +104 -0
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +1367 -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 +44 -0
- package/build/ts/kcmd/semantic/graph_ddl.js +656 -0
- package/build/ts/kcmd/semantic/import_model.d.ts +72 -0
- package/build/ts/kcmd/semantic/import_model.js +1698 -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 +37 -0
- package/build/ts/kcmd/semantic/kc_converter.js +839 -0
- package/build/ts/kcmd/semantic/kc_custom_types.d.ts +63 -0
- package/build/ts/kcmd/semantic/kc_custom_types.js +751 -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 +102 -0
- package/build/ts/kcmd/semantic/kc_ids.js +188 -0
- package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +29 -0
- package/build/ts/kcmd/semantic/knowledge_catalog.js +960 -0
- package/build/ts/kcmd/semantic/loader.d.ts +258 -0
- package/build/ts/kcmd/semantic/loader.js +3576 -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 +910 -0
- package/build/ts/kcmd/semantic/parameters.d.ts +19 -0
- package/build/ts/kcmd/semantic/parameters.js +171 -0
- package/build/ts/kcmd/semantic/pull_kc.d.ts +10 -0
- package/build/ts/kcmd/semantic/pull_kc.js +272 -0
- package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +59 -0
- package/build/ts/kcmd/semantic/resolve_inheritance.js +346 -0
- package/build/ts/kcmd/semantic/resolve_profiles.d.ts +291 -0
- package/build/ts/kcmd/semantic/resolve_profiles.js +2003 -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 +88 -0
- package/build/ts/kcmd/semantic/runtime/runtime.js +301 -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 +17 -0
- package/build/ts/kcmd/semantic/source_dialect.js +311 -0
- package/build/ts/kcmd/semantic/source_entries.d.ts +12 -0
- package/build/ts/kcmd/semantic/source_entries.js +341 -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 +23 -0
- package/build/ts/kcmd/semantic/sql_expr_utils.js +394 -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 +47 -0
- package/build/ts/kcmd/semantic/sql_parser.js +400 -0
- package/build/ts/kcmd/semantic/transpile.d.ts +76 -0
- package/build/ts/kcmd/semantic/transpile.js +581 -0
- package/build/ts/kcmd/semantic/validate.d.ts +70 -0
- package/build/ts/kcmd/semantic/validate.js +2266 -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 +37 -0
- package/build/ts/kcmd/snapshot.js +386 -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 +734 -0
- package/package.json +62 -4
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Adds translated target-dialect SQL entries to a Semantic Model's expressions
|
|
3
|
+
// by transpiling vendor SQL or ANSI_SQL that double-quotes column names.
|
|
4
|
+
//
|
|
5
|
+
// The loader (./loader) keeps every entry in `dialects` (and records
|
|
6
|
+
// `stringForm`), while still picking a target/canonical `expression` valid
|
|
7
|
+
// against the target (GoogleSQL/ANSI) and, when the source supplied a vendor
|
|
8
|
+
// variant, the first vendor SQL in `importedExpression` (+ `importedDialect`).
|
|
9
|
+
// When a node carries ONLY vendor entries (with no target or `ANSI_SQL` entry)
|
|
10
|
+
// or carries an `ANSI_SQL` entry that quotes column names with double quotes
|
|
11
|
+
// (which BigQuery reads as string literals), this pass translates that source
|
|
12
|
+
// SQL into the target dialect, appends `{dialect: target, expression: sql}` to
|
|
13
|
+
// `dialects`, and sets `stringForm` to `false`.
|
|
14
|
+
//
|
|
15
|
+
// It never overwrites an existing dialect entry or `expression`: a node that
|
|
16
|
+
// already has a target-dialect entry, or an `ANSI_SQL` entry without
|
|
17
|
+
// double-quoted column names, is untouched, so a model authored in
|
|
18
|
+
// GoogleSQL/unquoted ANSI is unaffected and its goldens are stable.
|
|
19
|
+
//
|
|
20
|
+
// Design:
|
|
21
|
+
// - Target-agnostic and non-mutating: `transpileModel(model, {target,
|
|
22
|
+
// transpiler})` returns a `structuredClone`, so the portable IR is
|
|
23
|
+
// preserved. The mechanism is injectable so tests run hermetically; the
|
|
24
|
+
// default adapter is the @polyglot-sql/sdk Rust/WASM engine, bundled into
|
|
25
|
+
// the tool -- there is no external runtime dependency to install.
|
|
26
|
+
// - Graceful degradation: if the engine fails to initialize, errors, writes a
|
|
27
|
+
// date form BigQuery rejects that the adapter cannot repair (see "BigQuery
|
|
28
|
+
// repairs" below), or would alter which entities an expression references
|
|
29
|
+
// (the qualifier-preservation guard), the node is left as it is and a
|
|
30
|
+
// warning is emitted -- never a throw.
|
|
31
|
+
//
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.polyglotTranspiler = void 0;
|
|
34
|
+
exports.hasDoubleQuotedColumn = hasDoubleQuotedColumn;
|
|
35
|
+
exports.doubleQuotedNames = doubleQuotedNames;
|
|
36
|
+
exports.transpileModel = transpileModel;
|
|
37
|
+
exports.translatesToBigQuery = translatesToBigQuery;
|
|
38
|
+
const sql_expr_utils_1 = require("./sql_expr_utils");
|
|
39
|
+
const sql_identifiers_1 = require("./sql_identifiers");
|
|
40
|
+
const sql_parser_1 = require("./sql_parser");
|
|
41
|
+
// The target dialect this pass rewrites into. The whole point of the pass is to
|
|
42
|
+
// reach GoogleSQL/BigQuery, so it is fixed rather than a knob; the polyglot
|
|
43
|
+
// adapter still accepts any target token for reuse/testing.
|
|
44
|
+
const DEFAULT_TARGET = 'BIGQUERY';
|
|
45
|
+
/**
|
|
46
|
+
* Returns true if `expression`, read as generic SQL (`ANSI_SQL`), quotes a
|
|
47
|
+
* column, table qualifier, or struct field with double quotes (`"..."`).
|
|
48
|
+
*
|
|
49
|
+
* The `ANSI_SQL` parser marks both `"..."` and `` `...` `` column identifiers
|
|
50
|
+
* with `quoted: true`, whereas `BIGQUERY` marks only `` `...` `` as `quoted:
|
|
51
|
+
* true` and treats `"..."` as a string literal (or fails to parse `."..."`).
|
|
52
|
+
* Comparing how many `quotedNames` the two ASTs hold yields three cases:
|
|
53
|
+
* - `ANSI_SQL > BIGQUERY`: at least one column identifier is double-quoted
|
|
54
|
+
* (e.g., `"orders"."amount"` or `"amount"`).
|
|
55
|
+
* - `ANSI_SQL < BIGQUERY`: `expression` uses BigQuery-only syntax (such as a
|
|
56
|
+
* `# "comment"` line comment in `` `orders`.`col` # "comment" ``) that
|
|
57
|
+
* `BIGQUERY` parses while `ANSI_SQL` fails to parse (`count = 0`).
|
|
58
|
+
* - `ANSI_SQL === BIGQUERY`: any `"` is inside a string literal or comment
|
|
59
|
+
* (e.g., `` `orders`.`note` = 'a "quoted" ` string' ``), and any quoted
|
|
60
|
+
* column identifiers use backticks in both dialects.
|
|
61
|
+
*/
|
|
62
|
+
function hasDoubleQuotedColumn(expression) {
|
|
63
|
+
if (!expression.includes('"'))
|
|
64
|
+
return false;
|
|
65
|
+
return quotedNames((0, sql_parser_1.sqlExpressionTree)(expression, 'ANSI_SQL')).length >
|
|
66
|
+
quotedNames((0, sql_parser_1.sqlExpressionTree)(expression, 'BIGQUERY')).length;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns the names that `expression` double-quotes, each once, for a
|
|
70
|
+
* message: those the `ANSI_SQL` parser marks quoted that `expression` writes
|
|
71
|
+
* in double quotes, else the first name it marks quoted.
|
|
72
|
+
*/
|
|
73
|
+
function doubleQuotedNames(expression) {
|
|
74
|
+
const names = quotedNames((0, sql_parser_1.sqlExpressionTree)(expression, 'ANSI_SQL'));
|
|
75
|
+
const written = names.filter(n => expression.includes(`"${n.replaceAll('"', '""')}"`));
|
|
76
|
+
return [...new Set(written.length ? written : names.slice(0, 1))];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns the names of the quoted column, table qualifier, and struct field
|
|
80
|
+
* identifiers (`quoted: true`) in a `@polyglot-sql/sdk` AST `node`, in tree
|
|
81
|
+
* order, with a table qualifier before its column name.
|
|
82
|
+
*
|
|
83
|
+
* `@polyglot-sql/sdk` represents simple or table-qualified column references as
|
|
84
|
+
* `{column: {name: {name, quoted}, table?: {name, quoted}}}` and chained
|
|
85
|
+
* struct field accesses (such as `orders.addr."city"`) as
|
|
86
|
+
* `{dot: {this: <base>, field: {name, quoted}}}`. When `sqlExpressionTree`
|
|
87
|
+
* cannot parse an expression in the requested dialect, it returns `undefined`,
|
|
88
|
+
* for which this function returns `[]`.
|
|
89
|
+
*/
|
|
90
|
+
function quotedNames(node) {
|
|
91
|
+
// Base case: `undefined` (when `sqlExpressionTree` fails to parse), `null`,
|
|
92
|
+
// or primitive AST leaf values (`string`, `number`, `boolean`) have no quoted
|
|
93
|
+
// column identifiers.
|
|
94
|
+
if (!node || typeof node !== 'object')
|
|
95
|
+
return [];
|
|
96
|
+
const obj = node;
|
|
97
|
+
const quoted = (id) => id?.quoted === true ? [id.name] : [];
|
|
98
|
+
// A `{column: {name, table}}` node represents a bare (`"col"`) or
|
|
99
|
+
// table-qualified (`"orders"."col"`) column reference; take the optional
|
|
100
|
+
// table qualifier and the column name separately whenever `quoted === true`.
|
|
101
|
+
if (obj.column) {
|
|
102
|
+
return [...quoted(obj.column.table), ...quoted(obj.column.name)];
|
|
103
|
+
}
|
|
104
|
+
// A `{dot: {this, field}}` node represents a struct subfield access (e.g.
|
|
105
|
+
// `orders.addr."city"`); recurse into `this` for the base column or outer
|
|
106
|
+
// struct path, and take `field` if `quoted === true`.
|
|
107
|
+
if (obj.dot) {
|
|
108
|
+
return [...quotedNames(obj.dot.this), ...quoted(obj.dot.field)];
|
|
109
|
+
}
|
|
110
|
+
// For any other AST container node (such as `sum`, `eq`, `case`) or array of
|
|
111
|
+
// child AST nodes, recursively collect the names across all child values.
|
|
112
|
+
return Object.values(obj).flatMap(quotedNames);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Returns the source `{dialect, expression}` to translate into `target` for
|
|
116
|
+
* `node`, or `undefined` when `node` should be left untouched.
|
|
117
|
+
*
|
|
118
|
+
* A field or metric is queued for translation in either of two cases:
|
|
119
|
+
* 1. Its only texts are in vendor dialects, with no `target` or `ANSI_SQL`
|
|
120
|
+
* entry -> translate from the vendor dialect.
|
|
121
|
+
* 2. It has an `ANSI_SQL` text that double-quotes a column identifier and no
|
|
122
|
+
* `target` entry -> translate from `ANSI_SQL`.
|
|
123
|
+
*/
|
|
124
|
+
function sourceToTranspile(node, target) {
|
|
125
|
+
// Parsed models store per-dialect SQL expressions in `node.dialects`.
|
|
126
|
+
if (node.dialects?.length) {
|
|
127
|
+
// Skip if `node` already has an explicit entry for the target dialect.
|
|
128
|
+
if (node.dialects.some(d => d.dialect === target))
|
|
129
|
+
return undefined;
|
|
130
|
+
// If `node` has a generic `ANSI_SQL` entry, `expressionForDialect` already
|
|
131
|
+
// falls back to it for `target` unless it double-quotes a column
|
|
132
|
+
// identifier (which `BIGQUERY` would read as a string literal). Only
|
|
133
|
+
// transpile `ANSI_SQL` when it uses double-quoted columns; otherwise skip
|
|
134
|
+
// transpilation even if vendor dialect entries are also present.
|
|
135
|
+
const ansi = node.dialects.find(d => d.dialect === 'ANSI_SQL');
|
|
136
|
+
if (ansi) {
|
|
137
|
+
return hasDoubleQuotedColumn(ansi.expression) ?
|
|
138
|
+
{ dialect: 'ANSI_SQL', expression: ansi.expression } :
|
|
139
|
+
undefined;
|
|
140
|
+
}
|
|
141
|
+
// Otherwise `node.dialects` contains only non-target vendor entries (e.g.
|
|
142
|
+
// `SNOWFLAKE`), so transpile from the first vendor entry.
|
|
143
|
+
const first = node.dialects[0];
|
|
144
|
+
return { dialect: first.dialect, expression: first.expression };
|
|
145
|
+
}
|
|
146
|
+
// Hand-built in-memory IR may omit `node.dialects` and set a bare
|
|
147
|
+
// `node.expression` (implicitly `ANSI_SQL`); only transpile it when it
|
|
148
|
+
// double-quotes a column identifier.
|
|
149
|
+
if (node.expression !== undefined) {
|
|
150
|
+
return hasDoubleQuotedColumn(node.expression) ?
|
|
151
|
+
{ dialect: 'ANSI_SQL', expression: node.expression } :
|
|
152
|
+
undefined;
|
|
153
|
+
}
|
|
154
|
+
// Hand-built in-memory IR may omit `node.dialects` and carry only a
|
|
155
|
+
// vendor-specific `importedExpression`; transpile it from `importedDialect`
|
|
156
|
+
// (falling back to `ANSI_SQL` if `importedDialect` was omitted).
|
|
157
|
+
if (node.importedExpression) {
|
|
158
|
+
return {
|
|
159
|
+
dialect: node.importedDialect || 'ANSI_SQL',
|
|
160
|
+
expression: node.importedExpression,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// The node has no SQL expression of any kind, so leave it untouched.
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Adds a translated `opts.target` dialect entry to every field, metric and
|
|
168
|
+
* profile field binding in `model` whose only texts are in vendor dialects or
|
|
169
|
+
* whose `ANSI_SQL` text double-quotes a column name, returning a cloned model
|
|
170
|
+
* plus diagnostics.
|
|
171
|
+
*
|
|
172
|
+
* The input model is never mutated. When no node needs transpilation, the clone
|
|
173
|
+
* is returned unchanged and the transpiler is not invoked. The
|
|
174
|
+
* qualifier-preservation guard parses the transpiled output with
|
|
175
|
+
* `columnReferences`, which requires the SQL parser to be loaded even when a
|
|
176
|
+
* custom `opts.transpiler` is injected (`loadSqlEngine()` is awaited before
|
|
177
|
+
* running the guard).
|
|
178
|
+
*/
|
|
179
|
+
async function transpileModel(model, opts = {}) {
|
|
180
|
+
const target = opts.target ?? DEFAULT_TARGET;
|
|
181
|
+
const transpiler = opts.transpiler ?? exports.polyglotTranspiler;
|
|
182
|
+
const clone = structuredClone(model);
|
|
183
|
+
const entityNames = clone.entities.map(e => e.name);
|
|
184
|
+
const warnings = [];
|
|
185
|
+
await (0, sql_parser_1.loadSqlEngine)();
|
|
186
|
+
const pending = [];
|
|
187
|
+
const add = (node, ctx) => {
|
|
188
|
+
const source = sourceToTranspile(node, target);
|
|
189
|
+
if (!source)
|
|
190
|
+
return;
|
|
191
|
+
const id = String(pending.length);
|
|
192
|
+
pending.push({
|
|
193
|
+
request: { id, dialect: source.dialect, expression: source.expression },
|
|
194
|
+
ctx,
|
|
195
|
+
accept: (sql) => {
|
|
196
|
+
node.dialects = [
|
|
197
|
+
...(node.dialects ?? []),
|
|
198
|
+
{ dialect: target, expression: sql },
|
|
199
|
+
];
|
|
200
|
+
node.stringForm = false;
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
for (const e of clone.entities) {
|
|
205
|
+
for (const f of e.fields)
|
|
206
|
+
add(f, `field '${e.name}.${f.name}'`);
|
|
207
|
+
}
|
|
208
|
+
for (const r of clone.relationships) {
|
|
209
|
+
// Direct FK edges carry no expressions; only an association (junction
|
|
210
|
+
// table) has edge-property fields, and only when hand-built IR supplies
|
|
211
|
+
// them.
|
|
212
|
+
for (const f of r.association?.fields ?? []) {
|
|
213
|
+
add(f, `relationship '${r.name}' field '${f.name}'`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const m of clone.metrics)
|
|
217
|
+
add(m, `metric '${m.name}'`);
|
|
218
|
+
for (const p of clone.profiles ?? []) {
|
|
219
|
+
if (opts.translatesProfile && !opts.translatesProfile(p, clone))
|
|
220
|
+
continue;
|
|
221
|
+
for (const pe of p.entities) {
|
|
222
|
+
for (const f of pe.fields ?? []) {
|
|
223
|
+
add(f, `profile '${p.name}': field '${pe.name}.${f.name}'`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!pending.length)
|
|
228
|
+
return { model: clone, warnings };
|
|
229
|
+
const responses = await transpiler(pending.map(p => p.request), target);
|
|
230
|
+
const byId = new Map(responses.map(r => [r.id, r]));
|
|
231
|
+
for (const p of pending) {
|
|
232
|
+
const { ctx, request } = p;
|
|
233
|
+
const res = byId.get(request.id);
|
|
234
|
+
if (!res || res.sql === undefined) {
|
|
235
|
+
const reason = res?.error ?? 'no result returned';
|
|
236
|
+
warnings.push(`${ctx}: could not transpile '${request.dialect}' -> '${target}' (${reason}); left untranslated`);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
// Qualifier-preservation guard: the BigQuery emitter locates measures and
|
|
240
|
+
// strips qualifiers by matching `<Entity>.` case-sensitively (see
|
|
241
|
+
// ./sql_expr_utils). A transpiler that re-cases a qualifier (`orders.` ->
|
|
242
|
+
// `Orders.`) leaves SQL the emitter can no longer match, so the measure
|
|
243
|
+
// would silently drop or misplace. Detect that on the OUTPUT with
|
|
244
|
+
// `columnReferences`: every entity the transpiled SQL references
|
|
245
|
+
// case-insensitively must also be matched case-sensitively -- i.e. kept in
|
|
246
|
+
// the exact case the emitter needs. Comparing the output against itself
|
|
247
|
+
// (rather than the vendor input) avoids false positives from dialect-
|
|
248
|
+
// specific identifier quoting.
|
|
249
|
+
const reached = qualifiersReached(res.sql, target, entityNames);
|
|
250
|
+
if (!reached) {
|
|
251
|
+
warnings.push(`${ctx}: transpiled '${request.dialect}' -> '${target}' could not ` +
|
|
252
|
+
`be parsed; qualifier check was skipped`);
|
|
253
|
+
}
|
|
254
|
+
else if (!sameSet(reached.exact, reached.anyCase)) {
|
|
255
|
+
warnings.push(`${ctx}: transpiled '${request.dialect}' -> '${target}' but it ` +
|
|
256
|
+
`re-cased an entity qualifier the emitter matches case-sensitively ` +
|
|
257
|
+
`(${fmtSet(reached.exact)} vs ${fmtSet(reached.anyCase)}); left ` +
|
|
258
|
+
`untranslated`);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
p.accept(res.sql);
|
|
262
|
+
warnings.push(`${ctx}: transpiled '${request.dialect}' -> '${target}'`);
|
|
263
|
+
if (request.dialect === 'SQLSERVER') {
|
|
264
|
+
warnings.push(`${ctx}: SQL Server text is translated to BigQuery on a ` +
|
|
265
|
+
`best-effort basis, and the translator can get some T-SQL wrong; ` +
|
|
266
|
+
`check the ${target} entry.`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { model: clone, warnings };
|
|
270
|
+
}
|
|
271
|
+
// The entities among `entityNames` that `sql`, in `target`, qualifies a
|
|
272
|
+
// column with in their exact case and in any case, or undefined when the
|
|
273
|
+
// parser cannot read `sql`.
|
|
274
|
+
function qualifiersReached(sql, target, entityNames) {
|
|
275
|
+
const cols = (0, sql_expr_utils_1.columnReferences)(sql, target);
|
|
276
|
+
if (cols === undefined)
|
|
277
|
+
return undefined;
|
|
278
|
+
const anyCase = new Set(cols.flatMap(c => {
|
|
279
|
+
const lower = c.qualifier?.toLowerCase();
|
|
280
|
+
return entityNames.filter(n => n.toLowerCase() === lower);
|
|
281
|
+
}));
|
|
282
|
+
return { exact: (0, sql_expr_utils_1.entitiesReached)(cols, entityNames), anyCase: [...anyCase] };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Whether `transpileModel`, with the default translator, gives `node` a
|
|
286
|
+
* BIGQUERY text in a model whose entities are `entityNames`. False until
|
|
287
|
+
* `loadSqlEngine()` has loaded the engine.
|
|
288
|
+
*/
|
|
289
|
+
function translatesToBigQuery(node, entityNames) {
|
|
290
|
+
const source = sourceToTranspile(node, DEFAULT_TARGET);
|
|
291
|
+
const engine = (0, sql_parser_1.loadedSqlEngine)();
|
|
292
|
+
if (!source || !engine)
|
|
293
|
+
return false;
|
|
294
|
+
const { sql } = transpileWith(engine, { id: '', ...source }, polyglotDialect(DEFAULT_TARGET));
|
|
295
|
+
if (sql === undefined)
|
|
296
|
+
return false;
|
|
297
|
+
const reached = qualifiersReached(sql, DEFAULT_TARGET, entityNames);
|
|
298
|
+
return !reached || sameSet(reached.exact, reached.anyCase);
|
|
299
|
+
}
|
|
300
|
+
function sameSet(a, b) {
|
|
301
|
+
if (a.length !== b.length)
|
|
302
|
+
return false;
|
|
303
|
+
const s = new Set(a);
|
|
304
|
+
return b.every(x => s.has(x));
|
|
305
|
+
}
|
|
306
|
+
function fmtSet(names) {
|
|
307
|
+
return names.length ? `{${names.join(', ')}}` : '{}';
|
|
308
|
+
}
|
|
309
|
+
// --- polyglot-sql adapter (the default mechanism) ----------------------------
|
|
310
|
+
// Maps our dialect tokens (as authored in the AI-first format) to the
|
|
311
|
+
// @polyglot-sql/sdk dialect names (lower-case). ANSI_SQL maps to the engine's
|
|
312
|
+
// dialect-neutral 'generic' parser. An unknown token has no mapping and
|
|
313
|
+
// degrades to a per-request error (the imported form + warning).
|
|
314
|
+
const POLYGLOT_DIALECTS = {
|
|
315
|
+
ANSI_SQL: 'generic', // the engine's dialect-neutral parser
|
|
316
|
+
BIGQUERY: 'bigquery',
|
|
317
|
+
SPANNER: 'bigquery',
|
|
318
|
+
POSTGRES: 'postgresql',
|
|
319
|
+
ALLOYDB: 'postgresql',
|
|
320
|
+
MYSQL: 'mysql',
|
|
321
|
+
SQLSERVER: 'tsql',
|
|
322
|
+
SNOWFLAKE: 'snowflake',
|
|
323
|
+
DATABRICKS: 'databricks',
|
|
324
|
+
};
|
|
325
|
+
function polyglotDialect(token) {
|
|
326
|
+
if (!token)
|
|
327
|
+
return undefined;
|
|
328
|
+
return POLYGLOT_DIALECTS[token.toUpperCase()];
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* The default {@link SqlTranspiler}: transpiles via the embedded
|
|
332
|
+
* @polyglot-sql/sdk Rust/WASM engine. There is no external runtime dependency
|
|
333
|
+
* -- the WASM blob is bundled into the tool. It never throws: an engine that
|
|
334
|
+
* fails to initialize yields an `error` response for every request, and a
|
|
335
|
+
* per-request parse failure or unsupported dialect yields an `error` for just
|
|
336
|
+
* that request, so the caller degrades to the imported form + warning.
|
|
337
|
+
*/
|
|
338
|
+
const polyglotTranspiler = async (requests, target) => {
|
|
339
|
+
if (!requests.length)
|
|
340
|
+
return [];
|
|
341
|
+
// Map the target to the engine's write dialect. A known token that maps to
|
|
342
|
+
// 'generic' (ANSI_SQL) means dialect-neutral output and must be preserved.
|
|
343
|
+
// An unknown target is an error for every request (symmetric with an unknown
|
|
344
|
+
// source dialect below) rather than a silent fallback to BigQuery, which would
|
|
345
|
+
// emit the wrong dialect while the caller's warning claimed the requested one.
|
|
346
|
+
const write = polyglotDialect(target);
|
|
347
|
+
if (write === undefined)
|
|
348
|
+
return requests.map(r => ({ id: r.id, error: `unsupported target dialect '${target}'` }));
|
|
349
|
+
let engine;
|
|
350
|
+
try {
|
|
351
|
+
engine = await (0, sql_parser_1.loadSqlEngine)();
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
const reason = `SQL transpiler engine unavailable: ${errMessage(err)}`;
|
|
355
|
+
return requests.map(r => ({ id: r.id, error: reason }));
|
|
356
|
+
}
|
|
357
|
+
return requests.map(r => transpileWith(engine, r, write));
|
|
358
|
+
};
|
|
359
|
+
exports.polyglotTranspiler = polyglotTranspiler;
|
|
360
|
+
// The engine's answer to `r`, with `write` as the engine's target dialect.
|
|
361
|
+
function transpileWith(engine, r, write) {
|
|
362
|
+
const read = polyglotDialect(r.dialect);
|
|
363
|
+
if (read === undefined)
|
|
364
|
+
return { id: r.id, error: `unsupported source dialect '${r.dialect}'` };
|
|
365
|
+
try {
|
|
366
|
+
const res = engine.transpile(r.expression, read, write);
|
|
367
|
+
if (!res.success || !res.sql || res.sql.length === 0)
|
|
368
|
+
return { id: r.id, error: res.error ?? 'transpilation produced no output' };
|
|
369
|
+
// A field/metric is a single scalar expression, so the engine should
|
|
370
|
+
// return exactly one statement. More than one means the input was not a
|
|
371
|
+
// lone expression; taking [0] would silently drop the rest, so reject it.
|
|
372
|
+
if (res.sql.length > 1)
|
|
373
|
+
return {
|
|
374
|
+
id: r.id,
|
|
375
|
+
error: `expected a single expression but got ${res.sql.length} statements`
|
|
376
|
+
};
|
|
377
|
+
if (write !== 'bigquery')
|
|
378
|
+
return { id: r.id, sql: res.sql[0] };
|
|
379
|
+
const repaired = repairForBigQuery(engine, res.sql[0], read);
|
|
380
|
+
return 'error' in repaired ? { id: r.id, error: repaired.error } :
|
|
381
|
+
{ id: r.id, sql: repaired.sql };
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
return { id: r.id, error: errMessage(err) };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function errMessage(err) {
|
|
388
|
+
return err instanceof Error ? err.message : String(err);
|
|
389
|
+
}
|
|
390
|
+
// --- BigQuery repairs ---------------------------------------------------------
|
|
391
|
+
//
|
|
392
|
+
// The engine (@polyglot-sql/sdk 0.9.1, and still 0.13.4) reads DATE_TRUNC,
|
|
393
|
+
// DATE_PART and TO_CHAR as functions it does not know and writes them to
|
|
394
|
+
// BigQuery as they were. It also keeps a PostgreSQL interval string such as
|
|
395
|
+
// `INTERVAL '1 day'`, and T-SQL's YEAR, MONTH and DAY. BigQuery rejects each of
|
|
396
|
+
// these, and the engine reports success. So the adapter reads the engine's
|
|
397
|
+
// BigQuery output back, rewrites the forms it can translate, and refuses the
|
|
398
|
+
// rest, so that no entry claims a translation BigQuery cannot run.
|
|
399
|
+
// BigQuery's DATE_TRUNC part for each part name PostgreSQL, Snowflake,
|
|
400
|
+
// Databricks and T-SQL take. PostgreSQL, Snowflake and Databricks start a week
|
|
401
|
+
// on Monday, as ISOWEEK does. T-SQL's DATETRUNC reaches this map too, and its
|
|
402
|
+
// `week` starts on the DATEFIRST day, so it is refused. Left out: `m`, which is
|
|
403
|
+
// month in T-SQL and minute in Snowflake.
|
|
404
|
+
const TRUNC_PARTS = {
|
|
405
|
+
microsecond: 'MICROSECOND',
|
|
406
|
+
microseconds: 'MICROSECOND',
|
|
407
|
+
millisecond: 'MILLISECOND',
|
|
408
|
+
milliseconds: 'MILLISECOND',
|
|
409
|
+
second: 'SECOND',
|
|
410
|
+
minute: 'MINUTE',
|
|
411
|
+
hour: 'HOUR',
|
|
412
|
+
day: 'DAY',
|
|
413
|
+
dd: 'DAY',
|
|
414
|
+
week: 'ISOWEEK',
|
|
415
|
+
iso_week: 'ISOWEEK',
|
|
416
|
+
month: 'MONTH',
|
|
417
|
+
mm: 'MONTH',
|
|
418
|
+
quarter: 'QUARTER',
|
|
419
|
+
year: 'YEAR',
|
|
420
|
+
yyyy: 'YEAR',
|
|
421
|
+
yy: 'YEAR',
|
|
422
|
+
};
|
|
423
|
+
// The engine's EXTRACT field for each DATE_PART part whose number BigQuery's
|
|
424
|
+
// EXTRACT gives too. Left out: `second`, which PostgreSQL gives with its
|
|
425
|
+
// fraction; `dow`, which PostgreSQL counts from 0; and `week`, which T-SQL's
|
|
426
|
+
// DATEPART does not count as ISO weeks. Day of year is a custom field because
|
|
427
|
+
// the engine writes its own `DayOfYear` field as `DOY`, which BigQuery rejects.
|
|
428
|
+
const EXTRACT_FIELDS = {
|
|
429
|
+
year: 'Year',
|
|
430
|
+
quarter: 'Quarter',
|
|
431
|
+
month: 'Month',
|
|
432
|
+
day: 'Day',
|
|
433
|
+
hour: 'Hour',
|
|
434
|
+
minute: 'Minute',
|
|
435
|
+
doy: { Custom: 'DAYOFYEAR' },
|
|
436
|
+
dayofyear: { Custom: 'DAYOFYEAR' },
|
|
437
|
+
isoyear: { Custom: 'ISOYEAR' },
|
|
438
|
+
};
|
|
439
|
+
// The engine's interval unit for each unit a PostgreSQL interval string names.
|
|
440
|
+
// Left out: `month` and `year`. BigQuery cannot add them to a TIMESTAMP, and
|
|
441
|
+
// fails each query that does, though a dry run passes. kcmd cannot see whether
|
|
442
|
+
// the value is a TIMESTAMP, so it refuses them.
|
|
443
|
+
const INTERVAL_UNITS = {
|
|
444
|
+
second: 'Second',
|
|
445
|
+
minute: 'Minute',
|
|
446
|
+
hour: 'Hour',
|
|
447
|
+
day: 'Day',
|
|
448
|
+
week: 'Week',
|
|
449
|
+
};
|
|
450
|
+
// `sql`, the engine's BigQuery output from SQL in the engine's dialect `read`,
|
|
451
|
+
// with the forms above rewritten, or an error naming a form kcmd cannot
|
|
452
|
+
// translate. A text the engine cannot read back is returned as it is;
|
|
453
|
+
// transpileModel's qualifier check reports it.
|
|
454
|
+
function repairForBigQuery(engine, sql, read) {
|
|
455
|
+
const tree = (0, sql_parser_1.sqlExpressionTree)(sql, 'BIGQUERY');
|
|
456
|
+
if (!tree)
|
|
457
|
+
return { sql };
|
|
458
|
+
let changed = false;
|
|
459
|
+
let refused;
|
|
460
|
+
const walk = (node) => {
|
|
461
|
+
if (!node || typeof node !== 'object')
|
|
462
|
+
return node;
|
|
463
|
+
if (Array.isArray(node))
|
|
464
|
+
return node.map(walk);
|
|
465
|
+
const out = {};
|
|
466
|
+
for (const [k, v] of Object.entries(node))
|
|
467
|
+
out[k] = walk(v);
|
|
468
|
+
// The engine reads BigQuery's TIMESTAMP as a timestamp without time zone,
|
|
469
|
+
// which it writes as DATETIME.
|
|
470
|
+
if (out.data_type === 'timestamp')
|
|
471
|
+
out.timezone = true;
|
|
472
|
+
const repaired = repairNode(out, read);
|
|
473
|
+
if (typeof repaired === 'string') {
|
|
474
|
+
refused ??= repaired;
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
if (repaired === undefined)
|
|
478
|
+
return out;
|
|
479
|
+
changed = true;
|
|
480
|
+
return repaired;
|
|
481
|
+
};
|
|
482
|
+
const result = walk(tree);
|
|
483
|
+
if (refused)
|
|
484
|
+
return { error: `kcmd cannot translate ${refused} to BigQuery` };
|
|
485
|
+
if (!changed)
|
|
486
|
+
return { sql };
|
|
487
|
+
const written = engine.generate([result], 'bigquery');
|
|
488
|
+
if (!written.success || written.sql?.length !== 1) {
|
|
489
|
+
return { error: written.error ?? 'the repaired SQL could not be written' };
|
|
490
|
+
}
|
|
491
|
+
return { sql: written.sql[0] };
|
|
492
|
+
}
|
|
493
|
+
// The node to put in place of `node`, a string naming a form kcmd cannot
|
|
494
|
+
// translate, or undefined to keep `node`. `read` is the engine's dialect for
|
|
495
|
+
// the source SQL.
|
|
496
|
+
function repairNode(node, read) {
|
|
497
|
+
const fn = node.function;
|
|
498
|
+
if (fn) {
|
|
499
|
+
const name = String(fn.name).toUpperCase();
|
|
500
|
+
const args = fn.args ?? [];
|
|
501
|
+
if (name === 'TO_CHAR')
|
|
502
|
+
return 'TO_CHAR';
|
|
503
|
+
// BigQuery's own DATE_TRUNC(d, MONTH) names its part last, unquoted.
|
|
504
|
+
if (name === 'DATE_TRUNC' && args.length === 2 &&
|
|
505
|
+
args[0].literal?.literal_type === 'string') {
|
|
506
|
+
const part = args[0].literal.value;
|
|
507
|
+
const bqPart = TRUNC_PARTS[part.toLowerCase()];
|
|
508
|
+
if (!bqPart || (read === 'tsql' && part.toLowerCase() === 'week')) {
|
|
509
|
+
return `DATE_TRUNC with part '${part}'`;
|
|
510
|
+
}
|
|
511
|
+
// The engine reads a lone name in the part position as a `var` node
|
|
512
|
+
// without its quotes, so put it back as a column, quoted if it must be.
|
|
513
|
+
const col = args[1].var?.this;
|
|
514
|
+
const date = col === undefined ? args[1] : {
|
|
515
|
+
column: { name: { name: col, quoted: !(0, sql_identifiers_1.isSimpleIdentifier)(col) }, table: null },
|
|
516
|
+
};
|
|
517
|
+
return { function: { ...fn, args: [date, { var: { this: bqPart } }] } };
|
|
518
|
+
}
|
|
519
|
+
if (name === 'DATE_PART') {
|
|
520
|
+
const part = args.length === 2 ? partName(args[0]) : undefined;
|
|
521
|
+
const field = part && EXTRACT_FIELDS[part.toLowerCase()];
|
|
522
|
+
if (!field)
|
|
523
|
+
return `DATE_PART with part '${part ?? '?'}'`;
|
|
524
|
+
if (isDifference(args[1]))
|
|
525
|
+
return 'DATE_PART of a difference';
|
|
526
|
+
return { extract: { this: args[1], field } };
|
|
527
|
+
}
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
if (node.extract && read !== 'bigquery') {
|
|
531
|
+
// A Spanner text already uses BigQuery's arithmetic.
|
|
532
|
+
if (isDifference(node.extract.this))
|
|
533
|
+
return 'EXTRACT of a difference';
|
|
534
|
+
// PostgreSQL and Databricks number weeks as ISO weeks.
|
|
535
|
+
if (node.extract.field === 'Week' &&
|
|
536
|
+
(read === 'postgresql' || read === 'databricks')) {
|
|
537
|
+
return { extract: { ...node.extract, field: { Custom: 'ISOWEEK' } } };
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// The engine's nodes for YEAR(d), MONTH(d) and DAY(d).
|
|
541
|
+
for (const kind of ['year', 'month', 'day']) {
|
|
542
|
+
if (node[kind]?.this) {
|
|
543
|
+
return { extract: { this: node[kind].this, field: EXTRACT_FIELDS[kind] } };
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// PostgreSQL's '1 day'::interval reaches here as a CAST to INTERVAL.
|
|
547
|
+
const interval = node.cast?.to?.data_type === 'interval' ?
|
|
548
|
+
{ this: node.cast.this, unit: null } :
|
|
549
|
+
node.interval;
|
|
550
|
+
if (interval && interval.unit === null &&
|
|
551
|
+
interval.this?.literal?.literal_type === 'string') {
|
|
552
|
+
const text = interval.this.literal.value;
|
|
553
|
+
const [, amount, unit] = /^\s*([+-]?\d+)\s*([a-z]+?)s?\s*$/i.exec(text) ?? [];
|
|
554
|
+
const bqUnit = unit && INTERVAL_UNITS[unit.toLowerCase()];
|
|
555
|
+
if (!bqUnit)
|
|
556
|
+
return `INTERVAL '${text}'`;
|
|
557
|
+
return {
|
|
558
|
+
interval: {
|
|
559
|
+
this: { literal: { literal_type: 'number', value: amount } },
|
|
560
|
+
unit: { type: 'simple', unit: bqUnit, use_plural: false },
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return undefined;
|
|
565
|
+
}
|
|
566
|
+
// Whether `arg` subtracts one value from another, other than an interval.
|
|
567
|
+
// BigQuery holds a TIMESTAMP difference in hours, so a day of it is 0 where
|
|
568
|
+
// PostgreSQL counts the days.
|
|
569
|
+
function isDifference(arg) {
|
|
570
|
+
const value = arg.paren?.this ?? arg;
|
|
571
|
+
return !!value.sub && !value.sub.right?.interval;
|
|
572
|
+
}
|
|
573
|
+
// The part a DATE_PART names: a string, or a bare name as Snowflake and
|
|
574
|
+
// T-SQL's DATEPART write it, which the engine reads as a column.
|
|
575
|
+
function partName(arg) {
|
|
576
|
+
if (arg.literal?.literal_type === 'string')
|
|
577
|
+
return arg.literal.value;
|
|
578
|
+
if (arg.column && !arg.column.table)
|
|
579
|
+
return arg.column.name?.name;
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { BigQueryClient } from '../gcp/bigquery';
|
|
2
|
+
import { SpannerClient } from '../gcp/spanner';
|
|
3
|
+
import { ResolvedDeployment } from './deployment_target';
|
|
4
|
+
import { SemanticModel } from './ir';
|
|
5
|
+
import { LoadedModel } from './loader';
|
|
6
|
+
import { AvailabilityReport, DeclaredProfiles, DialectLoad, LocalCheckError, ProfileFiles } from './resolve_profiles';
|
|
7
|
+
export declare function validatePushRequirements(models: LoadedModel[], opts?: {
|
|
8
|
+
fieldsPruned?: boolean;
|
|
9
|
+
warnings?: string[];
|
|
10
|
+
v2Aspects?: boolean;
|
|
11
|
+
}): string[];
|
|
12
|
+
export declare function validateDeployments(model: SemanticModel, document: string, load?: DialectLoad): string[];
|
|
13
|
+
export declare function validateInlineDialects(model: SemanticModel, document: string): string[];
|
|
14
|
+
export declare function validateInheritedColumns(model: SemanticModel, document: string): string[];
|
|
15
|
+
export declare function inlineBindingWarnings(model: SemanticModel, document: string, namedProfiles: boolean): string[];
|
|
16
|
+
export declare function validateActions(model: SemanticModel, document: string, fieldsPruned: boolean, checkDescriptions: boolean): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Returns one message per broken inheritance or concrete-leaf rule, or an
|
|
19
|
+
* empty list: a cycle, an unknown parent, a field inherited from two
|
|
20
|
+
* unrelated ancestors, or two fields of one entity whose names differ only in
|
|
21
|
+
* case; a redeclared inherited field; an
|
|
22
|
+
* abstract entity with a table; and a relationship or metric on an entity that
|
|
23
|
+
* is not a concrete leaf. Pruning can remove the field a rule is about, so a
|
|
24
|
+
* push that prunes runs this on the model before pruning as well as after.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateInheritance(models: LoadedModel[]): string[];
|
|
27
|
+
export declare function validateExpressionReferences(models: LoadedModel[], fieldsPruned?: boolean, warnings?: string[], flaggedMetrics?: Set<string>): string[];
|
|
28
|
+
export declare function deploymentTableErrors(document: string, model: SemanticModel, deployment: ResolvedDeployment): string[];
|
|
29
|
+
export declare function spannerMetricsErrors(document: string, model: SemanticModel, deployment: ResolvedDeployment): string[];
|
|
30
|
+
export declare function kcV2Aspects(): boolean;
|
|
31
|
+
export interface LocalCheckScope {
|
|
32
|
+
docs: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
text: string;
|
|
35
|
+
}>;
|
|
36
|
+
loaded: {
|
|
37
|
+
models: LoadedModel[];
|
|
38
|
+
warnings: string[];
|
|
39
|
+
};
|
|
40
|
+
models: LoadedModel[];
|
|
41
|
+
profileDocs: ProfileFiles;
|
|
42
|
+
declaredBy: DeclaredProfiles;
|
|
43
|
+
load: DialectLoad;
|
|
44
|
+
}
|
|
45
|
+
export interface DeploymentPlan {
|
|
46
|
+
deployment: ResolvedDeployment;
|
|
47
|
+
at: string;
|
|
48
|
+
model: LoadedModel;
|
|
49
|
+
report: AvailabilityReport;
|
|
50
|
+
generated?: {
|
|
51
|
+
ddl: string[];
|
|
52
|
+
warnings: string[];
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export declare function withProfileFiles(base: LoadedModel, profileDocs: ProfileFiles): LoadedModel | {
|
|
56
|
+
errors: LocalCheckError[];
|
|
57
|
+
};
|
|
58
|
+
export declare function localChecks(scope: LocalCheckScope, warn: (w: string) => void, selected?: string[]): Promise<{
|
|
59
|
+
plans: DeploymentPlan[];
|
|
60
|
+
} | {
|
|
61
|
+
errors: LocalCheckError[];
|
|
62
|
+
}>;
|
|
63
|
+
export declare function pruningLines({ deployment, model, report: r }: DeploymentPlan): string[];
|
|
64
|
+
export declare function validateBigQueryDataSources(models: LoadedModel[], bq: BigQueryClient, defaultProject: string, graph?: {
|
|
65
|
+
at: string;
|
|
66
|
+
dataset: string;
|
|
67
|
+
location: string;
|
|
68
|
+
}): Promise<string[]>;
|
|
69
|
+
export declare function validateSpannerActionStatements(models: LoadedModel[], spanner: SpannerClient): Promise<string[]>;
|
|
70
|
+
export declare function validateBigQueryActionStatements(models: LoadedModel[], bq: BigQueryClient, defaultProject: string): Promise<string[]>;
|