@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
|
@@ -19,12 +19,11 @@
|
|
|
19
19
|
//
|
|
20
20
|
// Publish sequence (split into read-only `preflightKnowledgeCatalog` and write
|
|
21
21
|
// `applyKnowledgeCatalog`):
|
|
22
|
-
// * Preflight validates that exactly one model is deployed per entry group
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// every aspect's size.
|
|
22
|
+
// * Preflight validates that exactly one model is deployed per entry group,
|
|
23
|
+
// snapshots the destination entry group to guard against foreign models
|
|
24
|
+
// (unless `--force-remove` is set), and checks that any required custom
|
|
25
|
+
// entry/aspect types exist in the target project. With the
|
|
26
|
+
// second-generation aspects, it also checks every aspect's size.
|
|
28
27
|
// * Apply creates or updates each model's entries in dependency order: the
|
|
29
28
|
// `semantic-model` anchor first, then independent child entries
|
|
30
29
|
// concurrently, and finally dependent child entries (`semantic-metric` and
|
|
@@ -44,6 +43,8 @@
|
|
|
44
43
|
// * Reconcile deletions: after writing, delete any child entry this push owns
|
|
45
44
|
// (by `ownedPrefixes`) that is present in the pre-write listing but was not
|
|
46
45
|
// re-emitted.
|
|
46
|
+
// * The anchor's write labels it `kcmd-push-state: writing`, and the push's
|
|
47
|
+
// last write, after the deletions, relabels it `finished`.
|
|
47
48
|
//
|
|
48
49
|
// This is a library module: it emits no console output. Warnings and the
|
|
49
50
|
// dry-run plan are returned in `KcDeployResult` for the CLI (commands.ts) to
|
|
@@ -88,9 +89,7 @@ exports.applyKnowledgeCatalog = applyKnowledgeCatalog;
|
|
|
88
89
|
exports.deployKnowledgeCatalog = deployKnowledgeCatalog;
|
|
89
90
|
exports.deployEmittedModels = deployEmittedModels;
|
|
90
91
|
exports.oneModelPerEntryGroupMessage = oneModelPerEntryGroupMessage;
|
|
91
|
-
exports.checkFileCopies = checkFileCopies;
|
|
92
92
|
exports.checkCatalogAspects = checkCatalogAspects;
|
|
93
|
-
exports.checkCustomTypes = checkCustomTypes;
|
|
94
93
|
const crm = __importStar(require("../gcp/crm"));
|
|
95
94
|
const dataplex_1 = require("../gcp/dataplex");
|
|
96
95
|
const kc_custom_types_1 = require("./kc_custom_types");
|
|
@@ -109,14 +108,13 @@ function sleep(ms) {
|
|
|
109
108
|
*
|
|
110
109
|
* The method performs the following actions:
|
|
111
110
|
* 1. Translates the IR models into catalog resources.
|
|
112
|
-
* 2. Verifies that exactly one model is being deployed
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
111
|
+
* 2. Verifies that exactly one model is being deployed, builds the dry-run
|
|
112
|
+
* plan, and returns early when `opts.validateOnly` is set.
|
|
113
|
+
* 3. Fails when the destination entry group does not exist. Lists it and
|
|
114
|
+
* fails if an unrecognized model's anchor entry is present without
|
|
115
|
+
* `--force-remove`.
|
|
117
116
|
* 4. Verifies that every custom entry type and aspect type required by the
|
|
118
|
-
* model exists in the target project.
|
|
119
|
-
* `opts.fileCopyTypeChecked` is set.
|
|
117
|
+
* model exists in the target project.
|
|
120
118
|
* 5. Returns the emitted resources, entry-group listing, warnings, and plan
|
|
121
119
|
* as `prepared` for `applyKnowledgeCatalog`.
|
|
122
120
|
* With `opts.v2Aspects` set, step 2 also checks every aspect's size.
|
|
@@ -157,20 +155,10 @@ async function applyKnowledgeCatalog(prepared, ctx, opts) {
|
|
|
157
155
|
const { emitted, existing } = prepared;
|
|
158
156
|
// Resource Manager is asked for the project number at most once per push.
|
|
159
157
|
const isScopeProject = scopeProjectMatcher(ctx, opts.project);
|
|
160
|
-
// Some of this model's writes may have landed before a failure in them.
|
|
161
|
-
// The catalog may then lack some or all of this push's file copies, and
|
|
162
|
-
// pull refuses that until a push succeeds.
|
|
163
|
-
const storesCopies = emitted.some(m => m.resources.entries.some(e => (0, kc_entries_1.entryTypeId)(e) === kc_custom_types_1.FILE_COPY_TYPE_ID));
|
|
164
|
-
const failed = (details) => result(false, storesCopies ?
|
|
165
|
-
`${details.replace(/\.$/, '')}. The catalog may now lack some or ` +
|
|
166
|
-
`all of this push's file copies, so push again before you pull.` :
|
|
167
|
-
details);
|
|
168
158
|
if (opts.forceRemove) {
|
|
169
159
|
const foreignAnchorIds = findForeignAnchorIds(emitted[0], existing);
|
|
170
160
|
if (foreignAnchorIds.length) {
|
|
171
|
-
//
|
|
172
|
-
// none of this push's file copies, so it gets the note too, and so
|
|
173
|
-
// does a thrown error.
|
|
161
|
+
// A thrown error is reported like a failed delete.
|
|
174
162
|
const removed = await removeForeignModels(cat, opts, existing, foreignAnchorIds, isScopeProject)
|
|
175
163
|
.catch((err) => ({
|
|
176
164
|
deleted: 0,
|
|
@@ -180,7 +168,7 @@ async function applyKnowledgeCatalog(prepared, ctx, opts) {
|
|
|
180
168
|
counts.deleted += removed.deleted;
|
|
181
169
|
counts.unlinked += removed.unlinked;
|
|
182
170
|
if (removed.error)
|
|
183
|
-
return
|
|
171
|
+
return result(false, removed.error);
|
|
184
172
|
}
|
|
185
173
|
}
|
|
186
174
|
// Write each emitted model's entries and relationship links and delete any
|
|
@@ -190,7 +178,7 @@ async function applyKnowledgeCatalog(prepared, ctx, opts) {
|
|
|
190
178
|
const written = await writeEmittedModels(cat, opts, emitted, existing, counts, isScopeProject)
|
|
191
179
|
.catch((err) => ({ error: String(err?.message || err).trim() }));
|
|
192
180
|
if (written.error)
|
|
193
|
-
return
|
|
181
|
+
return result(false, written.error);
|
|
194
182
|
return result(true);
|
|
195
183
|
}
|
|
196
184
|
/**
|
|
@@ -223,15 +211,15 @@ async function deployEmittedModels(emitted, emitWarnings, ctx, opts) {
|
|
|
223
211
|
* Runs read-only preflight checks on already-emitted models.
|
|
224
212
|
*
|
|
225
213
|
* The method performs the following checks:
|
|
226
|
-
* 1. Validates locally that `emitted` contains a single model,
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* 2.
|
|
231
|
-
* foreign models unless `--force-remove`
|
|
214
|
+
* 1. Validates locally that `emitted` contains a single model and, with
|
|
215
|
+
* `opts.v2Aspects` set, that every aspect size and instance count is
|
|
216
|
+
* within its limit. Builds the dry-run plan and returns early when
|
|
217
|
+
* `opts.validateOnly` is set.
|
|
218
|
+
* 2. Validates that the destination entry group exists. Lists it and
|
|
219
|
+
* validates that it contains no foreign models unless `--force-remove`
|
|
220
|
+
* is set.
|
|
232
221
|
* 3. Validates that every custom type used by the model exists in the
|
|
233
|
-
* target project.
|
|
234
|
-
* `opts.fileCopyTypeChecked` is set.
|
|
222
|
+
* target project.
|
|
235
223
|
*/
|
|
236
224
|
async function preflightEmittedModels(emitted, emitWarnings, ctx, opts) {
|
|
237
225
|
const warnings = [...emitWarnings];
|
|
@@ -246,9 +234,6 @@ async function preflightEmittedModels(emitted, emitWarnings, ctx, opts) {
|
|
|
246
234
|
// Once `validateEmittedModels` passes and `emitted` is non-empty, `emitted`
|
|
247
235
|
// is guaranteed to contain exactly one model (`emitted[0]`).
|
|
248
236
|
const model = emitted[0];
|
|
249
|
-
const sizeValidation = validateFileCopySizes(model, result);
|
|
250
|
-
if (!sizeValidation.success)
|
|
251
|
-
return { result: sizeValidation };
|
|
252
237
|
const aspectsError = opts.v2Aspects ? emittedAspectsError(model) : undefined;
|
|
253
238
|
if (aspectsError)
|
|
254
239
|
return { result: result(false, aspectsError) };
|
|
@@ -260,6 +245,14 @@ async function preflightEmittedModels(emitted, emitWarnings, ctx, opts) {
|
|
|
260
245
|
// reconciliation. A re-emitted entry is never a deletion candidate, so a
|
|
261
246
|
// pre-write snapshot is correct for all three.
|
|
262
247
|
const cat = new dataplex_1.CatalogClient(ctx);
|
|
248
|
+
// The listing reads a missing group as empty, so read the group itself.
|
|
249
|
+
const group = await cat.getEntryGroup(opts.project, opts.location, opts.entryGroup)
|
|
250
|
+
.catch((err) => `reading entry group '${opts.entryGroup}': ${err?.message || String(err)}`);
|
|
251
|
+
if (typeof group === 'string')
|
|
252
|
+
return { result: result(false, group) };
|
|
253
|
+
if (isNotFoundStatus(group)) {
|
|
254
|
+
return { result: result(false, missingEntryGroupMessage(opts)) };
|
|
255
|
+
}
|
|
263
256
|
const listing = await listEntryGroup(cat, opts);
|
|
264
257
|
if (listing.error)
|
|
265
258
|
return { result: result(false, listing.error) };
|
|
@@ -315,6 +308,14 @@ function oneModelPerEntryGroupMessage(entryGroup, models) {
|
|
|
315
308
|
`only one model per entry group is supported; split them into ` +
|
|
316
309
|
`separate entry groups.`;
|
|
317
310
|
}
|
|
311
|
+
/** The error for a push to an entry group that does not exist. */
|
|
312
|
+
function missingEntryGroupMessage(opts) {
|
|
313
|
+
const { project, location, entryGroup } = opts;
|
|
314
|
+
return `entry group '${entryGroup}' does not exist in location ` +
|
|
315
|
+
`'${location}' of project '${project}'; run 'kcmd init ` +
|
|
316
|
+
`--semantic-model ${project}.${location}.${entryGroup}' to create it, ` +
|
|
317
|
+
`or correct the scope in catalog.yaml.`;
|
|
318
|
+
}
|
|
318
319
|
/**
|
|
319
320
|
* Validates that `existing` (the destination entry-group snapshot) contains no
|
|
320
321
|
* foreign `semantic-model` anchors unless `--force-remove` is set.
|
|
@@ -339,62 +340,9 @@ function validateEntryGroup(model, existing, opts, result) {
|
|
|
339
340
|
exports.MAX_ASPECT_DATA_BYTES = 120 * 1024;
|
|
340
341
|
const MAX_SCHEMA_ASPECT_DATA_BYTES = 2 * 1024 * 1024;
|
|
341
342
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
* rather than partway through the writes. Push runs the same check on its
|
|
346
|
-
* copies, with `checkFileCopies`, before any graph deploys.
|
|
347
|
-
*/
|
|
348
|
-
function validateFileCopySizes(model, result) {
|
|
349
|
-
for (const entry of model.resources.entries) {
|
|
350
|
-
if ((0, kc_entries_1.entryTypeId)(entry) !== kc_custom_types_1.FILE_COPY_TYPE_ID)
|
|
351
|
-
continue;
|
|
352
|
-
for (const aspect of Object.values(entry.aspects ?? {})) {
|
|
353
|
-
const error = fileCopySizeError(aspect.data ?? {});
|
|
354
|
-
if (error)
|
|
355
|
-
return result(false, error);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return result(true);
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Checks a push's file copies before anything is written. Returns the error
|
|
362
|
-
* to report, or undefined when every copy fits in one aspect and no two file
|
|
363
|
-
* names map to one entry id.
|
|
364
|
-
*
|
|
365
|
-
* Push calls it before any graph deploys. Every copy of a model shares the
|
|
366
|
-
* model's part of the entry id, so two file names collide when their slugs
|
|
367
|
-
* do.
|
|
368
|
-
*/
|
|
369
|
-
function checkFileCopies(files) {
|
|
370
|
-
const bySlug = new Map();
|
|
371
|
-
for (const file of files) {
|
|
372
|
-
const tooLarge = fileCopySizeError(kcEmit.fileCopyData(file));
|
|
373
|
-
if (tooLarge)
|
|
374
|
-
return tooLarge;
|
|
375
|
-
const earlier = bySlug.get((0, kc_ids_1.slug)(file.fileName));
|
|
376
|
-
if (earlier !== undefined) {
|
|
377
|
-
return `files '${earlier}' and '${file.fileName}' both map to one ` +
|
|
378
|
-
`entry id; rename one of them`;
|
|
379
|
-
}
|
|
380
|
-
bySlug.set((0, kc_ids_1.slug)(file.fileName), file.fileName);
|
|
381
|
-
}
|
|
382
|
-
return undefined;
|
|
383
|
-
}
|
|
384
|
-
// Why the aspect data of one file copy is too large to store, or undefined
|
|
385
|
-
// when it fits.
|
|
386
|
-
function fileCopySizeError(data) {
|
|
387
|
-
const bytes = new TextEncoder().encode(JSON.stringify(data)).length;
|
|
388
|
-
if (bytes <= exports.MAX_ASPECT_DATA_BYTES)
|
|
389
|
-
return undefined;
|
|
390
|
-
return `file '${data.fileName}' is too large to store in Knowledge ` +
|
|
391
|
-
`Catalog: its copy takes ${bytes} bytes, and one aspect holds at most ` +
|
|
392
|
-
`${exports.MAX_ASPECT_DATA_BYTES}.`;
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* The emitter's warnings for `models` with v2Aspects on, and why push's
|
|
396
|
-
* catalog leg cannot write them: an emit error or an aspect too large to
|
|
397
|
-
* store. Calls no service.
|
|
343
|
+
* The emitter's warnings for `models`, and an emit error or the first aspect
|
|
344
|
+
* too large to store. With v2Aspects on, that error is why push's catalog leg
|
|
345
|
+
* cannot write them. Calls no service.
|
|
398
346
|
*/
|
|
399
347
|
function checkCatalogAspects(models, opts) {
|
|
400
348
|
const emit = emitModels(models, opts);
|
|
@@ -446,8 +394,7 @@ function aspectTypeIdOfKey(key) {
|
|
|
446
394
|
}
|
|
447
395
|
/**
|
|
448
396
|
* Validates that every kcmd-owned custom type (`semantic-action`,
|
|
449
|
-
* `semantic-constraint
|
|
450
|
-
* target project.
|
|
397
|
+
* `semantic-constraint`) used by `model` exists in the target project.
|
|
451
398
|
*
|
|
452
399
|
* Unlike built-in system types in `dataplex-types`, custom entry and aspect
|
|
453
400
|
* types are provisioned in the destination project by `kcmd init`;
|
|
@@ -455,22 +402,15 @@ function aspectTypeIdOfKey(key) {
|
|
|
455
402
|
* before any catalog entries or entry links are written, rather than failing
|
|
456
403
|
* mid-push when creating an action or constraint entry.
|
|
457
404
|
*
|
|
458
|
-
*
|
|
459
|
-
* the caller has already looked it up. Makes no network calls when no type
|
|
460
|
-
* is left to check.
|
|
405
|
+
* Makes no network calls when the model uses no custom type.
|
|
461
406
|
*/
|
|
462
407
|
async function validateCustomTypes(cat, model, opts, result) {
|
|
463
|
-
const
|
|
464
|
-
const error = await checkCustomTypes(cat, opts, typeIds);
|
|
408
|
+
const error = await checkCustomTypes(cat, opts, (0, kc_custom_types_1.collectCustomTypes)(model.resources.entries));
|
|
465
409
|
return error ? result(false, error) : result(true);
|
|
466
410
|
}
|
|
467
411
|
/**
|
|
468
412
|
* Checks that each custom type in `typeIds` exists in the destination
|
|
469
413
|
* project. Returns the error to report, or undefined when every one exists.
|
|
470
|
-
*
|
|
471
|
-
* Push also calls it before any graph deploys, for the file-copy type every
|
|
472
|
-
* push with v2Aspects off needs. Push then sets `fileCopyTypeChecked`, so the
|
|
473
|
-
* catalog preflight looks up only the other custom types.
|
|
474
414
|
*/
|
|
475
415
|
async function checkCustomTypes(cat, dest, typeIds) {
|
|
476
416
|
const home = (0, kc_custom_types_1.customTypeHome)(dest);
|
|
@@ -517,7 +457,6 @@ function emitModels(models, opts) {
|
|
|
517
457
|
systemTypeLocation: opts.systemTypeLocation,
|
|
518
458
|
emitExpressions: opts.emitExpressions,
|
|
519
459
|
v2Aspects: opts.v2Aspects,
|
|
520
|
-
sourceFiles: opts.sourceFiles,
|
|
521
460
|
overviews: opts.overviews,
|
|
522
461
|
});
|
|
523
462
|
}
|
|
@@ -571,7 +510,8 @@ function planSummary(model, resources, opts) {
|
|
|
571
510
|
* resources in model order.
|
|
572
511
|
*
|
|
573
512
|
* For each model, the method performs the following actions:
|
|
574
|
-
* 1. Creates or updates the model's entries (anchor first
|
|
513
|
+
* 1. Creates or updates the model's entries (anchor first, labeled as
|
|
514
|
+
* writing).
|
|
575
515
|
* 2. Looks up the model's existing relationship links when the pre-write
|
|
576
516
|
* entry group contains entities owned by the model.
|
|
577
517
|
* 3. Deletes any orphaned relationship links (no longer emitted) and stale
|
|
@@ -581,6 +521,7 @@ function planSummary(model, resources, opts) {
|
|
|
581
521
|
* 4. Creates or updates the model's relationship links.
|
|
582
522
|
* 5. Deletes any existing entries owned by the model that are no longer
|
|
583
523
|
* emitted.
|
|
524
|
+
* 6. Labels the anchor finished.
|
|
584
525
|
*
|
|
585
526
|
* Progress accumulates into `counts`, so a mid-way failure still reports what
|
|
586
527
|
* had been written.
|
|
@@ -684,9 +625,40 @@ async function writeEmittedModels(cat, opts, emitted, existing, counts, isScopeP
|
|
|
684
625
|
error: `Deleting orphaned entry '${entryId}' from model '${model}' failed: ${message}`,
|
|
685
626
|
};
|
|
686
627
|
}
|
|
628
|
+
if (resources.entries.length) {
|
|
629
|
+
const anchor = resources.entries[0];
|
|
630
|
+
const error = await finishPush(cat, opts, anchor)
|
|
631
|
+
.catch((err) => String(err?.message || err).trim() || 'unknown error');
|
|
632
|
+
if (error) {
|
|
633
|
+
return {
|
|
634
|
+
error: `Model '${model}': setting the ${kc_custom_types_1.PUSH_STATE_LABEL} label of ` +
|
|
635
|
+
`entry '${(0, kc_entries_1.entryId)(anchor)}' to ${kc_custom_types_1.PUSH_FINISHED} failed: ` +
|
|
636
|
+
`${error.replace(/\.$/, '')}. Push again before you pull.`,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
687
640
|
}
|
|
688
641
|
return {};
|
|
689
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Labels the model entry `anchor` finished, and returns the error when that
|
|
645
|
+
* fails. It reads the entry first and sends back the entrySource the entry
|
|
646
|
+
* holds then, so what another writer set during the push stays. The update
|
|
647
|
+
* names only the entrySource.
|
|
648
|
+
*/
|
|
649
|
+
async function finishPush(cat, opts, anchor) {
|
|
650
|
+
const read = await cat.getEntry(opts.project, opts.location, opts.entryGroup, (0, kc_entries_1.entryId)(anchor), undefined, { keepSourceResource: true });
|
|
651
|
+
if (!isOkStatus(read))
|
|
652
|
+
return errText(read);
|
|
653
|
+
const { name, entryType } = anchor;
|
|
654
|
+
const entrySource = withPushState(read.result?.entrySource, kc_custom_types_1.PUSH_FINISHED);
|
|
655
|
+
const res = await cat.updateEntry({ name, entryType, entrySource }, ['entry_source']);
|
|
656
|
+
return isOkStatus(res) ? undefined : errText(res);
|
|
657
|
+
}
|
|
658
|
+
// `source` with its kcmd-push-state label set to `state`.
|
|
659
|
+
function withPushState(source, state) {
|
|
660
|
+
return { ...source, labels: { ...source?.labels, [kc_custom_types_1.PUSH_STATE_LABEL]: state } };
|
|
661
|
+
}
|
|
690
662
|
/**
|
|
691
663
|
* Returns the push-again hint for an error, or '' when no link is missing.
|
|
692
664
|
*
|
|
@@ -1032,8 +1004,8 @@ async function removeForeignModels(cat, opts, existing, foreignAnchorIds, isScop
|
|
|
1032
1004
|
* Creates or updates a model's entries in dependency order.
|
|
1033
1005
|
*
|
|
1034
1006
|
* The method writes entries in three stages:
|
|
1035
|
-
* 1. Writes the model anchor (`entries[0]`) first,
|
|
1036
|
-
* every child entry.
|
|
1007
|
+
* 1. Writes the model anchor (`entries[0]`) first, labeled as writing, since
|
|
1008
|
+
* it is the parent of every child entry.
|
|
1037
1009
|
* 2. Writes independent child entries (such as entities, relationships,
|
|
1038
1010
|
* actions, and constraints) concurrently.
|
|
1039
1011
|
* 3. Writes dependent child entries (`semantic-metric` via
|
|
@@ -1051,7 +1023,8 @@ async function writeEntries(cat, opts, entries, existing = []) {
|
|
|
1051
1023
|
const sources = new Map(existing.map(e => [(0, kc_entries_1.entryId)(e), e.entrySource]));
|
|
1052
1024
|
const write = (e) => writeEntry(cat, opts, e, sources);
|
|
1053
1025
|
const [anchor, ...children] = entries;
|
|
1054
|
-
const
|
|
1026
|
+
const entrySource = withPushState(anchor.entrySource, kc_custom_types_1.PUSH_WRITING);
|
|
1027
|
+
const anchorRes = await write({ ...anchor, entrySource });
|
|
1055
1028
|
if (anchorRes.error)
|
|
1056
1029
|
return { created: 0, updated: 0, error: anchorRes.error };
|
|
1057
1030
|
let created = anchorRes.updated ? 0 : 1;
|
|
@@ -1314,18 +1287,16 @@ const KEPT_SOURCE_FIELDS = ['resource', 'system', 'platform', 'createTime', 'upd
|
|
|
1314
1287
|
*
|
|
1315
1288
|
* The update mask replaces the whole entrySource, so the update carries over
|
|
1316
1289
|
* fields from `source`, the entrySource the entry had before this push. Of
|
|
1317
|
-
* those fields kcmd owns the display name
|
|
1318
|
-
*
|
|
1319
|
-
* `
|
|
1320
|
-
* `kcmd-push-
|
|
1321
|
-
*
|
|
1322
|
-
*
|
|
1323
|
-
*
|
|
1324
|
-
* update fails instead.
|
|
1290
|
+
* those fields kcmd owns the display name and the description. The update
|
|
1291
|
+
* keeps another writer's `resource`, `system`, `platform`, `createTime` and
|
|
1292
|
+
* `updateTime`, and every label. A label `entry` carries, such as the model
|
|
1293
|
+
* entry's `kcmd-push-state`, replaces the stored one. Dataplex refuses an
|
|
1294
|
+
* update that leaves out an `updateTime` the entry has, or moves it back. A
|
|
1295
|
+
* kept field another writer changes after `source` was read is written back
|
|
1296
|
+
* with the old value. For `updateTime` the update fails instead.
|
|
1325
1297
|
*/
|
|
1326
1298
|
async function updateEntry(cat, opts, entry, source) {
|
|
1327
|
-
const
|
|
1328
|
-
const labels = { ...others, ...entry.entrySource?.labels };
|
|
1299
|
+
const labels = { ...source?.labels, ...entry.entrySource?.labels };
|
|
1329
1300
|
const kept = Object.fromEntries(KEPT_SOURCE_FIELDS.filter(k => source?.[k] !== undefined)
|
|
1330
1301
|
.map(k => [k, source[k]]));
|
|
1331
1302
|
const merged = {
|
|
@@ -7,6 +7,16 @@ export interface MetricPlacementInput {
|
|
|
7
7
|
entityByName: ReadonlyMap<string, Entity>;
|
|
8
8
|
skipped: ReadonlySet<string>;
|
|
9
9
|
ancestorsUsed: ReadonlySet<string>;
|
|
10
|
+
renderedFieldSql(entity: Entity, field: Field): {
|
|
11
|
+
sql: string;
|
|
12
|
+
} | {
|
|
13
|
+
reads: string;
|
|
14
|
+
};
|
|
15
|
+
localSql(entity: Entity, sql: string): {
|
|
16
|
+
sql: string;
|
|
17
|
+
} | {
|
|
18
|
+
reads: string;
|
|
19
|
+
};
|
|
10
20
|
}
|
|
11
21
|
export interface GraphEngine {
|
|
12
22
|
name: string;
|