@google-cloud/kcmd 0.1.0 → 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/README.md +18 -20
- package/build/ts/kcmd/cli.js +5842 -5168
- package/build/ts/kcmd/gcp/biglake.d.ts +2 -0
- package/build/ts/kcmd/gcp/biglake.js +13 -4
- package/build/ts/kcmd/gcp/spanner.d.ts +10 -0
- package/build/ts/kcmd/gcp/spanner.js +6 -3
- package/build/ts/kcmd/layouts/okf.js +49 -7
- package/build/ts/kcmd/layouts/semantic-model.d.ts +13 -0
- package/build/ts/kcmd/layouts/semantic-model.js +129 -50
- package/build/ts/kcmd/okf/concept.d.ts +1 -1
- package/build/ts/kcmd/okf/concept.js +6 -0
- package/build/ts/kcmd/okf/concept_to_entry.d.ts +2 -0
- package/build/ts/kcmd/okf/concept_to_entry.js +29 -2
- package/build/ts/kcmd/okf/init_okf_workspace.js +8 -10
- package/build/ts/kcmd/okf/okf_metadata.js +17 -0
- package/build/ts/kcmd/semantic/bigquery.js +139 -195
- package/build/ts/kcmd/semantic/binding.js +14 -19
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +10 -35
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +92 -121
- package/build/ts/kcmd/semantic/graph_ddl.d.ts +10 -0
- package/build/ts/kcmd/semantic/graph_ddl.js +152 -59
- package/build/ts/kcmd/semantic/import_model.d.ts +26 -4
- package/build/ts/kcmd/semantic/import_model.js +526 -103
- package/build/ts/kcmd/semantic/kc_converter.d.ts +7 -1
- package/build/ts/kcmd/semantic/kc_converter.js +20 -5
- package/build/ts/kcmd/semantic/kc_custom_types.d.ts +5 -12
- package/build/ts/kcmd/semantic/kc_custom_types.js +16 -93
- package/build/ts/kcmd/semantic/kc_ids.d.ts +4 -5
- package/build/ts/kcmd/semantic/kc_ids.js +4 -7
- package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +0 -3
- package/build/ts/kcmd/semantic/knowledge_catalog.js +3 -86
- package/build/ts/kcmd/semantic/loader.d.ts +7 -11
- package/build/ts/kcmd/semantic/loader.js +284 -148
- package/build/ts/kcmd/semantic/osi_converter.js +1 -2
- package/build/ts/kcmd/semantic/parameters.d.ts +1 -0
- package/build/ts/kcmd/semantic/parameters.js +22 -3
- package/build/ts/kcmd/semantic/pull_kc.d.ts +1 -5
- package/build/ts/kcmd/semantic/pull_kc.js +38 -112
- package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +7 -2
- package/build/ts/kcmd/semantic/resolve_inheritance.js +37 -21
- package/build/ts/kcmd/semantic/resolve_profiles.d.ts +51 -10
- package/build/ts/kcmd/semantic/resolve_profiles.js +213 -32
- package/build/ts/kcmd/semantic/runtime/runtime.d.ts +0 -6
- package/build/ts/kcmd/semantic/runtime/runtime.js +4 -21
- package/build/ts/kcmd/semantic/source_dialect.d.ts +9 -2
- package/build/ts/kcmd/semantic/source_dialect.js +83 -33
- package/build/ts/kcmd/semantic/source_entries.d.ts +1 -1
- package/build/ts/kcmd/semantic/source_entries.js +95 -36
- package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +13 -7
- package/build/ts/kcmd/semantic/sql_expr_utils.js +240 -67
- package/build/ts/kcmd/semantic/sql_parser.d.ts +5 -2
- package/build/ts/kcmd/semantic/sql_parser.js +22 -0
- package/build/ts/kcmd/semantic/transpile.d.ts +12 -10
- package/build/ts/kcmd/semantic/transpile.js +79 -61
- package/build/ts/kcmd/semantic/validate.d.ts +37 -2
- package/build/ts/kcmd/semantic/validate.js +465 -98
- package/build/ts/kcmd/snapshot.d.ts +4 -0
- package/build/ts/kcmd/snapshot.js +7 -0
- package/build/ts/kcmd/sync.js +44 -7
- package/package.json +1 -1
|
@@ -34,7 +34,7 @@ exports.polyglotTranspiler = void 0;
|
|
|
34
34
|
exports.hasDoubleQuotedColumn = hasDoubleQuotedColumn;
|
|
35
35
|
exports.doubleQuotedNames = doubleQuotedNames;
|
|
36
36
|
exports.transpileModel = transpileModel;
|
|
37
|
-
exports.
|
|
37
|
+
exports.translatesToBigQuery = translatesToBigQuery;
|
|
38
38
|
const sql_expr_utils_1 = require("./sql_expr_utils");
|
|
39
39
|
const sql_identifiers_1 = require("./sql_identifiers");
|
|
40
40
|
const sql_parser_1 = require("./sql_parser");
|
|
@@ -164,9 +164,10 @@ function sourceToTranspile(node, target) {
|
|
|
164
164
|
return undefined;
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
|
-
* Adds a translated `opts.target` dialect entry to every field
|
|
168
|
-
* `model` whose only texts are in vendor dialects or
|
|
169
|
-
* double-quotes a column name, returning a cloned model
|
|
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.
|
|
170
171
|
*
|
|
171
172
|
* The input model is never mutated. When no node needs transpilation, the clone
|
|
172
173
|
* is returned unchanged and the transpiler is not invoked. The
|
|
@@ -214,6 +215,15 @@ async function transpileModel(model, opts = {}) {
|
|
|
214
215
|
}
|
|
215
216
|
for (const m of clone.metrics)
|
|
216
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
|
+
}
|
|
217
227
|
if (!pending.length)
|
|
218
228
|
return { model: clone, warnings };
|
|
219
229
|
const responses = await transpiler(pending.map(p => p.request), target);
|
|
@@ -236,27 +246,17 @@ async function transpileModel(model, opts = {}) {
|
|
|
236
246
|
// the exact case the emitter needs. Comparing the output against itself
|
|
237
247
|
// (rather than the vendor input) avoids false positives from dialect-
|
|
238
248
|
// specific identifier quoting.
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
249
|
+
const reached = qualifiersReached(res.sql, target, entityNames);
|
|
250
|
+
if (!reached) {
|
|
241
251
|
warnings.push(`${ctx}: transpiled '${request.dialect}' -> '${target}' could not ` +
|
|
242
252
|
`be parsed; qualifier check was skipped`);
|
|
243
253
|
}
|
|
244
|
-
else {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const lower = c.qualifier.toLowerCase();
|
|
251
|
-
return entityNames.filter(n => n.toLowerCase() === lower);
|
|
252
|
-
})),
|
|
253
|
-
];
|
|
254
|
-
if (!sameSet(emitterVisible, anyCase)) {
|
|
255
|
-
warnings.push(`${ctx}: transpiled '${request.dialect}' -> '${target}' but it re-cased an entity qualifier the emitter matches ` +
|
|
256
|
-
`case-sensitively (${fmtSet(emitterVisible)} vs ${fmtSet(anyCase)}); ` +
|
|
257
|
-
`left untranslated`);
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
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
260
|
}
|
|
261
261
|
p.accept(res.sql);
|
|
262
262
|
warnings.push(`${ctx}: transpiled '${request.dialect}' -> '${target}'`);
|
|
@@ -268,6 +268,35 @@ async function transpileModel(model, opts = {}) {
|
|
|
268
268
|
}
|
|
269
269
|
return { model: clone, warnings };
|
|
270
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
|
+
}
|
|
271
300
|
function sameSet(a, b) {
|
|
272
301
|
if (a.length !== b.length)
|
|
273
302
|
return false;
|
|
@@ -298,16 +327,6 @@ function polyglotDialect(token) {
|
|
|
298
327
|
return undefined;
|
|
299
328
|
return POLYGLOT_DIALECTS[token.toUpperCase()];
|
|
300
329
|
}
|
|
301
|
-
/**
|
|
302
|
-
* Returns true if the default translator reads SQL written in `dialect`
|
|
303
|
-
* (treating an empty string as `ANSI_SQL`).
|
|
304
|
-
*
|
|
305
|
-
* `transpileModel` leaves a text in any other dialect as written, with a
|
|
306
|
-
* warning.
|
|
307
|
-
*/
|
|
308
|
-
function transpilesFrom(dialect) {
|
|
309
|
-
return polyglotDialect(dialect || 'ANSI_SQL') !== undefined;
|
|
310
|
-
}
|
|
311
330
|
/**
|
|
312
331
|
* The default {@link SqlTranspiler}: transpiles via the embedded
|
|
313
332
|
* @polyglot-sql/sdk Rust/WASM engine. There is no external runtime dependency
|
|
@@ -335,37 +354,36 @@ const polyglotTranspiler = async (requests, target) => {
|
|
|
335
354
|
const reason = `SQL transpiler engine unavailable: ${errMessage(err)}`;
|
|
336
355
|
return requests.map(r => ({ id: r.id, error: reason }));
|
|
337
356
|
}
|
|
338
|
-
return requests.map(r =>
|
|
339
|
-
const read = polyglotDialect(r.dialect);
|
|
340
|
-
if (read === undefined)
|
|
341
|
-
return { id: r.id, error: `unsupported source dialect '${r.dialect}'` };
|
|
342
|
-
try {
|
|
343
|
-
const res = engine.transpile(r.expression, read, write);
|
|
344
|
-
if (!res.success || !res.sql || res.sql.length === 0)
|
|
345
|
-
return {
|
|
346
|
-
id: r.id,
|
|
347
|
-
error: res.error ?? 'transpilation produced no output'
|
|
348
|
-
};
|
|
349
|
-
// A field/metric is a single scalar expression, so the engine should
|
|
350
|
-
// return exactly one statement. More than one means the input was not a
|
|
351
|
-
// lone expression; taking [0] would silently drop the rest, so reject it.
|
|
352
|
-
if (res.sql.length > 1)
|
|
353
|
-
return {
|
|
354
|
-
id: r.id,
|
|
355
|
-
error: `expected a single expression but got ${res.sql.length} statements`
|
|
356
|
-
};
|
|
357
|
-
if (write !== 'bigquery')
|
|
358
|
-
return { id: r.id, sql: res.sql[0] };
|
|
359
|
-
const repaired = repairForBigQuery(engine, res.sql[0], read);
|
|
360
|
-
return 'error' in repaired ? { id: r.id, error: repaired.error } :
|
|
361
|
-
{ id: r.id, sql: repaired.sql };
|
|
362
|
-
}
|
|
363
|
-
catch (err) {
|
|
364
|
-
return { id: r.id, error: errMessage(err) };
|
|
365
|
-
}
|
|
366
|
-
});
|
|
357
|
+
return requests.map(r => transpileWith(engine, r, write));
|
|
367
358
|
};
|
|
368
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
|
+
}
|
|
369
387
|
function errMessage(err) {
|
|
370
388
|
return err instanceof Error ? err.message : String(err);
|
|
371
389
|
}
|
|
@@ -3,12 +3,13 @@ import { SpannerClient } from '../gcp/spanner';
|
|
|
3
3
|
import { ResolvedDeployment } from './deployment_target';
|
|
4
4
|
import { SemanticModel } from './ir';
|
|
5
5
|
import { LoadedModel } from './loader';
|
|
6
|
+
import { AvailabilityReport, DeclaredProfiles, DialectLoad, LocalCheckError, ProfileFiles } from './resolve_profiles';
|
|
6
7
|
export declare function validatePushRequirements(models: LoadedModel[], opts?: {
|
|
7
8
|
fieldsPruned?: boolean;
|
|
8
9
|
warnings?: string[];
|
|
9
10
|
v2Aspects?: boolean;
|
|
10
11
|
}): string[];
|
|
11
|
-
export declare function validateDeployments(model: SemanticModel, document: string): string[];
|
|
12
|
+
export declare function validateDeployments(model: SemanticModel, document: string, load?: DialectLoad): string[];
|
|
12
13
|
export declare function validateInlineDialects(model: SemanticModel, document: string): string[];
|
|
13
14
|
export declare function validateInheritedColumns(model: SemanticModel, document: string): string[];
|
|
14
15
|
export declare function inlineBindingWarnings(model: SemanticModel, document: string, namedProfiles: boolean): string[];
|
|
@@ -25,11 +26,45 @@ export declare function validateActions(model: SemanticModel, document: string,
|
|
|
25
26
|
export declare function validateInheritance(models: LoadedModel[]): string[];
|
|
26
27
|
export declare function validateExpressionReferences(models: LoadedModel[], fieldsPruned?: boolean, warnings?: string[], flaggedMetrics?: Set<string>): string[];
|
|
27
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[];
|
|
28
64
|
export declare function validateBigQueryDataSources(models: LoadedModel[], bq: BigQueryClient, defaultProject: string, graph?: {
|
|
29
65
|
at: string;
|
|
30
66
|
dataset: string;
|
|
31
67
|
location: string;
|
|
32
68
|
}): Promise<string[]>;
|
|
33
|
-
export declare function storeMessage(message: string | undefined, status: number): string;
|
|
34
69
|
export declare function validateSpannerActionStatements(models: LoadedModel[], spanner: SpannerClient): Promise<string[]>;
|
|
35
70
|
export declare function validateBigQueryActionStatements(models: LoadedModel[], bq: BigQueryClient, defaultProject: string): Promise<string[]>;
|