@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.
Files changed (150) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +264 -2
  3. package/build/ts/kcmd/cli.js +20252 -0
  4. package/build/ts/kcmd/gcp/alloydb.d.ts +12 -0
  5. package/build/ts/kcmd/gcp/alloydb.js +70 -0
  6. package/build/ts/kcmd/gcp/api.d.ts +29 -0
  7. package/build/ts/kcmd/gcp/api.js +162 -0
  8. package/build/ts/kcmd/gcp/biglake.d.ts +18 -0
  9. package/build/ts/kcmd/gcp/biglake.js +59 -0
  10. package/build/ts/kcmd/gcp/bigquery.d.ts +57 -0
  11. package/build/ts/kcmd/gcp/bigquery.js +134 -0
  12. package/build/ts/kcmd/gcp/context.d.ts +10 -0
  13. package/build/ts/kcmd/gcp/context.js +74 -0
  14. package/build/ts/kcmd/gcp/crm.d.ts +13 -0
  15. package/build/ts/kcmd/gcp/crm.js +91 -0
  16. package/build/ts/kcmd/gcp/dataplex.d.ts +147 -0
  17. package/build/ts/kcmd/gcp/dataplex.js +406 -0
  18. package/build/ts/kcmd/gcp/index.d.ts +3 -0
  19. package/build/ts/kcmd/gcp/index.js +21 -0
  20. package/build/ts/kcmd/gcp/spanner.d.ts +73 -0
  21. package/build/ts/kcmd/gcp/spanner.js +126 -0
  22. package/build/ts/kcmd/index.d.ts +7 -0
  23. package/build/ts/kcmd/index.js +46 -0
  24. package/build/ts/kcmd/layout.d.ts +16 -0
  25. package/build/ts/kcmd/layout.js +31 -0
  26. package/build/ts/kcmd/layouts/documents.d.ts +24 -0
  27. package/build/ts/kcmd/layouts/documents.js +264 -0
  28. package/build/ts/kcmd/layouts/okf.d.ts +31 -0
  29. package/build/ts/kcmd/layouts/okf.js +386 -0
  30. package/build/ts/kcmd/layouts/semantic-model.d.ts +74 -0
  31. package/build/ts/kcmd/layouts/semantic-model.js +621 -0
  32. package/build/ts/kcmd/layouts/standard.d.ts +13 -0
  33. package/build/ts/kcmd/layouts/standard.js +108 -0
  34. package/build/ts/kcmd/manifest.d.ts +34 -0
  35. package/build/ts/kcmd/manifest.js +262 -0
  36. package/build/ts/kcmd/metadata.d.ts +25 -0
  37. package/build/ts/kcmd/metadata.js +8 -0
  38. package/build/ts/kcmd/okf/catalog_residue.d.ts +14 -0
  39. package/build/ts/kcmd/okf/catalog_residue.js +124 -0
  40. package/build/ts/kcmd/okf/concept.d.ts +14 -0
  41. package/build/ts/kcmd/okf/concept.js +110 -0
  42. package/build/ts/kcmd/okf/concept_to_entry.d.ts +10 -0
  43. package/build/ts/kcmd/okf/concept_to_entry.js +281 -0
  44. package/build/ts/kcmd/okf/init_okf_workspace.d.ts +15 -0
  45. package/build/ts/kcmd/okf/init_okf_workspace.js +113 -0
  46. package/build/ts/kcmd/okf/links.d.ts +23 -0
  47. package/build/ts/kcmd/okf/links.js +468 -0
  48. package/build/ts/kcmd/okf/okf_metadata.d.ts +12 -0
  49. package/build/ts/kcmd/okf/okf_metadata.js +99 -0
  50. package/build/ts/kcmd/semantic/bigquery.d.ts +22 -0
  51. package/build/ts/kcmd/semantic/bigquery.js +469 -0
  52. package/build/ts/kcmd/semantic/binding.d.ts +3 -0
  53. package/build/ts/kcmd/semantic/binding.js +92 -0
  54. package/build/ts/kcmd/semantic/catalog_entry.d.ts +24 -0
  55. package/build/ts/kcmd/semantic/catalog_entry.js +35 -0
  56. package/build/ts/kcmd/semantic/converters/owl/convert.d.ts +26 -0
  57. package/build/ts/kcmd/semantic/converters/owl/convert.js +41 -0
  58. package/build/ts/kcmd/semantic/converters/owl/model.d.ts +98 -0
  59. package/build/ts/kcmd/semantic/converters/owl/model.js +30 -0
  60. package/build/ts/kcmd/semantic/converters/owl/parse.d.ts +11 -0
  61. package/build/ts/kcmd/semantic/converters/owl/parse.js +634 -0
  62. package/build/ts/kcmd/semantic/converters/owl/to_ir.d.ts +19 -0
  63. package/build/ts/kcmd/semantic/converters/owl/to_ir.js +390 -0
  64. package/build/ts/kcmd/semantic/deploy_bigquery.d.ts +20 -0
  65. package/build/ts/kcmd/semantic/deploy_bigquery.js +226 -0
  66. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +104 -0
  67. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +1367 -0
  68. package/build/ts/kcmd/semantic/deploy_spanner.d.ts +15 -0
  69. package/build/ts/kcmd/semantic/deploy_spanner.js +150 -0
  70. package/build/ts/kcmd/semantic/deployment_target.d.ts +93 -0
  71. package/build/ts/kcmd/semantic/deployment_target.js +379 -0
  72. package/build/ts/kcmd/semantic/graph_ddl.d.ts +44 -0
  73. package/build/ts/kcmd/semantic/graph_ddl.js +656 -0
  74. package/build/ts/kcmd/semantic/import_model.d.ts +72 -0
  75. package/build/ts/kcmd/semantic/import_model.js +1698 -0
  76. package/build/ts/kcmd/semantic/ir.d.ts +589 -0
  77. package/build/ts/kcmd/semantic/ir.js +247 -0
  78. package/build/ts/kcmd/semantic/kc_actions.d.ts +45 -0
  79. package/build/ts/kcmd/semantic/kc_actions.js +531 -0
  80. package/build/ts/kcmd/semantic/kc_constraints.d.ts +44 -0
  81. package/build/ts/kcmd/semantic/kc_constraints.js +279 -0
  82. package/build/ts/kcmd/semantic/kc_converter.d.ts +37 -0
  83. package/build/ts/kcmd/semantic/kc_converter.js +839 -0
  84. package/build/ts/kcmd/semantic/kc_custom_types.d.ts +63 -0
  85. package/build/ts/kcmd/semantic/kc_custom_types.js +751 -0
  86. package/build/ts/kcmd/semantic/kc_entries.d.ts +155 -0
  87. package/build/ts/kcmd/semantic/kc_entries.js +284 -0
  88. package/build/ts/kcmd/semantic/kc_ids.d.ts +102 -0
  89. package/build/ts/kcmd/semantic/kc_ids.js +188 -0
  90. package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +29 -0
  91. package/build/ts/kcmd/semantic/knowledge_catalog.js +960 -0
  92. package/build/ts/kcmd/semantic/loader.d.ts +258 -0
  93. package/build/ts/kcmd/semantic/loader.js +3576 -0
  94. package/build/ts/kcmd/semantic/names.d.ts +10 -0
  95. package/build/ts/kcmd/semantic/names.js +85 -0
  96. package/build/ts/kcmd/semantic/osi_converter.d.ts +60 -0
  97. package/build/ts/kcmd/semantic/osi_converter.js +910 -0
  98. package/build/ts/kcmd/semantic/parameters.d.ts +19 -0
  99. package/build/ts/kcmd/semantic/parameters.js +171 -0
  100. package/build/ts/kcmd/semantic/pull_kc.d.ts +10 -0
  101. package/build/ts/kcmd/semantic/pull_kc.js +272 -0
  102. package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +59 -0
  103. package/build/ts/kcmd/semantic/resolve_inheritance.js +346 -0
  104. package/build/ts/kcmd/semantic/resolve_profiles.d.ts +291 -0
  105. package/build/ts/kcmd/semantic/resolve_profiles.js +2003 -0
  106. package/build/ts/kcmd/semantic/runtime/agent_tools.d.ts +93 -0
  107. package/build/ts/kcmd/semantic/runtime/agent_tools.js +243 -0
  108. package/build/ts/kcmd/semantic/runtime/dialect.d.ts +13 -0
  109. package/build/ts/kcmd/semantic/runtime/dialect.js +39 -0
  110. package/build/ts/kcmd/semantic/runtime/runtime.d.ts +88 -0
  111. package/build/ts/kcmd/semantic/runtime/runtime.js +301 -0
  112. package/build/ts/kcmd/semantic/runtime/store.d.ts +112 -0
  113. package/build/ts/kcmd/semantic/runtime/store.js +344 -0
  114. package/build/ts/kcmd/semantic/skills.d.ts +110 -0
  115. package/build/ts/kcmd/semantic/skills.js +697 -0
  116. package/build/ts/kcmd/semantic/source_dialect.d.ts +17 -0
  117. package/build/ts/kcmd/semantic/source_dialect.js +311 -0
  118. package/build/ts/kcmd/semantic/source_entries.d.ts +12 -0
  119. package/build/ts/kcmd/semantic/source_entries.js +341 -0
  120. package/build/ts/kcmd/semantic/spanner.d.ts +18 -0
  121. package/build/ts/kcmd/semantic/spanner.js +60 -0
  122. package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +23 -0
  123. package/build/ts/kcmd/semantic/sql_expr_utils.js +394 -0
  124. package/build/ts/kcmd/semantic/sql_identifiers.d.ts +6 -0
  125. package/build/ts/kcmd/semantic/sql_identifiers.js +267 -0
  126. package/build/ts/kcmd/semantic/sql_parser.d.ts +47 -0
  127. package/build/ts/kcmd/semantic/sql_parser.js +400 -0
  128. package/build/ts/kcmd/semantic/transpile.d.ts +76 -0
  129. package/build/ts/kcmd/semantic/transpile.js +581 -0
  130. package/build/ts/kcmd/semantic/validate.d.ts +70 -0
  131. package/build/ts/kcmd/semantic/validate.js +2266 -0
  132. package/build/ts/kcmd/semantic/yaml_options.d.ts +6 -0
  133. package/build/ts/kcmd/semantic/yaml_options.js +58 -0
  134. package/build/ts/kcmd/semantic/yaml_update.d.ts +26 -0
  135. package/build/ts/kcmd/semantic/yaml_update.js +782 -0
  136. package/build/ts/kcmd/snapshot.d.ts +37 -0
  137. package/build/ts/kcmd/snapshot.js +386 -0
  138. package/build/ts/kcmd/source.d.ts +31 -0
  139. package/build/ts/kcmd/source.js +119 -0
  140. package/build/ts/kcmd/sources/bq-dataset.d.ts +17 -0
  141. package/build/ts/kcmd/sources/bq-dataset.js +131 -0
  142. package/build/ts/kcmd/sources/entrygroup.d.ts +21 -0
  143. package/build/ts/kcmd/sources/entrygroup.js +83 -0
  144. package/build/ts/kcmd/sources/kb.d.ts +21 -0
  145. package/build/ts/kcmd/sources/kb.js +82 -0
  146. package/build/ts/kcmd/sources/semantic-model.d.ts +18 -0
  147. package/build/ts/kcmd/sources/semantic-model.js +48 -0
  148. package/build/ts/kcmd/sync.d.ts +35 -0
  149. package/build/ts/kcmd/sync.js +734 -0
  150. package/package.json +62 -4
