@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
@@ -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
- // and that each file copy fits in one aspect, snapshots the destination
24
- // entry group to guard against foreign models (unless `--force-remove` is
25
- // set), and checks that any required custom entry/aspect types exist in
26
- // the target project. With the second-generation aspects, it also checks
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 and that each file
113
- * copy fits in one aspect, builds the dry-run plan, and returns early
114
- * when `opts.validateOnly` is set.
115
- * 3. Lists the destination entry group and fails if an unrecognized model's
116
- * anchor entry is present without `--force-remove`.
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. Skips the file-copy type when
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
- // This runs before any of this model's writes. A failure here leaves
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 failed(removed.error);
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 failed(written.error);
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, that each
227
- * file copy fits in one aspect and, with `opts.v2Aspects` set, that every
228
- * aspect size and instance count is within its limit. Builds the
229
- * dry-run plan and returns early when `opts.validateOnly` is set.
230
- * 2. Lists the destination entry group and validates that it contains no
231
- * foreign models unless `--force-remove` is set.
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. Skips the file-copy type when
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
- * Validates that every file copy in `model` fits in one aspect.
343
- *
344
- * A file too large to store then fails before any write, naming the file,
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`, `semantic-model-file`) used by `model` exists in the
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
- * Skips the file-copy type when `opts.fileCopyTypeChecked` is set, because
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 typeIds = (0, kc_custom_types_1.collectCustomTypes)(model.resources.entries).filter(id => !(opts.fileCopyTypeChecked && id === kc_custom_types_1.FILE_COPY_TYPE_ID));
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, since it is the parent of
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 anchorRes = await write(anchor);
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, the description and the
1318
- * `kcmd-push-id` label. The update keeps another writer's `resource`,
1319
- * `system`, `platform`, `createTime` and `updateTime`, and every label but
1320
- * `kcmd-push-id`. It sets that label when `entry` carries it and drops it
1321
- * when not. Dataplex refuses an update that leaves out an `updateTime` the
1322
- * entry has, or moves it back. A kept field another writer changes after
1323
- * `source` was read is written back with the old value. For `updateTime` the
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 { [kc_custom_types_1.PUSH_ID_LABEL]: _own, ...others } = source?.labels ?? {};
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;