@itwin/imodel-transformer 1.0.0-dev.17 → 1.0.0-dev.18

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.
@@ -19,8 +19,6 @@ const core_common_1 = require("@itwin/core-common");
19
19
  const IModelExporter_1 = require("./IModelExporter");
20
20
  const IModelImporter_1 = require("./IModelImporter");
21
21
  const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
22
- const PendingReferenceMap_1 = require("./PendingReferenceMap");
23
- const EntityMap_1 = require("./EntityMap");
24
22
  const IModelCloneContext_1 = require("./IModelCloneContext");
25
23
  const EntityUnifier_1 = require("./EntityUnifier");
26
24
  const Algo_1 = require("./Algo");
@@ -31,30 +29,6 @@ const nullLastProvenanceEntityInfo = {
31
29
  aspectVersion: "",
32
30
  aspectKind: core_backend_1.ExternalSourceAspect.Kind.Element,
33
31
  };
34
- /**
35
- * A container for tracking the state of a partially committed entity and finalizing it when it's ready to be fully committed
36
- * @internal
37
- */
38
- class PartiallyCommittedEntity {
39
- constructor(
40
- /**
41
- * A set of "model|element ++ ID64" pairs, (e.g. `model0x11` or `element0x12`)
42
- * It is possible for the submodel of an element to be separately resolved from the actual element,
43
- * so its resolution must be tracked separately
44
- */
45
- _missingReferences, _onComplete) {
46
- this._missingReferences = _missingReferences;
47
- this._onComplete = _onComplete;
48
- }
49
- resolveReference(id) {
50
- this._missingReferences.delete(id);
51
- if (this._missingReferences.size === 0)
52
- this._onComplete();
53
- }
54
- forceComplete() {
55
- this._onComplete();
56
- }
57
- }
58
32
  /**
59
33
  * Apply a function to each Id64 in a supported container type of Id64s.
60
34
  * Currently only supports raw Id64String or RelatedElement-like objects containing an `id` property that is a Id64String,
@@ -217,13 +191,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
217
191
  */
