@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.
Files changed (60) hide show
  1. package/README.md +18 -20
  2. package/build/ts/kcmd/cli.js +5842 -5168
  3. package/build/ts/kcmd/gcp/biglake.d.ts +2 -0
  4. package/build/ts/kcmd/gcp/biglake.js +13 -4
  5. package/build/ts/kcmd/gcp/spanner.d.ts +10 -0
  6. package/build/ts/kcmd/gcp/spanner.js +6 -3
  7. package/build/ts/kcmd/layouts/okf.js +49 -7
  8. package/build/ts/kcmd/layouts/semantic-model.d.ts +13 -0
  9. package/build/ts/kcmd/layouts/semantic-model.js +129 -50
  10. package/build/ts/kcmd/okf/concept.d.ts +1 -1
  11. package/build/ts/kcmd/okf/concept.js +6 -0
  12. package/build/ts/kcmd/okf/concept_to_entry.d.ts +2 -0
  13. package/build/ts/kcmd/okf/concept_to_entry.js +29 -2
  14. package/build/ts/kcmd/okf/init_okf_workspace.js +8 -10
  15. package/build/ts/kcmd/okf/okf_metadata.js +17 -0
  16. package/build/ts/kcmd/semantic/bigquery.js +139 -195
  17. package/build/ts/kcmd/semantic/binding.js +14 -19
  18. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +10 -35
  19. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +92 -121
  20. package/build/ts/kcmd/semantic/graph_ddl.d.ts +10 -0
  21. package/build/ts/kcmd/semantic/graph_ddl.js +152 -59
  22. package/build/ts/kcmd/semantic/import_model.d.ts +26 -4
  23. package/build/ts/kcmd/semantic/import_model.js +526 -103
  24. package/build/ts/kcmd/semantic/kc_converter.d.ts +7 -1
  25. package/build/ts/kcmd/semantic/kc_converter.js +20 -5
  26. package/build/ts/kcmd/semantic/kc_custom_types.d.ts +5 -12
  27. package/build/ts/kcmd/semantic/kc_custom_types.js +16 -93
  28. package/build/ts/kcmd/semantic/kc_ids.d.ts +4 -5
  29. package/build/ts/kcmd/semantic/kc_ids.js +4 -7
  30. package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +0 -3
  31. package/build/ts/kcmd/semantic/knowledge_catalog.js +3 -86
  32. package/build/ts/kcmd/semantic/loader.d.ts +7 -11
  33. package/build/ts/kcmd/semantic/loader.js +284 -148
  34. package/build/ts/kcmd/semantic/osi_converter.js +1 -2
  35. package/build/ts/kcmd/semantic/parameters.d.ts +1 -0
  36. package/build/ts/kcmd/semantic/parameters.js +22 -3
  37. package/build/ts/kcmd/semantic/pull_kc.d.ts +1 -5
  38. package/build/ts/kcmd/semantic/pull_kc.js +38 -112
  39. package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +7 -2
  40. package/build/ts/kcmd/semantic/resolve_inheritance.js +37 -21
  41. package/build/ts/kcmd/semantic/resolve_profiles.d.ts +51 -10
  42. package/build/ts/kcmd/semantic/resolve_profiles.js +213 -32
  43. package/build/ts/kcmd/semantic/runtime/runtime.d.ts +0 -6
  44. package/build/ts/kcmd/semantic/runtime/runtime.js +4 -21
  45. package/build/ts/kcmd/semantic/source_dialect.d.ts +9 -2
  46. package/build/ts/kcmd/semantic/source_dialect.js +83 -33
  47. package/build/ts/kcmd/semantic/source_entries.d.ts +1 -1
  48. package/build/ts/kcmd/semantic/source_entries.js +95 -36
  49. package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +13 -7
  50. package/build/ts/kcmd/semantic/sql_expr_utils.js +240 -67
  51. package/build/ts/kcmd/semantic/sql_parser.d.ts +5 -2
  52. package/build/ts/kcmd/semantic/sql_parser.js +22 -0
  53. package/build/ts/kcmd/semantic/transpile.d.ts +12 -10
  54. package/build/ts/kcmd/semantic/transpile.js +79 -61
  55. package/build/ts/kcmd/semantic/validate.d.ts +37 -2
  56. package/build/ts/kcmd/semantic/validate.js +465 -98
  57. package/build/ts/kcmd/snapshot.d.ts +4 -0
  58. package/build/ts/kcmd/snapshot.js +7 -0
  59. package/build/ts/kcmd/sync.js +44 -7
  60. 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.transpilesFrom = transpilesFrom;
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 or metric in
168
- * `model` whose only texts are in vendor dialects or whose `ANSI_SQL` text
169
- * double-quotes a column name, returning a cloned model plus diagnostics.
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 cols = (0, sql_expr_utils_1.columnReferences)(res.sql, target);
240
- if (cols === undefined) {
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
- const emitterVisible = (0, sql_expr_utils_1.entitiesReached)(cols, entityNames);
246
- const anyCase = [
247
- ...new Set(cols.flatMap(c => {
248
- if (!c.qualifier)
249
- return [];
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[]>;