@@ -0,0 +1,17 @@
1
+ import type { SpannerClient } from '../gcp/spanner';
2
+ import { EntryLookup } from './catalog_entry';
3
+ export interface SourceDialects {
4
+ dialects: Map<string, string>;
5
+ databases: Map<string, string>;
6
+ errors: string[];
7
+ unread: string[];
8
+ }
9
+ export declare function sourceDialects(texts: Iterable<string>, catalog: EntryLookup, { thirdParty, spanner }?: {
10
+ thirdParty?: boolean;
11
+ spanner?: Pick<SpannerClient, 'getDatabase'>;
12
+ }): Promise<SourceDialects>;
13
+ export declare function tableFqnParts(fqn: string): {
14
+ prefix: string;
15
+ parts: string[];
16
+ } | undefined;
17
+ export declare function sourcesToLookUp(texts: Iterable<string>): string[];
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ // The dialect of a Cloud SQL table written as its resource name, or of a
3
+ // third-party table written as its entry name, read from the table's
4
+ // Knowledge Catalog entry, and the dialect of a Spanner table's database.
5
+ //
6
+ // The resource name cannot say the dialect: a MySQL table has no schema, but a
7
+ // PostgreSQL table and a SQL Server table have the same shape. Dataplex keeps an
8
+ // entry for each table of an instance with Dataplex integration on, in the
9
+ // system entry group `@cloudsql`, and the entry's id is the table's resource
10
+ // name. Its entry type names the engine.
11
+ //
12
+ // A third-party table's entry names its system twice: in the prefix of its
13
+ // fullyQualifiedName, which Dataplex checks, and in entrySource.system, which
14
+ // is free text. So the prefix decides, and the system only when the entry has
15
+ // no fullyQualifiedName. Dataplex also checks the rest of the
16
+ // fullyQualifiedName, so its parts say whether the entry is a table's or, for
17
+ // example, a schema's.
18
+ //
19
+ // Knowledge Catalog's Spanner entries leave their dialect empty, so a Spanner
20
+ // table's dialect is its database's `databaseDialect`, read by databases.get.
21
+ //
22
+ // This is the only place kcmd looks up a source's dialect. A command looks
23
+ // every such source up once, before it loads the model, and passes the
24
+ // dialects to the load (LoadOptions.sourceDialects).
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.sourceDialects = sourceDialects;
60
+ exports.tableFqnParts = tableFqnParts;
61
+ exports.sourcesToLookUp = sourcesToLookUp;
62
+ const yaml = __importStar(require("yaml"));
63
+ const loader_1 = require("./loader");
64
+ const catalog_entry_1 = require("./catalog_entry");
65
+ const parameters_1 = require("./parameters");
66
+ const yaml_options_1 = require("./yaml_options");
67
+ // The dialect each entrySource.system gives, in lower case.
68
+ const DIALECT_OF_ENTRY_SYSTEM = new Map([...loader_1.DIALECT_OF_FQN_PREFIX, ['postgres', 'POSTGRES']]);
69
+ // The parts of a table's fullyQualifiedName after each prefix, as Dataplex
70
+ // reads it. A view's name has the same parts as a table's.
71
+ const TABLE_FQN_PARTS = new Map([
72
+ ['snowflake', ['account', 'database', 'schema', 'table']],
73
+ ['databricks', ['metastore', 'catalog', 'schema', 'table']],
74
+ ['mysql', ['host', 'schema', 'table']],
75
+ ['postgresql', ['host', 'database', 'schema', 'table']],
76
+ ['sqlserver', ['host', 'database', 'schema', 'table']],
77
+ ]);
78
+ // Looks up, once each and in parallel, the dialect of every Cloud SQL and
79
+ // third-party table the files `texts` write (see sourcesToLookUp), and of every
80
+ // Spanner database they name. Without `spanner`, a Spanner database gives
81
+ // SPANNER, the dialect kcmd's Spanner store speaks, with no call.
82
+ // `thirdParty: false` skips third-party tables. Never throws: a failed lookup
83
+ // goes to `errors`.
84
+ async function sourceDialects(texts, catalog, { thirdParty = true, spanner } = {}) {
85
+ // Keyed as dialectLookupKey gives, so both spellings of one table, and the
86
+ // tables of one Spanner database, are one lookup. A database's lookup names
87
+ // the first of its tables.
88
+ const lookups = new Map();
89
+ for (const source of sourcesToLookUp(texts)) {
90
+ const table = (0, loader_1.cloudSqlTableOf)(source);
91
+ if (table) {
92
+ lookups.set(table.resourceName, () => cloudSqlDialect(catalog, table));
93
+ }
94
+ const entry = thirdParty ? (0, loader_1.thirdPartyEntryOf)(source) : undefined;
95
+ if (entry)
96
+ lookups.set(entry.name, () => thirdPartyDialect(catalog, entry));
97
+ const database = (0, loader_1.spannerDatabaseOf)(source);
98
+ if (database && !lookups.has(database)) {
99
+ lookups.set(database, spanner ? () => spannerDialect(spanner, database, source) :
100
+ async () => ({ dialect: 'SPANNER' }));
101
+ }
102
+ }
103
+ const dialects = new Map();
104
+ const databases = new Map();
105
+ const errors = [];
106
+ const unread = [];
107
+ await Promise.all([...lookups].map(async ([key, lookUp]) => {
108
+ const got = await lookUp();
109
+ if ('error' in got)
110
+ errors.push(got.error);
111
+ else
112
+ dialects.set(key, got.dialect);
113
+ if ('unread' in got)
114
+ unread.push(got.error);
115
+ if ('database' in got && got.database)
116
+ databases.set(key, got.database);
117
+ }));
118
+ errors.sort();
119
+ unread.sort();
120
+ return { dialects, databases, errors, unread };
121
+ }
122
+ // The dialect of a Cloud SQL table, by its entry's type.
123
+ async function cloudSqlDialect(catalog, table) {
124
+ const at = `source '${table.resourceName}'`;
125
+ const res = await (0, catalog_entry_1.lookupSystemEntry)(catalog, 'cloudsql', table.project, table.location, table.resourceName);
126
+ if ('error' in res)
127
+ return { error: `${at}: ${res.error}`, unread: true };
128
+ // Dataplex answers 403 both when the entry does not exist and when the
129
+ // caller may not read it, so the message names both causes.
130
+ if ('unreadable' in res) {
131
+ return {
132
+ unread: true,
133
+ error: `${at}: kcmd reads a Cloud SQL table's dialect from its ` +
134
+ `Knowledge Catalog entry, and could not read it. Turn on Dataplex ` +
135
+ `integration for instance '${table.instance}' (gcloud sql ` +
136
+ `instances patch ${table.instance} --enable-dataplex-integration ` +
137
+ `--project ${table.project}) and wait for the table to appear in ` +
138
+ `the catalog, or check that you can read the table. If integration ` +
139
+ `is on, check the name: the project ID, the instance's region, and ` +
140
+ `a schema only for PostgreSQL and SQL Server.`,
141
+ };
142
+ }
143
+ // The type comes back as a full name; its last part says the engine.
144
+ const type = (res.result.entryType ?? '').split('/').pop() ?? '';
145
+ const dialect = loader_1.DIALECT_OF_ENTRY_TYPE[type];
146
+ return dialect ? { dialect } : {
147
+ error: `${at}: its Knowledge Catalog entry has type '${type}', which is ` +
148
+ `not a Cloud SQL table or view type kcmd knows (${Object.keys(loader_1.DIALECT_OF_ENTRY_TYPE).join(', ')}).`,
149
+ };
150
+ }
151
+ // The dialect of the Spanner database `database`, which `source` names a
152
+ // table of, by its databaseDialect.
153
+ async function spannerDialect(spanner, database, source) {
154
+ const [, , project, , instance, , name] = database.split('/');
155
+ const failed = (why) => ({
156
+ unread: true,
157
+ error: `source '${source}': kcmd reads its Spanner database's SQL ` +
158
+ `dialect, and could not read database ${project}/${instance}/${name}: ${why}`,
159
+ });
160
+ try {
161
+ const res = await spanner.getDatabase(project, instance, name);
162
+ if (res.status !== 200) {
163
+ return failed((0, parameters_1.storeMessage)(res.message, res.status));
164
+ }
165
+ const postgres = res.result?.databaseDialect === 'POSTGRESQL';
166
+ return { dialect: postgres ? 'POSTGRES' : 'SPANNER' };
167
+ }
168
+ catch (err) {
169
+ return failed(err?.message ?? String(err));
170
+ }
171
+ }
172
+ // The dialect of a third-party table, by its entry's fullyQualifiedName or
173
+ // entrySource.system, and the database the fullyQualifiedName names. An entry
174
+ // whose fullyQualifiedName names no table or view is rejected.
175
+ async function thirdPartyDialect(catalog, { project, location, name }) {
176
+ const at = `source '${name}'`;
177
+ const res = await (0, catalog_entry_1.read)('looking up its Knowledge Catalog entry', () => catalog.lookupEntry(project, location, name));
178
+ if ('error' in res)
179
+ return { error: `${at}: ${res.error}`, unread: true };
180
+ if ('unreadable' in res) {
181
+ return {
182
+ unread: true,
183
+ error: `${at} names no table Knowledge Catalog knows about. The entry ` +
184
+ `may not exist, or you may not be allowed to read it. kcmd reads a ` +
185
+ `third-party table's dialect from its entry, so add the table to ` +
186
+ `Knowledge Catalog, or fix the source.`,
187
+ };
188
+ }
189
+ const { fullyQualifiedName: fqn, entrySource } = res.result;
190
+ const system = entrySource?.system;
191
+ const prefix = fqn?.match(/^([a-z]+):/)?.[1] ?? '';
192
+ const dialect = fqn ? loader_1.DIALECT_OF_FQN_PREFIX.get(prefix) :
193
+ DIALECT_OF_ENTRY_SYSTEM.get(system?.toLowerCase() ?? '');
194
+ if (dialect && fqn) {
195
+ const database = databaseOfFqn(fqn);
196
+ if (database)
197
+ return { dialect, database };
198
+ const parts = TABLE_FQN_PARTS.get(prefix).map(p => `<${p}>`).join('.');
199
+ return {
200
+ error: `${at}: its Knowledge Catalog entry has fullyQualifiedName ` +
201
+ `'${fqn}', which names no table or view. A table's ` +
202
+ `fullyQualifiedName has the form ${prefix}:${parts}. Write the ` +
203
+ `entry name of the table's own entry.`,
204
+ };
205
+ }
206
+ if (dialect)
207
+ return { dialect };
208
+ const has = fqn ? `fullyQualifiedName '${fqn}'` :
209
+ system ? `entrySource.system '${system}' and no fullyQualifiedName` :
210
+ 'neither a fullyQualifiedName nor an entrySource.system';
211
+ return {
212
+ error: `${at}: kcmd cannot tell the table's SQL dialect from its ` +
213
+ `Knowledge Catalog entry, which has ${has}. kcmd reads the prefix of ` +
214
+ `the entry's fullyQualifiedName (snowflake:, databricks:, mysql:, ` +
215
+ `postgresql: or sqlserver:), or entrySource.system when there is no ` +
216
+ `fullyQualifiedName.`,
217
+ };
218
+ }
219
+ // The prefix of the fullyQualifiedName `fqn` of a third-party table or view,
220
+ // and its unquoted parts, such as `snowflake` and ['acme', 'retail', 'public',
221
+ // 'orders'] for `snowflake:acme.retail.public.orders`. Undefined when `fqn`
222
+ // has another number of parts than TABLE_FQN_PARTS gives, or a subtype other
223
+ // than Databricks' `table:` and `view:`.
224
+ function tableFqnParts(fqn) {
225
+ // The subtype holds no backtick, so a colon inside a quoted host and port
226
+ // stays in the path. A colon in an unquoted database, schema or table
227
+ // name stays in its segment when it comes after the name's first backtick.
228
+ const [, prefix, subtype, path] = fqn.match(/^([a-z]+):(?:([a-z0-9_-]+):)?(.*)$/s) ?? [];
229
+ const parts = TABLE_FQN_PARTS.get(prefix ?? '');
230
+ const seg = (0, loader_1.catalogNameSegments)(path ?? '', true);
231
+ const tableSubtype = subtype === undefined ||
232
+ (prefix === 'databricks' && (subtype === 'table' || subtype === 'view'));
233
+ if (!parts || !tableSubtype || seg.length !== parts.length)
234
+ return undefined;
235
+ return { prefix, parts: seg };
236
+ }
237
+ // The database the fullyQualifiedName `fqn` of a third-party table or view
238
+ // names: a Snowflake account, a MySQL or SQL Server host, a PostgreSQL host
239
+ // and database, or a Databricks metastore. Each account and host is in lower
240
+ // case. Undefined when `fqn` names no table or view.
241
+ function databaseOfFqn(fqn) {
242
+ const table = tableFqnParts(fqn);
243
+ if (!table)
244
+ return undefined;
245
+ const { prefix, parts } = table;
246
+ const n = prefix === 'postgresql' ? 2 : 1;
247
+ // A Snowflake account and a host name ignore letter case.
248
+ if (prefix !== 'databricks')
249
+ parts[0] = parts[0].toLowerCase();
250
+ return [prefix, ...parts.slice(0, n)].join('/');
251
+ }
252
+ // Every Cloud SQL table, Spanner table and third-party entry the model and
253
+ // profile files `texts` write. A Cloud SQL table counts as a resource name or
254
+ // resource URI anywhere, JSON strings included. A Spanner table or an entry
255
+ // name counts as a `source` value in the YAML or in a GOOGLE block's JSON
256
+ // `data`, unless it sits in ai_context, an action parameter's default or
257
+ // another vendor's block. Values are read from the parsed YAML, so a name in a
258
+ // comment is not collected. A file that does not parse collects nothing; the
259
+ // load reports it.
260
+ function sourcesToLookUp(texts) {
261
+ const found = new Set();
262
+ // `key` names the member that holds `value`. `opaque` marks a value whose
263
+ // `source` names no table: one inside ai_context, an action parameter's
264
+ // default or another vendor's block, or in JSON written anywhere but a
265
+ // `data` member.
266
+ const visit = (value, key, opaque = false) => {
267
+ if (typeof value === 'string') {
268
+ if ((0, loader_1.cloudSqlTableOf)(value) ||
269
+ (key === 'source' && !opaque &&
270
+ ((0, loader_1.thirdPartyEntryOf)(value) || (0, loader_1.spannerDatabaseOf)(value)))) {
271
+ found.add(value.trim());
272
+ }
273
+ else if (/^\s*[[{]/.test(value)) {
274
+ try {
275
+ visit(JSON.parse(value), undefined, opaque || key !== 'data');
276
+ }
277
+ catch {
278
+ // Not JSON: an ordinary string.
279
+ }
280
+ }
281
+ }
282
+ else if (Array.isArray(value)) {
283
+ value.forEach(v => visit(v, undefined, opaque));
284
+ }
285
+ else if (value && typeof value === 'object') {
286
+ const vendor = 'vendor_name' in value && !(0, loader_1.isGoogleVendor)(value.vendor_name);
287
+ for (const [k, v] of Object.entries(value)) {
288
+ visit(v, k, opaque || vendor || k === 'ai_context' || k === 'default');
289
+ }
290
+ }
291
+ };
292
+ for (const text of texts) {
293
+ // Parsed as the loader parses, so both read the same values.
294
+ for (const doc of yaml.parseAllDocuments(text, yaml_options_1.YAML_OPTIONS)) {
295
+ if (doc.errors.length)
296
+ continue;
297
+ // Reading the values can still fail: on an alias whose anchor is not
298
+ // set, on more aliases than a load expands, or, while walking them, on
299
+ // an alias that refers to itself, which makes the values cyclic. The
300
+ // load reports that file in its own words, so the rest of it is skipped
301
+ // here rather than stopping the command.
302
+ try {
303
+ visit(doc.toJS());
304
+ }
305
+ catch {
306
+ continue;
307
+ }
308
+ }
309
+ }
310
+ return [...found];
311
+ }
@@ -0,0 +1,12 @@
1
+ import type { BigLakeClient } from '../gcp/biglake';
2
+ import type { BigQueryClient } from '../gcp/bigquery';
3
+ import type { SpannerClient } from '../gcp/spanner';
4
+ import { EntryLookup } from './catalog_entry';
5
+ import type { SemanticModel } from './ir';
6
+ export interface SourceReaders {
7
+ catalog: EntryLookup;
8
+ bigquery: Pick<BigQueryClient, 'getTable'>;
9
+ biglake: Pick<BigLakeClient, 'getCatalog' | 'getHiveCatalog'>;
10
+ spanner: Pick<SpannerClient, 'getInstance' | 'getInstanceConfig'>;
11
+ }
12
+ export declare function sourceEntryErrors(model: Pick<SemanticModel, 'name' | 'entities' | 'profiles'>, readers: SourceReaders): Promise<string[]>;