218
192
  constructor(source, target, options) {
219
193
  super();
220
- /** map of (unprocessed element, referencing processed element) pairs to the partially committed element that needs the reference resolved
221
- * and have some helper methods below for now */
222
- this._pendingReferences = new PendingReferenceMap_1.PendingReferenceMap();
223
194
  /** a set of elements for which source provenance will be explicitly tracked by ExternalSourceAspects */
224
195
  this._elementsWithExplicitlyTrackedProvenance = new Set();
225
- /** map of partially committed entities to their partial commit progress */
226
- this._partiallyCommittedEntities = new EntityMap_1.EntityMap();
196
+ this._partiallyCommittedElementIds = new Set();
197
+ this._partiallyCommittedAspectIds = new Set();
227
198
  /**
228
199
  * A private variable meant to be set by tests which have an outdated way of setting up transforms. In all synchronizations today we expect to find an ESA in the branch db which describes the master -> branch relationship.
229
200
  * The exception to this is the first transform aka the provenance initializing transform which requires that the master imodel and the branch imodel are identical at the time of provenance initialization.
@@ -232,10 +203,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
232
203
  */
233
204
  this._allowNoScopingESA = false;
234
205
  this._changesetRanges = undefined;
235
- /** Set of entity keys which were not exported and don't need to be tracked for pending reference resolution.
236
- * @note Currently only tracks elements which were not exported.
237
- */
238
- this._skippedEntities = new Set();
239
206
  /**
240
207
  * Previously the transformer would insert provenance always pointing to the "target" relationship.
241
208
  * It should (and now by default does) instead insert provenance pointing to the provenanceSource
@@ -954,80 +921,69 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
954
921
  nodeAssert(this._hasElementChangedCache !== undefined, "has element changed cache should be initialized by now");
955
922
  return this._hasElementChangedCache.has(sourceElement.id);
956
923
  }
957
- static transformCallbackFor(transformer, entity) {
958
- if (entity instanceof core_backend_1.Element)
959
- return transformer.onTransformElement; // eslint-disable-line @typescript-eslint/unbound-method
960
- else if (entity instanceof core_backend_1.Element)
961
- return transformer.onTransformModel; // eslint-disable-line @typescript-eslint/unbound-method
962
- else if (entity instanceof core_backend_1.Relationship)
963
- return transformer.onTransformRelationship; // eslint-disable-line @typescript-eslint/unbound-method
964
- else if (entity instanceof core_backend_1.ElementAspect)
965
- return transformer.onTransformElementAspect; // eslint-disable-line @typescript-eslint/unbound-method
966
- else
967
- (0, core_bentley_1.assert)(false, `unreachable; entity was '${entity.constructor.name}' not an Element, Relationship, or ElementAspect`);
968
- }
969
- /** callback to perform when a partial element says it's ready to be completed
970
- * transforms the source element with all references now valid, then updates the partial element with the results
971
- */
972
- makePartialEntityCompleter(sourceEntity) {
973
- return () => {
974
- const targetId = this.context.findTargetEntityId(core_backend_1.EntityReferences.from(sourceEntity));
975
- if (!core_backend_1.EntityReferences.isValid(targetId))
976
- throw Error(`${sourceEntity.id} has not been inserted into the target yet, the completer is invalid. This is a bug.`);
977
- const onEntityTransform = IModelTransformer.transformCallbackFor(this, sourceEntity);
978
- const updateEntity = EntityUnifier_1.EntityUnifier.updaterFor(this.targetDb, sourceEntity);
979
- const targetProps = onEntityTransform.call(this, sourceEntity);
980
- if (sourceEntity instanceof core_backend_1.Relationship) {
981
- targetProps.sourceId =
982
- this.context.findTargetElementId(sourceEntity.sourceId);
983
- targetProps.targetId =
984
- this.context.findTargetElementId(sourceEntity.targetId);
924
+ completePartiallyCommittedElements() {
925
+ for (const sourceElementId of this._partiallyCommittedElementIds) {
926
+ const sourceElement = this.sourceDb.elements.getElement({
927
+ id: sourceElementId,
928
+ wantGeometry: this.exporter.wantGeometry,
929
+ wantBRepData: this.exporter.wantGeometry,
930
+ });
931
+ const targetId = this.context.findTargetElementId(sourceElementId);
932
+ if (core_bentley_1.Id64.isInvalid(targetId)) {
933
+ throw new Error(`source-target element mapping not found for element "${sourceElementId}" when completing partially committed elements. This is a bug.`);
985
934
  }
986
- updateEntity({ ...targetProps, id: core_backend_1.EntityReferences.toId64(targetId) });
987
- this._partiallyCommittedEntities.delete(sourceEntity);
988
- };
935
+ const targetProps = this.onTransformElement(sourceElement);
936
+ this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
937
+ }
989
938
  }
990
- /** collect references this entity has that are yet to be mapped, and if there are any
991
- * create a [[PartiallyCommittedEntity]] to track resolution of those references
992
- */
993
- collectUnmappedReferences(entity) {
994
- const missingReferences = new core_common_1.EntityReferenceSet();
995
- let thisPartialElem;
996
- // eslint-disable-next-line deprecation/deprecation
997
- for (const referenceId of entity.getReferenceConcreteIds()) {
998
- // TODO: probably need to rename from 'id' to 'ref' so these names aren't so ambiguous
999
- const referenceIdInTarget = this.context.findTargetEntityId(referenceId);
1000
- const alreadyProcessed = core_backend_1.EntityReferences.isValid(referenceIdInTarget) ||
1001
- this._skippedEntities.has(referenceId);
1002
- if (alreadyProcessed)
1003
- continue;
1004
- core_bentley_1.Logger.logTrace(loggerCategory, `Deferring resolution of reference '${referenceId}' of element '${entity.id}'`);
1005
- const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
1006
- entityReference: referenceId,
939
+ completePartiallyCommittedAspects() {
940
+ for (const sourceAspectId of this._partiallyCommittedAspectIds) {
941
+ const sourceAspect = this.sourceDb.elements.getAspect(sourceAspectId);
942
+ const targetAspectId = this.context.findTargetAspectId(sourceAspectId);
943
+ if (core_bentley_1.Id64.isInvalid(targetAspectId)) {
944
+ throw new Error(`source-target aspect mapping not found for aspect "${sourceAspectId}" when completing partially committed aspects. This is a bug.`);
945
+ }
946
+ const targetAspectProps = this.onTransformElementAspect(sourceAspect);
947
+ this.targetDb.elements.updateAspect({
948
+ ...targetAspectProps,
949
+ id: targetAspectId,
1007
950
  });
1008
- if (!referencedExistsInSource) {
1009
- core_bentley_1.Logger.logWarning(loggerCategory, `Source ${EntityUnifier_1.EntityUnifier.getReadableType(entity)} (${entity.id}) has a dangling reference to (${referenceId})`);
1010
- switch (this._options.danglingReferencesBehavior) {
1011
- case "ignore":
1012
- continue;
1013
- case "reject":
1014
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, [
1015
- `Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
1016
- "This must have been caused by an upstream application that changed the iModel.",
1017
- "You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this, but this will leave the iModel",
1018
- "in a state where downstream consuming applications will need to handle the invalidity themselves. In some cases, writing a custom",
1019
- "transformer to remove the reference and fix affected elements may be suitable.",
1020
- ].join("\n"));
951
+ }
952
+ }
953
+ doAllReferencesExistInTarget(entity) {
954
+ let allReferencesExist = true;
955
+ for (const referenceId of entity.getReferenceIds()) {
956
+ const referencedEntityId = core_backend_1.EntityReferences.toId64(referenceId);
957
+ if (referencedEntityId === core_common_1.IModel.repositoryModelId ||
958
+ referencedEntityId === core_common_1.IModel.dictionaryId ||
959
+ referencedEntityId === "0xe") {
960
+ continue;
961
+ }
962
+ if (allReferencesExist &&
963
+ !core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
964
+ // if we care about references existing then we cannot return early and must check all other references.
965
+ if (this._options.danglingReferencesBehavior === "ignore") {
966
+ return false;
1021
967
  }
968
+ allReferencesExist = false;
1022
969
  }
1023
- if (thisPartialElem === undefined) {
1024
- thisPartialElem = new PartiallyCommittedEntity(missingReferences, this.makePartialEntityCompleter(entity));
1025
- if (!this._partiallyCommittedEntities.has(entity))
1026
- this._partiallyCommittedEntities.set(entity, thisPartialElem);
970
+ if (this._options.danglingReferencesBehavior === "reject") {
971
+ this.assertReferenceExistsInSource(referenceId, entity);
1027
972
  }
1028
- missingReferences.add(referenceId);
1029
- const entityReference = core_backend_1.EntityReferences.from(entity);
1030
- this._pendingReferences.set({ referenced: referenceId, referencer: entityReference }, thisPartialElem);
973
+ }
974
+ return allReferencesExist;
975
+ }
976
+ assertReferenceExistsInSource(referenceId, entity) {
977
+ const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
978
+ entityReference: referenceId,
979
+ });
980
+ if (!referencedExistsInSource) {
981
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, [
982
+ `Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
983
+ "This must have been caused by an upstream application that changed the iModel.",
984
+ "You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this,",
985
+ `and the referenceId found on "${entity.id}" will not be carried over to corresponding target element.`,
986
+ ].join("\n"));
1031
987
  }
1032
988
  }
1033
989
  /** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
@@ -1055,24 +1011,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1055
1011
  shouldExportElement(_sourceElement) {
1056
1012
  return true;
1057
1013
  }
1058
- onSkipElement(sourceElementId) {
1059
- if (this.context.findTargetElementId(sourceElementId) !== core_bentley_1.Id64.invalid) {
1060
- // element already has provenance
1061
- return;
1062
- }
1063
- core_bentley_1.Logger.logInfo(loggerCategory, `Element '${sourceElementId}' won't be exported. Marking its references as resolved`);
1064
- const elementKey = `e${sourceElementId}`;
1065
- this._skippedEntities.add(elementKey);
1066
- // Mark any existing pending references to the skipped element as resolved.
1067
- for (const referencer of this._pendingReferences.getReferencersByEntityKey(elementKey)) {
1068
- const key = PendingReferenceMap_1.PendingReference.from(referencer, elementKey);
1069
- const pendingRef = this._pendingReferences.get(key);
1070
- if (!pendingRef)
1071
- continue;
1072
- pendingRef.resolveReference(elementKey);
1073
- this._pendingReferences.delete(key);
1074
- }
1075
- }
1076
1014
  /**
1077
1015
  * If they haven't been already, import all of the required references
1078
1016
  * @internal do not call, override or implement this, it will be removed
@@ -1182,7 +1120,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1182
1120
  }
1183
1121
  if (!this.hasElementChanged(sourceElement))
1184
1122
  return;
1185
- this.collectUnmappedReferences(sourceElement);
1123
+ if (!this.doAllReferencesExistInTarget(sourceElement)) {
1124
+ this._partiallyCommittedElementIds.add(sourceElement.id);
1125
+ }
1186
1126
  // targetElementId will be valid (indicating update) or undefined (indicating insert)
1187
1127
  targetElementProps.id = core_bentley_1.Id64.isValid(targetElementId)
1188
1128
  ? targetElementId
@@ -1191,8 +1131,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1191
1131
  this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
1192
1132
  }
1193
1133
  this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
1194
- // now that we've mapped this elem we can fix unmapped references to it
1195
- this.resolvePendingReferences(sourceElement);
1196
1134
  // the transformer does not currently 'split' or 'join' any elements, therefore, it does not
1197
1135
  // insert external source aspects because federation guids are sufficient for this.
1198
1136
  // Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
@@ -1220,16 +1158,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1220
1158
  this.markLastProvenance(provenance, { isRelationship: false });
1221
1159
  }
1222
1160
  }
1223
- resolvePendingReferences(entity) {
1224
- for (const referencer of this._pendingReferences.getReferencers(entity)) {
1225
- const key = PendingReferenceMap_1.PendingReference.from(referencer, entity);
1226
- const pendingRef = this._pendingReferences.get(key);
1227
- if (!pendingRef)
1228
- continue;
1229
- pendingRef.resolveReference(core_backend_1.EntityReferences.from(entity));
1230
- this._pendingReferences.delete(key);
1231
- }
1232
- }
1233
1161
  /** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
1234
1162
  * This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
1235
1163
  */
@@ -1254,7 +1182,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1254
1182
  return;
1255
1183
  const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
1256
1184
  this.importer.importModel(targetModelProps);
1257
- this.resolvePendingReferences(sourceModel);
1258
1185
  }
1259
1186
  /** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
1260
1187
  onDeleteModel(sourceModelId) {
@@ -1488,20 +1415,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1488
1415
  this.updateSynchronizationVersion({
1489
1416
  initializeReverseSyncVersion: this._isProvenanceInitTransform,
1490
1417
  });
1491
- if (this._partiallyCommittedEntities.size > 0) {
1492
- const message = [
1493
- "The following elements were never fully resolved:",
1494
- [...this._partiallyCommittedEntities.keys()].join(","),
1495
- "This indicates that either some references were excluded from the transformation",
1496
- "or the source has dangling references.",
1497
- ].join("\n");
1498
- if (this._options.danglingReferencesBehavior === "reject")
1499
- throw new Error(message);
1500
- core_bentley_1.Logger.logWarning(loggerCategory, message);
1501
- for (const partiallyCommittedElem of this._partiallyCommittedEntities.values()) {
1502
- partiallyCommittedElem.forceComplete();
1503
- }
1504
- }
1505
1418
  // TODO: ignore if we remove change cache usage
1506
1419
  if (!this._options.noDetachChangeCache) {
1507
1420
  if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
@@ -1658,22 +1571,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1658
1571
  * This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
1659
1572
  */
1660
1573
  onExportElementUniqueAspect(sourceAspect) {
1661
- const targetElementId = this.context.findTargetElementId(sourceAspect.element.id);
1662
- const targetAspectProps = this.onTransformElementAspect(sourceAspect, targetElementId);
1663
- this.collectUnmappedReferences(sourceAspect);
1574
+ const targetAspectProps = this.onTransformElementAspect(sourceAspect);
1575
+ if (!this.doAllReferencesExistInTarget(sourceAspect)) {
1576
+ this._partiallyCommittedAspectIds.add(sourceAspect.id);
1577
+ }
1664
1578
  const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
1665
1579
  this.context.remapElementAspect(sourceAspect.id, targetId);
1666
- this.resolvePendingReferences(sourceAspect);
1667
1580
  }
1668
1581
  /** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
1669
1582
  * This override calls [[onTransformElementAspect]] for each ElementMultiAspect and then [IModelImporter.importElementMultiAspects]($transformer) to update the target iModel.
1670
1583
  * @note ElementMultiAspects are handled as a group to make it easier to differentiate between insert, update, and delete.
1671
1584
  */
1672
1585
  onExportElementMultiAspects(sourceAspects) {
1673
- const targetElementId = this.context.findTargetElementId(sourceAspects[0].element.id);
1674
1586
  // Transform source ElementMultiAspects into target ElementAspectProps
1675
- const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA, targetElementId));
1676
- sourceAspects.forEach((a) => this.collectUnmappedReferences(a));
1587
+ const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA));
1588
+ sourceAspects.forEach((a) => {
1589
+ if (!this.doAllReferencesExistInTarget(a)) {
1590
+ this._partiallyCommittedAspectIds.add(a.id);
1591
+ }
1592
+ });
1677
1593
  // const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
1678
1594
  const targetIds = this.importer.importElementMultiAspects(targetAspectPropsArray, (a) => {
1679
1595
  const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
@@ -1683,16 +1599,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1683
1599
  });
1684
1600
  for (let i = 0; i < targetIds.length; ++i) {
1685
1601
  this.context.remapElementAspect(sourceAspects[i].id, targetIds[i]);
1686
- this.resolvePendingReferences(sourceAspects[i]);
1687
1602
  }
1688
1603
  }
1689
1604
  /** Transform the specified sourceElementAspect into ElementAspectProps for the target iModel.
1690
1605
  * @param sourceElementAspect The ElementAspect from the source iModel to be transformed.
1691
- * @param _targetElementId The ElementId of the target Element that will own the ElementAspects after transformation.
1692
1606
  * @returns ElementAspectProps for the target iModel.
1693
1607
  * @note A subclass can override this method to provide custom transform behavior.
1694
1608
  */
1695
- onTransformElementAspect(sourceElementAspect, _targetElementId) {
1609
+ onTransformElementAspect(sourceElementAspect) {
1696
1610
  const targetElementAspectProps = this.context.cloneElementAspect(sourceElementAspect);
1697
1611
  return targetElementAspectProps;
1698
1612
  }
@@ -1816,6 +1730,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1816
1730
  this.context.remapElement(sourceSubjectId, targetSubjectId);
1817
1731
  await this.processChildElements(sourceSubjectId);
1818
1732
  await this.processSubjectSubModels(sourceSubjectId);
1733
+ this.completePartiallyCommittedElements();
1734
+ this.completePartiallyCommittedAspects();
1819
1735
  }
1820
1736
  /**
1821
1737
  * Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
@@ -2137,7 +2053,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2137
2053
  else {
2138
2054
  await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
2139
2055
  }
2056
+ this.completePartiallyCommittedElements();
2140
2057
  await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
2058
+ this.completePartiallyCommittedAspects();
2141
2059
  await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
2142
2060
  if (this._options.forceExternalSourceAspectProvenance &&
2143
2061
  this.shouldDetectDeletes()) {
@@ -2176,6 +2094,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2176
2094
  async processChanges(options) {
2177
2095
  // must wait for initialization of synchronization provenance data
2178
2096
  await this.exporter.exportChanges(this.getExportInitOpts(options));
2097
+ this.completePartiallyCommittedElements();
2098
+ this.completePartiallyCommittedAspects();
2179
2099
  if (this._options.optimizeGeometry)
2180
2100
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
2181
2101
  this.importer.computeProjectExtents();