@itwin/imodel-transformer 1.0.0-dev.9 → 1.0.1-customchanges.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/CHANGELOG.md +29 -1
- package/README.md +17 -0
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +2 -0
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/EntityUnifier.d.ts +1 -1
- package/lib/cjs/IModelExporter.d.ts +100 -11
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +160 -29
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +26 -3
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +58 -20
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +119 -140
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +388 -366
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/{transformer.d.ts → imodel-transformer.d.ts} +1 -1
- package/lib/cjs/imodel-transformer.d.ts.map +1 -0
- package/lib/cjs/{transformer.js → imodel-transformer.js} +11 -2
- package/lib/cjs/imodel-transformer.js.map +1 -0
- package/package.json +6 -5
- package/lib/cjs/PendingReferenceMap.d.ts +0 -37
- package/lib/cjs/PendingReferenceMap.d.ts.map +0 -1
- package/lib/cjs/PendingReferenceMap.js +0 -92
- package/lib/cjs/PendingReferenceMap.js.map +0 -1
- package/lib/cjs/transformer.d.ts.map +0 -1
- package/lib/cjs/transformer.js.map +0 -1
|
@@ -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,
|
|
@@ -172,7 +146,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
172
146
|
if (this._isProvenanceInitTransform) {
|
|
173
147
|
return "forward";
|
|
174
148
|
}
|
|
175
|
-
if (!this.
|
|
149
|
+
if (!this._options.argsForProcessChanges) {
|
|
176
150
|
return "not-sync";
|
|
177
151
|
}
|
|
178
152
|
try {
|
|
@@ -217,14 +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
|
-
|
|
226
|
-
this.
|
|
227
|
-
this._isSynchronization = false;
|
|
196
|
+
this._partiallyCommittedElementIds = new Set();
|
|
197
|
+
this._partiallyCommittedAspectIds = new Set();
|
|
228
198
|
/**
|
|
229
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.
|
|
230
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.
|
|
@@ -233,10 +203,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
233
203
|
*/
|
|
234
204
|
this._allowNoScopingESA = false;
|
|
235
205
|
this._changesetRanges = undefined;
|
|
236
|
-
/** Set of entity keys which were not exported and don't need to be tracked for pending reference resolution.
|
|
237
|
-
* @note Currently only tracks elements which were not exported.
|
|
238
|
-
*/
|
|
239
|
-
this._skippedEntities = new Set();
|
|
240
206
|
/**
|
|
241
207
|
* Previously the transformer would insert provenance always pointing to the "target" relationship.
|
|
242
208
|
* It should (and now by default does) instead insert provenance pointing to the provenanceSource
|
|
@@ -276,7 +242,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
276
242
|
// eslint-disable-next-line deprecation/deprecation
|
|
277
243
|
danglingReferencesBehavior: options?.danglingReferencesBehavior ?? "reject",
|
|
278
244
|
branchRelationshipDataBehavior: options?.branchRelationshipDataBehavior ?? "reject",
|
|
245
|
+
skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
|
|
279
246
|
};
|
|
247
|
+
// check if authorization client is defined
|
|
248
|
+
if (core_backend_1.IModelHost.authorizationClient === undefined) {
|
|
249
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "Authorization client is not set in IModelHost. If the transformer needs an accessToken, then it will fail.");
|
|
250
|
+
}
|
|
280
251
|
this._isProvenanceInitTransform = this._options
|
|
281
252
|
.wasSourceIModelCopiedToTarget
|
|
282
253
|
? true
|
|
@@ -360,9 +331,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
360
331
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._includeSourceProvenance=${this._options.includeSourceProvenance}`);
|
|
361
332
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._cloneUsingBinaryGeometry=${this._options.cloneUsingBinaryGeometry}`);
|
|
362
333
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._wasSourceIModelCopiedToTarget=${this._options.wasSourceIModelCopiedToTarget}`);
|
|
363
|
-
core_bentley_1.Logger.logInfo(loggerCategory,
|
|
364
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
365
|
-
`this._isReverseSynchronization=${this._options.isReverseSynchronization}`);
|
|
366
334
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
367
335
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
368
336
|
}
|
|
@@ -605,8 +573,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
605
573
|
let madeChange = false;
|
|
606
574
|
if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
|
|
607
575
|
return madeChange;
|
|
608
|
-
const fallbackSyncVersionToUse = this._options.unsafeFallbackSyncVersion ?? "";
|
|
609
|
-
const fallbackReverseSyncVersionToUse = this._options.unsafeFallbackReverseSyncVersion ??
|
|
576
|
+
const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
|
|
577
|
+
const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
|
|
578
|
+
"";
|
|
610
579
|
if (aspectProps.version === undefined ||
|
|
611
580
|
(aspectProps.version === "" &&
|
|
612
581
|
aspectProps.version !== fallbackSyncVersionToUse)) {
|
|
@@ -744,7 +713,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
744
713
|
targetScopeElementId: this.targetScopeElementId,
|
|
745
714
|
isReverseSynchronization: this.isReverseSynchronization,
|
|
746
715
|
fn,
|
|
747
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ??
|
|
716
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
|
|
748
717
|
});
|
|
749
718
|
}
|
|
750
719
|
/**
|
|
@@ -866,7 +835,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
866
835
|
}
|
|
867
836
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
868
837
|
* @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
|
|
869
|
-
* @note Not relevant for
|
|
838
|
+
* @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
|
|
870
839
|
*/
|
|
871
840
|
shouldDetectDeletes() {
|
|
872
841
|
nodeAssert(this._syncType !== undefined);
|
|
@@ -876,9 +845,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
876
845
|
* Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements
|
|
877
846
|
* in the source iModel.
|
|
878
847
|
* @deprecated in 1.x. Do not use this. // FIXME<MIKE>: how to better explain this?
|
|
879
|
-
* This method is only called during [[
|
|
848
|
+
* This method is only called during [[process]] when [[IModelTransformOptions.argsForProcessChanges]] is undefined and the option
|
|
880
849
|
* [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
|
|
881
|
-
* necessary when
|
|
850
|
+
* necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] defined, since changeset information is sufficient.
|
|
882
851
|
* @note you do not need to call this directly unless processing a subset of an iModel.
|
|
883
852
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
884
853
|
*/
|
|
@@ -908,12 +877,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
908
877
|
}
|
|
909
878
|
});
|
|
910
879
|
}
|
|
911
|
-
/**
|
|
912
|
-
* @deprecated in 3.x, this no longer has any effect except emitting a warning
|
|
913
|
-
*/
|
|
914
|
-
skipElement(_sourceElement) {
|
|
915
|
-
core_bentley_1.Logger.logWarning(loggerCategory, "Tried to defer/skip an element, which is no longer necessary");
|
|
916
|
-
}
|
|
917
880
|
/** Transform the specified sourceElement into ElementProps for the target iModel.
|
|
918
881
|
* @param sourceElement The Element from the source iModel to transform.
|
|
919
882
|
* @returns ElementProps for the target iModel.
|
|
@@ -958,85 +921,74 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
958
921
|
nodeAssert(this._hasElementChangedCache !== undefined, "has element changed cache should be initialized by now");
|
|
959
922
|
return this._hasElementChangedCache.has(sourceElement.id);
|
|
960
923
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
(0, core_bentley_1.assert)(false, `unreachable; entity was '${entity.constructor.name}' not an Element, Relationship, or ElementAspect`);
|
|
972
|
-
}
|
|
973
|
-
/** callback to perform when a partial element says it's ready to be completed
|
|
974
|
-
* transforms the source element with all references now valid, then updates the partial element with the results
|
|
975
|
-
*/
|
|
976
|
-
makePartialEntityCompleter(sourceEntity) {
|
|
977
|
-
return () => {
|
|
978
|
-
const targetId = this.context.findTargetEntityId(core_backend_1.EntityReferences.from(sourceEntity));
|
|
979
|
-
if (!core_backend_1.EntityReferences.isValid(targetId))
|
|
980
|
-
throw Error(`${sourceEntity.id} has not been inserted into the target yet, the completer is invalid. This is a bug.`);
|
|
981
|
-
const onEntityTransform = IModelTransformer.transformCallbackFor(this, sourceEntity);
|
|
982
|
-
const updateEntity = EntityUnifier_1.EntityUnifier.updaterFor(this.targetDb, sourceEntity);
|
|
983
|
-
const targetProps = onEntityTransform.call(this, sourceEntity);
|
|
984
|
-
if (sourceEntity instanceof core_backend_1.Relationship) {
|
|
985
|
-
targetProps.sourceId =
|
|
986
|
-
this.context.findTargetElementId(sourceEntity.sourceId);
|
|
987
|
-
targetProps.targetId =
|
|
988
|
-
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.`);
|
|
989
934
|
}
|
|
990
|
-
|
|
991
|
-
this.
|
|
992
|
-
}
|
|
935
|
+
const targetProps = this.onTransformElement(sourceElement);
|
|
936
|
+
this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
|
|
937
|
+
}
|
|
993
938
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
this._skippedEntities.has(referenceId);
|
|
1006
|
-
if (alreadyProcessed)
|
|
1007
|
-
continue;
|
|
1008
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `Deferring resolution of reference '${referenceId}' of element '${entity.id}'`);
|
|
1009
|
-
const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
|
|
1010
|
-
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,
|
|
1011
950
|
});
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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;
|
|
1025
967
|
}
|
|
968
|
+
allReferencesExist = false;
|
|
1026
969
|
}
|
|
1027
|
-
if (
|
|
1028
|
-
|
|
1029
|
-
if (!this._partiallyCommittedEntities.has(entity))
|
|
1030
|
-
this._partiallyCommittedEntities.set(entity, thisPartialElem);
|
|
970
|
+
if (this._options.danglingReferencesBehavior === "reject") {
|
|
971
|
+
this.assertReferenceExistsInSource(referenceId, entity);
|
|
1031
972
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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"));
|
|
1035
987
|
}
|
|
1036
988
|
}
|
|
1037
989
|
/** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
|
|
1038
990
|
* @param sourceElementId Identifies the Element from the source iModel to import.
|
|
1039
|
-
* @note This method is called from [[
|
|
991
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1040
992
|
*/
|
|
1041
993
|
async processElement(sourceElementId) {
|
|
1042
994
|
await this.initialize();
|
|
@@ -1047,7 +999,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1047
999
|
}
|
|
1048
1000
|
/** Import child elements into the target IModelDb
|
|
1049
1001
|
* @param sourceElementId Import the child elements of this element in the source IModelDb.
|
|
1050
|
-
* @note This method is called from [[
|
|
1002
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1051
1003
|
*/
|
|
1052
1004
|
async processChildElements(sourceElementId) {
|
|
1053
1005
|
await this.initialize();
|
|
@@ -1059,24 +1011,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1059
1011
|
shouldExportElement(_sourceElement) {
|
|
1060
1012
|
return true;
|
|
1061
1013
|
}
|
|
1062
|
-
onSkipElement(sourceElementId) {
|
|
1063
|
-
if (this.context.findTargetElementId(sourceElementId) !== core_bentley_1.Id64.invalid) {
|
|
1064
|
-
// element already has provenance
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `Element '${sourceElementId}' won't be exported. Marking its references as resolved`);
|
|
1068
|
-
const elementKey = `e${sourceElementId}`;
|
|
1069
|
-
this._skippedEntities.add(elementKey);
|
|
1070
|
-
// Mark any existing pending references to the skipped element as resolved.
|
|
1071
|
-
for (const referencer of this._pendingReferences.getReferencersByEntityKey(elementKey)) {
|
|
1072
|
-
const key = PendingReferenceMap_1.PendingReference.from(referencer, elementKey);
|
|
1073
|
-
const pendingRef = this._pendingReferences.get(key);
|
|
1074
|
-
if (!pendingRef)
|
|
1075
|
-
continue;
|
|
1076
|
-
pendingRef.resolveReference(elementKey);
|
|
1077
|
-
this._pendingReferences.delete(key);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
1014
|
/**
|
|
1081
1015
|
* If they haven't been already, import all of the required references
|
|
1082
1016
|
* @internal do not call, override or implement this, it will be removed
|
|
@@ -1143,11 +1077,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1143
1077
|
onExportElement(sourceElement) {
|
|
1144
1078
|
let targetElementId;
|
|
1145
1079
|
let targetElementProps;
|
|
1146
|
-
if (this._options.
|
|
1147
|
-
targetElementId = sourceElement.id;
|
|
1148
|
-
targetElementProps = this.onTransformElement(sourceElement);
|
|
1149
|
-
}
|
|
1150
|
-
else if (this._options.wasSourceIModelCopiedToTarget) {
|
|
1080
|
+
if (this._options.wasSourceIModelCopiedToTarget) {
|
|
1151
1081
|
targetElementId = sourceElement.id;
|
|
1152
1082
|
targetElementProps =
|
|
1153
1083
|
this.targetDb.elements.getElementProps(targetElementId);
|
|
@@ -1186,17 +1116,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1186
1116
|
}
|
|
1187
1117
|
if (!this.hasElementChanged(sourceElement))
|
|
1188
1118
|
return;
|
|
1189
|
-
this.
|
|
1119
|
+
if (!this.doAllReferencesExistInTarget(sourceElement)) {
|
|
1120
|
+
this._partiallyCommittedElementIds.add(sourceElement.id);
|
|
1121
|
+
}
|
|
1190
1122
|
// targetElementId will be valid (indicating update) or undefined (indicating insert)
|
|
1191
1123
|
targetElementProps.id = core_bentley_1.Id64.isValid(targetElementId)
|
|
1192
1124
|
? targetElementId
|
|
1193
1125
|
: undefined;
|
|
1126
|
+
if (this._options.preserveElementIdsForFiltering) {
|
|
1127
|
+
const isValid = core_bentley_1.Id64.isValid(targetElementId);
|
|
1128
|
+
if (isValid && targetElementId !== sourceElement.id) {
|
|
1129
|
+
// Element found with different id
|
|
1130
|
+
throw new Error(`Element id(${sourceElement.id}) cannot be preserved. Found a different mapping(${targetElementId}) from source element`);
|
|
1131
|
+
}
|
|
1132
|
+
else if (isValid && targetElementId === sourceElement.id) {
|
|
1133
|
+
// targetElementId is valid (indicating update)
|
|
1134
|
+
this.importer.markElementToUpdateDuringPreserveIds(sourceElement.id);
|
|
1135
|
+
}
|
|
1136
|
+
else if (!isValid) {
|
|
1137
|
+
const sourceInTargetElemProps = this.targetDb.elements.tryGetElementProps(sourceElement.id);
|
|
1138
|
+
// if we don't find mapping for source element in target(invalid) but another element with source id exists in target
|
|
1139
|
+
if (sourceInTargetElemProps) {
|
|
1140
|
+
// Element id is already taken by another element
|
|
1141
|
+
throw new Error(`Element id(${sourceElement.id}) cannot be preserved. An unrelated element in the target already uses id: ${sourceElement.id}`);
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
// Element id in target is available to be remapped
|
|
1145
|
+
targetElementProps.id = sourceElement.id;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1194
1149
|
if (!this._options.wasSourceIModelCopiedToTarget) {
|
|
1195
1150
|
this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
|
|
1196
1151
|
}
|
|
1197
1152
|
this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
|
|
1198
|
-
// now that we've mapped this elem we can fix unmapped references to it
|
|
1199
|
-
this.resolvePendingReferences(sourceElement);
|
|
1200
1153
|
// the transformer does not currently 'split' or 'join' any elements, therefore, it does not
|
|
1201
1154
|
// insert external source aspects because federation guids are sufficient for this.
|
|
1202
1155
|
// Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
|
|
@@ -1224,16 +1177,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1224
1177
|
this.markLastProvenance(provenance, { isRelationship: false });
|
|
1225
1178
|
}
|
|
1226
1179
|
}
|
|
1227
|
-
resolvePendingReferences(entity) {
|
|
1228
|
-
for (const referencer of this._pendingReferences.getReferencers(entity)) {
|
|
1229
|
-
const key = PendingReferenceMap_1.PendingReference.from(referencer, entity);
|
|
1230
|
-
const pendingRef = this._pendingReferences.get(key);
|
|
1231
|
-
if (!pendingRef)
|
|
1232
|
-
continue;
|
|
1233
|
-
pendingRef.resolveReference(core_backend_1.EntityReferences.from(entity));
|
|
1234
|
-
this._pendingReferences.delete(key);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
1180
|
/** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
|
|
1238
1181
|
* This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
|
|
1239
1182
|
*/
|
|
@@ -1258,7 +1201,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1258
1201
|
return;
|
|
1259
1202
|
const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
|
|
1260
1203
|
this.importer.importModel(targetModelProps);
|
|
1261
|
-
this.resolvePendingReferences(sourceModel);
|
|
1262
1204
|
}
|
|
1263
1205
|
/** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
|
|
1264
1206
|
onDeleteModel(sourceModelId) {
|
|
@@ -1332,7 +1274,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1332
1274
|
}
|
|
1333
1275
|
/** Cause the model container, contents, and sub-models to be exported from the source iModel and imported into the target iModel.
|
|
1334
1276
|
* @param sourceModeledElementId Import this [Model]($backend) from the source IModelDb.
|
|
1335
|
-
* @note This method is called from [[
|
|
1277
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1336
1278
|
*/
|
|
1337
1279
|
async processModel(sourceModeledElementId) {
|
|
1338
1280
|
await this.initialize();
|
|
@@ -1342,7 +1284,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1342
1284
|
* @param sourceModelId Import the contents of this model from the source IModelDb.
|
|
1343
1285
|
* @param targetModelId Import into this model in the target IModelDb. The target model must exist prior to this call.
|
|
1344
1286
|
* @param elementClassFullName Optional classFullName of an element subclass to limit import query against the source model.
|
|
1345
|
-
* @note This method is called from [[
|
|
1287
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1346
1288
|
*/
|
|
1347
1289
|
async processModelContents(sourceModelId, targetModelId, elementClassFullName = core_backend_1.Element.classFullName) {
|
|
1348
1290
|
await this.initialize();
|
|
@@ -1399,46 +1341,50 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1399
1341
|
targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
|
|
1400
1342
|
return targetModelProps;
|
|
1401
1343
|
}
|
|
1402
|
-
/**
|
|
1403
|
-
*
|
|
1404
|
-
*/
|
|
1405
|
-
async processDeferredElements(_numRetries = 3) { }
|
|
1406
|
-
/** called at the end of a transformation,
|
|
1344
|
+
/**
|
|
1345
|
+
* Called at the end of a transformation,
|
|
1407
1346
|
* updates the target scope element to say that transformation up through the
|
|
1408
1347
|
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1409
1348
|
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
1410
1349
|
*
|
|
1411
|
-
* You generally should not call this function yourself and use [[
|
|
1350
|
+
* You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
|
|
1412
1351
|
* It is public for unsupported use cases of custom synchronization transforms.
|
|
1413
|
-
* @note
|
|
1414
|
-
*
|
|
1352
|
+
* @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
|
|
1353
|
+
* unless the `initializeReverseSyncVersion` option is set to `true`
|
|
1354
|
+
*
|
|
1355
|
+
* The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
|
|
1356
|
+
* When set to `true`, it saves the reverse sync version as the current changeset of the targetDb. This is typically used for the first transformation between a master and branch iModel.
|
|
1357
|
+
* Setting `initializeReverseSyncVersion` to `true` has the effect of making it so any changesets in the branch iModel at the time of the first transformation will be ignored during any future reverse synchronizations from the branch to the master iModel.
|
|
1358
|
+
*
|
|
1359
|
+
* Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
|
|
1360
|
+
* Setting initializeReverseSyncVersion to true during a forward transformation could overwrite this correct reverseSyncVersion and should only be done during the first transformation between a master and branch iModel.
|
|
1415
1361
|
*/
|
|
1416
|
-
updateSynchronizationVersion({
|
|
1417
|
-
const
|
|
1418
|
-
this._sourceChangeDataState !== "has-changes"
|
|
1419
|
-
|
|
1420
|
-
if (notForcedAndHasNoChangesAndIsntProvenanceInit)
|
|
1362
|
+
updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
|
|
1363
|
+
const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
|
|
1364
|
+
this._sourceChangeDataState !== "has-changes";
|
|
1365
|
+
if (shouldSkipSyncVersionUpdate)
|
|
1421
1366
|
return;
|
|
1422
1367
|
nodeAssert(this._targetScopeProvenanceProps);
|
|
1423
1368
|
const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
|
|
1424
1369
|
const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
|
|
1425
|
-
if (this.
|
|
1426
|
-
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1427
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1428
|
-
targetVersion;
|
|
1429
|
-
}
|
|
1430
|
-
else if (this.isReverseSynchronization) {
|
|
1370
|
+
if (this.isReverseSynchronization) {
|
|
1431
1371
|
const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
|
|
1432
1372
|
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
|
|
1433
1373
|
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1434
1374
|
sourceVersion;
|
|
1435
1375
|
}
|
|
1436
|
-
else
|
|
1376
|
+
else {
|
|
1437
1377
|
core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
|
|
1438
1378
|
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1379
|
+
// save reverse sync version
|
|
1380
|
+
if (initializeReverseSyncVersion) {
|
|
1381
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
|
|
1382
|
+
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1383
|
+
targetVersion;
|
|
1384
|
+
}
|
|
1439
1385
|
}
|
|
1440
|
-
if (this.
|
|
1441
|
-
(this._startingChangesetIndices &&
|
|
1386
|
+
if (this._options.argsForProcessChanges ||
|
|
1387
|
+
(this._startingChangesetIndices && initializeReverseSyncVersion)) {
|
|
1442
1388
|
nodeAssert(this.targetDb.changeset.index !== undefined &&
|
|
1443
1389
|
this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
|
|
1444
1390
|
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
@@ -1446,16 +1392,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1446
1392
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1447
1393
|
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1448
1394
|
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1395
|
+
// Determine which keys to clear and update based on the synchronization direction
|
|
1396
|
+
let syncChangesetsToClearKey;
|
|
1397
|
+
let syncChangesetsToUpdateKey;
|
|
1398
|
+
if (this.isReverseSynchronization) {
|
|
1399
|
+
syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
|
|
1400
|
+
syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
|
|
1404
|
+
syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
|
|
1405
|
+
}
|
|
1406
|
+
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1407
|
+
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1408
|
+
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1409
|
+
// stuff to it which would break future synchronizations
|
|
1459
1410
|
for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1460
1411
|
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1461
1412
|
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
@@ -1478,23 +1429,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1478
1429
|
this.clearCachedSynchronizationVersion();
|
|
1479
1430
|
}
|
|
1480
1431
|
// FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
|
|
1481
|
-
|
|
1432
|
+
finalizeTransformation() {
|
|
1482
1433
|
this.importer.finalize();
|
|
1483
|
-
this.updateSynchronizationVersion(
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
"The following elements were never fully resolved:",
|
|
1487
|
-
[...this._partiallyCommittedEntities.keys()].join(","),
|
|
1488
|
-
"This indicates that either some references were excluded from the transformation",
|
|
1489
|
-
"or the source has dangling references.",
|
|
1490
|
-
].join("\n");
|
|
1491
|
-
if (this._options.danglingReferencesBehavior === "reject")
|
|
1492
|
-
throw new Error(message);
|
|
1493
|
-
core_bentley_1.Logger.logWarning(loggerCategory, message);
|
|
1494
|
-
for (const partiallyCommittedElem of this._partiallyCommittedEntities.values()) {
|
|
1495
|
-
partiallyCommittedElem.forceComplete();
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1434
|
+
this.updateSynchronizationVersion({
|
|
1435
|
+
initializeReverseSyncVersion: this._isProvenanceInitTransform,
|
|
1436
|
+
});
|
|
1498
1437
|
// TODO: ignore if we remove change cache usage
|
|
1499
1438
|
if (!this._options.noDetachChangeCache) {
|
|
1500
1439
|
if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
|
|
@@ -1507,35 +1446,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1507
1446
|
this.targetDb.codeValueBehavior = "trim-unicode-whitespace";
|
|
1508
1447
|
}
|
|
1509
1448
|
/* eslint-enable @itwin/no-internal */
|
|
1510
|
-
const defaultSaveTargetChanges = () => this.targetDb.saveChanges();
|
|
1511
|
-
await (options?.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
1512
|
-
if (this.isReverseSynchronization)
|
|
1513
|
-
this.sourceDb.saveChanges();
|
|
1514
|
-
const description = `${this._isProvenanceInitTransform
|
|
1515
|
-
? options?.provenanceInitTransformChangesetDescription ??
|
|
1516
|
-
`initialized branch provenance with master iModel: ${this.sourceDb.iModelId}`
|
|
1517
|
-
: this.isForwardSynchronization
|
|
1518
|
-
? options?.forwardSyncBranchChangesetDescription ??
|
|
1519
|
-
`Forward sync of iModel: ${this.sourceDb.iModelId}`
|
|
1520
|
-
: options?.reverseSyncMasterChangesetDescription ??
|
|
1521
|
-
`Reverse sync of iModel: ${this.sourceDb.iModelId}`}`;
|
|
1522
|
-
if (this.targetDb.isBriefcaseDb()) {
|
|
1523
|
-
// This relies on authorizationClient on iModelHost being defined, otherwise this will fail
|
|
1524
|
-
await this.targetDb.pushChanges({
|
|
1525
|
-
description,
|
|
1526
|
-
});
|
|
1527
|
-
}
|
|
1528
|
-
if (this.isReverseSynchronization && this.sourceDb.isBriefcaseDb()) {
|
|
1529
|
-
// This relies on authorizationClient on iModelHost being defined, otherwise this will fail
|
|
1530
|
-
await this.sourceDb.pushChanges({
|
|
1531
|
-
description: options?.reverseSyncBranchChangesetDescription ??
|
|
1532
|
-
`Update provenance in response to a reverse sync to iModel: ${this.targetDb.iModelId}`,
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
1449
|
}
|
|
1536
1450
|
/** Imports all relationships that subclass from the specified base class.
|
|
1537
1451
|
* @param baseRelClassFullName The specified base relationship class.
|
|
1538
|
-
* @note This method is called from [[
|
|
1452
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1539
1453
|
*/
|
|
1540
1454
|
async processRelationships(baseRelClassFullName) {
|
|
1541
1455
|
await this.initialize();
|
|
@@ -1608,8 +1522,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1608
1522
|
}
|
|
1609
1523
|
/** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
|
|
1610
1524
|
* @deprecated in 1.x. Don't use this anymore
|
|
1611
|
-
* @see
|
|
1612
|
-
* @note This method is called from [[
|
|
1525
|
+
* @see [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided.
|
|
1526
|
+
* @note This method is called from [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are undefined, so it only needs to be called directly when processing a subset of an iModel.
|
|
1613
1527
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
1614
1528
|
*/
|
|
1615
1529
|
async detectRelationshipDeletes() {
|
|
@@ -1676,22 +1590,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1676
1590
|
* This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
|
|
1677
1591
|
*/
|
|
1678
1592
|
onExportElementUniqueAspect(sourceAspect) {
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1681
|
-
|
|
1593
|
+
const targetAspectProps = this.onTransformElementAspect(sourceAspect);
|
|
1594
|
+
if (!this.doAllReferencesExistInTarget(sourceAspect)) {
|
|
1595
|
+
this._partiallyCommittedAspectIds.add(sourceAspect.id);
|
|
1596
|
+
}
|
|
1682
1597
|
const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
|
|
1683
1598
|
this.context.remapElementAspect(sourceAspect.id, targetId);
|
|
1684
|
-
this.resolvePendingReferences(sourceAspect);
|
|
1685
1599
|
}
|
|
1686
1600
|
/** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
|
|
1687
1601
|
* This override calls [[onTransformElementAspect]] for each ElementMultiAspect and then [IModelImporter.importElementMultiAspects]($transformer) to update the target iModel.
|
|
1688
1602
|
* @note ElementMultiAspects are handled as a group to make it easier to differentiate between insert, update, and delete.
|
|
1689
1603
|
*/
|
|
1690
1604
|
onExportElementMultiAspects(sourceAspects) {
|
|
1691
|
-
const targetElementId = this.context.findTargetElementId(sourceAspects[0].element.id);
|
|
1692
1605
|
// Transform source ElementMultiAspects into target ElementAspectProps
|
|
1693
|
-
const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA
|
|
1694
|
-
sourceAspects.forEach((a) =>
|
|
1606
|
+
const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA));
|
|
1607
|
+
sourceAspects.forEach((a) => {
|
|
1608
|
+
if (!this.doAllReferencesExistInTarget(a)) {
|
|
1609
|
+
this._partiallyCommittedAspectIds.add(a.id);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1695
1612
|
// const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
|
|
1696
1613
|
const targetIds = this.importer.importElementMultiAspects(targetAspectPropsArray, (a) => {
|
|
1697
1614
|
const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
|
|
@@ -1701,16 +1618,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1701
1618
|
});
|
|
1702
1619
|
for (let i = 0; i < targetIds.length; ++i) {
|
|
1703
1620
|
this.context.remapElementAspect(sourceAspects[i].id, targetIds[i]);
|
|
1704
|
-
this.resolvePendingReferences(sourceAspects[i]);
|
|
1705
1621
|
}
|
|
1706
1622
|
}
|
|
1707
1623
|
/** Transform the specified sourceElementAspect into ElementAspectProps for the target iModel.
|
|
1708
1624
|
* @param sourceElementAspect The ElementAspect from the source iModel to be transformed.
|
|
1709
|
-
* @param _targetElementId The ElementId of the target Element that will own the ElementAspects after transformation.
|
|
1710
1625
|
* @returns ElementAspectProps for the target iModel.
|
|
1711
1626
|
* @note A subclass can override this method to provide custom transform behavior.
|
|
1712
1627
|
*/
|
|
1713
|
-
onTransformElementAspect(sourceElementAspect
|
|
1628
|
+
onTransformElementAspect(sourceElementAspect) {
|
|
1714
1629
|
const targetElementAspectProps = this.context.cloneElementAspect(sourceElementAspect);
|
|
1715
1630
|
return targetElementAspectProps;
|
|
1716
1631
|
}
|
|
@@ -1750,6 +1665,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1750
1665
|
nodeAssert(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
|
|
1751
1666
|
this._longNamedSchemasMap.set(schema.name, schemaFileName);
|
|
1752
1667
|
}
|
|
1668
|
+
/* eslint-disable-next-line deprecation/deprecation */
|
|
1753
1669
|
this.sourceDb.nativeDb.exportSchema(schema.name, this._schemaExportDir, schemaFileName);
|
|
1754
1670
|
return { schemaPath: path.join(this._schemaExportDir, schemaFileName) };
|
|
1755
1671
|
}
|
|
@@ -1790,7 +1706,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1790
1706
|
}
|
|
1791
1707
|
}
|
|
1792
1708
|
/** Cause all fonts to be exported from the source iModel and imported into the target iModel.
|
|
1793
|
-
* @note This method is called from [[
|
|
1709
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1794
1710
|
*/
|
|
1795
1711
|
async processFonts() {
|
|
1796
1712
|
// we do not need to initialize for this since no entities are exported
|
|
@@ -1802,14 +1718,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1802
1718
|
this.context.importFont(font.id);
|
|
1803
1719
|
}
|
|
1804
1720
|
/** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
|
|
1805
|
-
* @note This method is called from [[
|
|
1721
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1806
1722
|
*/
|
|
1807
1723
|
async processCodeSpecs() {
|
|
1808
1724
|
await this.initialize();
|
|
1809
1725
|
return this.exporter.exportCodeSpecs();
|
|
1810
1726
|
}
|
|
1811
1727
|
/** Cause a single CodeSpec to be exported from the source iModel and imported into the target iModel.
|
|
1812
|
-
* @note This method is called from [[
|
|
1728
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1813
1729
|
*/
|
|
1814
1730
|
async processCodeSpec(codeSpecName) {
|
|
1815
1731
|
await this.initialize();
|
|
@@ -1833,25 +1749,56 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1833
1749
|
this.context.remapElement(sourceSubjectId, targetSubjectId);
|
|
1834
1750
|
await this.processChildElements(sourceSubjectId);
|
|
1835
1751
|
await this.processSubjectSubModels(sourceSubjectId);
|
|
1836
|
-
|
|
1752
|
+
this.completePartiallyCommittedElements();
|
|
1753
|
+
this.completePartiallyCommittedAspects();
|
|
1837
1754
|
}
|
|
1838
1755
|
/**
|
|
1839
1756
|
* Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
|
|
1840
|
-
* are intending to process changes
|
|
1757
|
+
* are intending to process changes. Callers may wish to explicitly call initialize if they need to execute code after initialize but before [[process]] is called.
|
|
1841
1758
|
* @note Called by all `process*` functions implicitly.
|
|
1842
1759
|
* Overriders must call `super.initialize()` first
|
|
1843
1760
|
*/
|
|
1844
|
-
async initialize(
|
|
1761
|
+
async initialize() {
|
|
1845
1762
|
if (this._initialized)
|
|
1846
1763
|
return;
|
|
1847
|
-
|
|
1764
|
+
this.initScopeProvenance();
|
|
1765
|
+
await this._tryInitChangesetData(this._options.argsForProcessChanges);
|
|
1848
1766
|
await this.context.initialize();
|
|
1849
1767
|
// need exporter initialized to do remapdeletedsourceentities.
|
|
1850
|
-
await this.exporter.initialize(this.getExportInitOpts(
|
|
1768
|
+
await this.exporter.initialize(this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
|
|
1851
1769
|
// Exporter must be initialized prior to processing changesets in order to properly handle entity recreations (an entity delete followed by an insert of that same entity).
|
|
1852
1770
|
await this.processChangesets();
|
|
1853
1771
|
this._initialized = true;
|
|
1854
1772
|
}
|
|
1773
|
+
async handleCustomChanges(hasElementChangedCache, deleteIdsProcessed) {
|
|
1774
|
+
// The hasElementChangedCache gets populated by changes from this._csFileProps.
|
|
1775
|
+
// Because there is a possibility that someone could manually add ids to exporter.sourceDbChanges, we must separately process exporter.sourceDbChanges and add them to our hasElementChangedCache.
|
|
1776
|
+
// Without this change we risk onExportElement returning early because we use hasElementChangedCache to decide if an element has changed or not.
|
|
1777
|
+
this.exporter.sourceDbChanges?.element.updateIds.forEach((id) => hasElementChangedCache.add(id));
|
|
1778
|
+
this.exporter.sourceDbChanges?.element.insertIds.forEach((id) => hasElementChangedCache.add(id));
|
|
1779
|
+
// This loop is to process all custom deleteIds. Unclear if the special logic is still necessary for relationships or not (TODO!!). For all other entities, we assume that the element is still present in the sourceDb because it is not
|
|
1780
|
+
// a real delete and instead a simulated delete to update filtering criteria between source and target. Since the element is still present, we do not need to call processDeletedOp to find the corresponding targetId.
|
|
1781
|
+
// We can instead rely on `forEachTrackedElement` at the top of processChangesets to find the corresponding targetId.
|
|
1782
|
+
// Note this also assumes we don't need to handle entity recreation for these custom deletes. I.e. a caller of API would not be able to add a custom delete for an entity that was recreated.
|
|
1783
|
+
// a delete followed by an insert.
|
|
1784
|
+
// ASSUME: If a changeset has a deleteId then custom change will never reference it. Is this still true if it was re-inserted? (TODO!!)
|
|
1785
|
+
if (this.exporter.sourceDbChanges?.hasCustomChanges) {
|
|
1786
|
+
for (const id of this.exporter.sourceDbChanges?.relationship.deleteIds.keys() ??
|
|
1787
|
+
[]) {
|
|
1788
|
+
if (deleteIdsProcessed?.has(id))
|
|
1789
|
+
continue;
|
|
1790
|
+
const customData = this.exporter.sourceDbChanges?.getCustomRelationshipDataFromId(id, "relationship");
|
|
1791
|
+
if (customData === undefined) {
|
|
1792
|
+
core_bentley_1.Logger.logError(loggerCategory, "Custom data not found for relationship.", { id });
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
const classFullName = customData.classFullName;
|
|
1796
|
+
const sourceIdOfRelationshipInSource = customData?.sourceIdOfRelationship;
|
|
1797
|
+
const targetIdOfRelationshipInSource = customData?.targetIdOfRelationship;
|
|
1798
|
+
await this.processRelationshipDeleteOp(id, classFullName, sourceIdOfRelationshipInSource, targetIdOfRelationshipInSource);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1855
1802
|
/**
|
|
1856
1803
|
* Reads all the changeset files in the private member of the transformer: _csFileProps and does two things with these changesets.
|
|
1857
1804
|
* Finds the corresponding target entity for any deleted source entities and remaps the sourceId to the targetId.
|
|
@@ -1863,8 +1810,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1863
1810
|
this.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1864
1811
|
this.context.remapElement(sourceElementId, targetElementId);
|
|
1865
1812
|
});
|
|
1866
|
-
|
|
1867
|
-
|
|
1813
|
+
this.exporter.addCustomChanges();
|
|
1814
|
+
if (this._csFileProps === undefined || this._csFileProps.length === 0) {
|
|
1815
|
+
if (this.exporter.sourceDbChanges?.isEmpty)
|
|
1816
|
+
return;
|
|
1817
|
+
// our sourcedbChanges aren't empty (probably due to someone adding custom changes), change our sourceChangeDataState to has-changes
|
|
1818
|
+
if (this._sourceChangeDataState === "no-changes")
|
|
1819
|
+
this._sourceChangeDataState = "has-changes";
|
|
1820
|
+
}
|
|
1868
1821
|
const hasElementChangedCache = new Set();
|
|
1869
1822
|
const relationshipECClassIdsToSkip = new Set();
|
|
1870
1823
|
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)")) {
|
|
@@ -1892,7 +1845,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1892
1845
|
alreadyImportedModelInserts.add(targetModelId);
|
|
1893
1846
|
});
|
|
1894
1847
|
this._deletedSourceRelationshipData = new Map();
|
|
1895
|
-
|
|
1848
|
+
/** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
|
|
1849
|
+
const elemIdToScopeEsa = new Map();
|
|
1850
|
+
const deleteIdsProcessed = new Set();
|
|
1851
|
+
for (const csFile of this._csFileProps ?? []) {
|
|
1896
1852
|
const csReader = core_backend_1.SqliteChangesetReader.openFile({
|
|
1897
1853
|
fileName: csFile.pathname,
|
|
1898
1854
|
db: this.sourceDb,
|
|
@@ -1904,8 +1860,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1904
1860
|
ecChangeUnifier.appendFrom(csAdaptor);
|
|
1905
1861
|
}
|
|
1906
1862
|
const changes = [...ecChangeUnifier.instances];
|
|
1907
|
-
/** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
|
|
1908
|
-
const elemIdToScopeEsa = new Map();
|
|
1909
1863
|
for (const change of changes) {
|
|
1910
1864
|
if (change.ECClassId !== undefined &&
|
|
1911
1865
|
relationshipECClassIdsToSkip.has(change.ECClassId))
|
|
@@ -1913,7 +1867,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1913
1867
|
const changeType = change.$meta?.op;
|
|
1914
1868
|
if (changeType === "Deleted" &&
|
|
1915
1869
|
change?.$meta?.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
|
|
1916
|
-
change.Scope.Id === this.targetScopeElementId
|
|
1870
|
+
change.Scope.Id === this.targetScopeElementId &&
|
|
1871
|
+
change.Kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
|
|
1917
1872
|
elemIdToScopeEsa.set(change.Element.Id, change);
|
|
1918
1873
|
}
|
|
1919
1874
|
else if ((changeType === "Inserted" || changeType === "Updated") &&
|
|
@@ -1932,115 +1887,151 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1932
1887
|
if (changeType !== "Deleted" ||
|
|
1933
1888
|
relationshipECClassIdsToSkip.has(ecClassId))
|
|
1934
1889
|
continue;
|
|
1935
|
-
|
|
1890
|
+
if (relationshipECClassIds.has(ecClassId)) {
|
|
1891
|
+
if (change.$meta?.classFullName === undefined) {
|
|
1892
|
+
core_bentley_1.Logger.logError(loggerCategory, "ClassFullName was not found for relationship when reading changes. Relationship delete will not propagate.", { relationshipId: change.ECInstanceId, ecClassId });
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
if (change.SourceECInstanceId === undefined ||
|
|
1896
|
+
change.TargetECInstanceId === undefined) {
|
|
1897
|
+
core_bentley_1.Logger.logError(loggerCategory, "SourceECInstanceId or TargetECInstanceId was not found for relationship when reading changes. Relationship delete will not propagate.", {
|
|
1898
|
+
relationshipId: change.ECInstanceId,
|
|
1899
|
+
ecClassId,
|
|
1900
|
+
classFullName: change.$meta.classFullName,
|
|
1901
|
+
});
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1904
|
+
await this.processRelationshipDeleteOp(change.ECInstanceId, change.$meta.classFullName, change.SourceECInstanceId, change.TargetECInstanceId);
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
await this.processElementDeleteOp(change.ECInstanceId, alreadyImportedElementInserts, alreadyImportedModelInserts, elemIdToScopeEsa, change.FederationGuid);
|
|
1908
|
+
}
|
|
1909
|
+
deleteIdsProcessed.add(change.ECInstanceId);
|
|
1936
1910
|
}
|
|
1937
1911
|
csReader.close();
|
|
1938
1912
|
}
|
|
1913
|
+
await this.handleCustomChanges(hasElementChangedCache, deleteIdsProcessed);
|
|
1939
1914
|
this._hasElementChangedCache = hasElementChangedCache;
|
|
1940
1915
|
return;
|
|
1941
1916
|
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Helper function for processChangesets.
|
|
1919
|
+
* Populates the '_deletedSourceRelationshipData' map, whose key is the id of the relationship in the source and the value is an object used to find that relationship in the target.
|
|
1920
|
+
* @param changedInstanceId The id of the relationship that was deleted
|
|
1921
|
+
* @param classFullName classFullName of relationship
|
|
1922
|
+
* @param sourceIdOfRelationshipInSource the element Id acting as the source of the relationship in the sourceDb
|
|
1923
|
+
* @param targetIdOfRelationshipInSource the element Id acting as the target of the relationship in the sourceDb
|
|
1924
|
+
* @returns
|
|
1925
|
+
*/
|
|
1926
|
+
async processRelationshipDeleteOp(changedInstanceId, classFullName, sourceIdOfRelationshipInSource, targetIdOfRelationshipInSource) {
|
|
1927
|
+
// we need a connected iModel with changes to remap elements with deletions
|
|
1928
|
+
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1929
|
+
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index &&
|
|
1930
|
+
this.exporter.sourceDbChanges?.isEmpty;
|
|
1931
|
+
if (notConnectedModel || noChanges)
|
|
1932
|
+
return;
|
|
1933
|
+
const sourceIdOfRelationshipInTarget = await this.getTargetIdFromSourceId(sourceIdOfRelationshipInSource, true);
|
|
1934
|
+
const targetIdOfRelationshipInTarget = await this.getTargetIdFromSourceId(targetIdOfRelationshipInSource, true);
|
|
1935
|
+
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
1936
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1937
|
+
classFullName,
|
|
1938
|
+
sourceIdInTarget: sourceIdOfRelationshipInTarget,
|
|
1939
|
+
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
else if (this.sourceDb === this.provenanceSourceDb) {
|
|
1943
|
+
const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
|
|
1944
|
+
classFullName,
|
|
1945
|
+
sourceId: sourceIdOfRelationshipInSource,
|
|
1946
|
+
targetId: targetIdOfRelationshipInSource,
|
|
1947
|
+
});
|
|
1948
|
+
if (relProvenance && relProvenance.relationshipId)
|
|
1949
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1950
|
+
classFullName,
|
|
1951
|
+
relId: relProvenance.relationshipId,
|
|
1952
|
+
provenanceAspectId: relProvenance.aspectId,
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1942
1956
|
/**
|
|
1943
1957
|
* Helper function for processChangesets. Remaps the id of element deleted found in the 'change' to an element in the targetDb.
|
|
1944
1958
|
* @param change the change to process, must be of changeType "Deleted"
|
|
1945
1959
|
* @param mapOfDeletedElemIdToScopeEsas a map of elementIds to changedECInstances (which are ESAs). the elementId is not the id of the esa itself, but the elementid that the esa was stored on before the esa's deletion.
|
|
1946
1960
|
* All ESAs in this map are part of the transformer's scope / ESA data and are tracked in case the ESA is deleted in the target.
|
|
1947
|
-
* @param isRelationship is relationship or not
|
|
1948
1961
|
* @param alreadyImportedElementInserts used to handle entity recreation and not delete already handled element inserts.
|
|
1949
1962
|
* @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
|
|
1950
1963
|
* @returns void
|
|
1951
1964
|
*/
|
|
1952
|
-
async
|
|
1965
|
+
async processElementDeleteOp(changedInstanceId, alreadyImportedElementInserts, alreadyImportedModelInserts, mapOfDeletedElemIdToScopeEsas, federationGuid) {
|
|
1953
1966
|
// we need a connected iModel with changes to remap elements with deletions
|
|
1954
1967
|
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1955
|
-
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index
|
|
1968
|
+
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index &&
|
|
1969
|
+
this.exporter.sourceDbChanges?.isEmpty;
|
|
1956
1970
|
if (notConnectedModel || noChanges)
|
|
1957
1971
|
return;
|
|
1972
|
+
let targetId = await this.getTargetIdFromSourceId(changedInstanceId, false, mapOfDeletedElemIdToScopeEsas, federationGuid);
|
|
1973
|
+
if (targetId === undefined && this.sourceDb === this.provenanceSourceDb) {
|
|
1974
|
+
targetId = this._queryProvenanceForElement(changedInstanceId);
|
|
1975
|
+
}
|
|
1976
|
+
// since we are processing one changeset at a time, we can see local source deletes
|
|
1977
|
+
// of entities that were never synced and can be safely ignored
|
|
1978
|
+
const deletionNotInTarget = !targetId;
|
|
1979
|
+
if (deletionNotInTarget)
|
|
1980
|
+
return;
|
|
1981
|
+
this.context.remapElement(changedInstanceId, targetId);
|
|
1982
|
+
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
1983
|
+
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
1984
|
+
if (alreadyImportedElementInserts.has(targetId)) {
|
|
1985
|
+
this.exporter.sourceDbChanges?.element.deleteIds.delete(changedInstanceId);
|
|
1986
|
+
}
|
|
1987
|
+
if (alreadyImportedModelInserts.has(targetId)) {
|
|
1988
|
+
this.exporter.sourceDbChanges?.model.deleteIds.delete(changedInstanceId);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Find the corresponding id in the targetDb given a id from the sourceDb
|
|
1993
|
+
* @param id the id in the source that we want to find the target id for
|
|
1994
|
+
* @param isRelationship Changes the way we look for the federationGuid , if true we look for the federationGuid on the element itself, if false we expect it to be passed in because it was part of the ChangedECInstance.
|
|
1995
|
+
* Typically the source and targetIds of the relationship and not the relationshipId itself is passed to this function
|
|
1996
|
+
* @param mapOfDeletedElemIdToScopeEsas a map of elementIds to changedECInstances (which are ESAs). the elementId is not the id of the esa itself, but the elementid that the esa was stored on before the esa's deletion.
|
|
1997
|
+
* All ESAs in this map are part of the transformer's scope / ESA data and are tracked in case the ESA is deleted in the target.
|
|
1998
|
+
* @param federationGuid
|
|
1999
|
+
* @returns id of the corresponding entity in the targetDb or undefined if not found
|
|
2000
|
+
*/
|
|
2001
|
+
async getTargetIdFromSourceId(id, isRelationship, mapOfDeletedElemIdToScopeEsas, federationGuid) {
|
|
1958
2002
|
/**
|
|
1959
2003
|
* if our ChangedECInstance is in the provenanceDb, then we can use the ids we find in the ChangedECInstance to query for ESAs.
|
|
1960
2004
|
* This is because the ESAs are stored on an element Id thats present in the provenanceDb.
|
|
1961
2005
|
*/
|
|
1962
2006
|
const changeDataInProvenanceDb = this.sourceDb === this.provenanceDb;
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
let element;
|
|
1966
|
-
if (isRelationship) {
|
|
1967
|
-
element = this.sourceDb.elements.tryGetElement(id);
|
|
1968
|
-
}
|
|
1969
|
-
const fedGuid = isRelationship
|
|
1970
|
-
? element?.federationGuid
|
|
1971
|
-
: change.FederationGuid;
|
|
1972
|
-
if (changeDataInProvenanceDb) {
|
|
1973
|
-
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
1974
|
-
for await (const row of this.sourceDb.createQueryReader("SELECT esa.Identifier FROM bis.ExternalSourceAspect esa WHERE Scope.Id=:scopeId AND Kind=:kind AND Element.Id=:relatedElementId LIMIT 1", core_common_1.QueryBinder.from([
|
|
1975
|
-
this.targetScopeElementId,
|
|
1976
|
-
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1977
|
-
id,
|
|
1978
|
-
]))) {
|
|
1979
|
-
identifierValue = row.Identifier;
|
|
1980
|
-
}
|
|
1981
|
-
identifierValue =
|
|
1982
|
-
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.Identifier;
|
|
1983
|
-
}
|
|
1984
|
-
// Check for targetId by an esa first
|
|
1985
|
-
if (changeDataInProvenanceDb && identifierValue) {
|
|
1986
|
-
const targetId = identifierValue;
|
|
1987
|
-
return targetId;
|
|
1988
|
-
}
|
|
1989
|
-
// Check for targetId using sourceId's fedguid if we didn't find an esa.
|
|
1990
|
-
if (fedGuid) {
|
|
1991
|
-
const targetId = this._queryElemIdByFedGuid(this.targetDb, fedGuid);
|
|
1992
|
-
return targetId;
|
|
1993
|
-
}
|
|
1994
|
-
return undefined;
|
|
1995
|
-
};
|
|
1996
|
-
const changedInstanceId = change.ECInstanceId;
|
|
2007
|
+
let identifierValue;
|
|
2008
|
+
let element;
|
|
1997
2009
|
if (isRelationship) {
|
|
1998
|
-
|
|
1999
|
-
const targetIdOfRelationshipInSource = change.TargetECInstanceId;
|
|
2000
|
-
const classFullName = change.$meta?.classFullName;
|
|
2001
|
-
const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
|
|
2002
|
-
const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
|
|
2003
|
-
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
2004
|
-
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
2005
|
-
classFullName: classFullName ?? "",
|
|
2006
|
-
sourceIdInTarget: sourceIdOfRelationshipInTarget,
|
|
2007
|
-
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
2008
|
-
});
|
|
2009
|
-
}
|
|
2010
|
-
else if (this.sourceDb === this.provenanceSourceDb) {
|
|
2011
|
-
const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
|
|
2012
|
-
classFullName: classFullName ?? "",
|
|
2013
|
-
sourceId: sourceIdOfRelationshipInSource,
|
|
2014
|
-
targetId: targetIdOfRelationshipInSource,
|
|
2015
|
-
});
|
|
2016
|
-
if (relProvenance && relProvenance.relationshipId)
|
|
2017
|
-
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
2018
|
-
classFullName: classFullName ?? "",
|
|
2019
|
-
relId: relProvenance.relationshipId,
|
|
2020
|
-
provenanceAspectId: relProvenance.aspectId,
|
|
2021
|
-
});
|
|
2022
|
-
}
|
|
2010
|
+
element = this.sourceDb.elements.tryGetElement(id);
|
|
2023
2011
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
this.
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
if (alreadyImportedModelInserts.has(targetId)) {
|
|
2041
|
-
this.exporter.sourceDbChanges?.model.deleteIds.delete(changedInstanceId);
|
|
2012
|
+
const fedGuid = isRelationship ? element?.federationGuid : federationGuid;
|
|
2013
|
+
// Check for targetId using sourceId's fedguid
|
|
2014
|
+
if (fedGuid) {
|
|
2015
|
+
const targetId = this._queryElemIdByFedGuid(this.targetDb, fedGuid);
|
|
2016
|
+
if (targetId !== undefined)
|
|
2017
|
+
return targetId;
|
|
2018
|
+
}
|
|
2019
|
+
// Check for targetId by esa
|
|
2020
|
+
if (changeDataInProvenanceDb) {
|
|
2021
|
+
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
2022
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT esa.Identifier FROM bis.ExternalSourceAspect esa WHERE Scope.Id=:scopeId AND Kind=:kind AND Element.Id=:relatedElementId LIMIT 1", core_common_1.QueryBinder.from([
|
|
2023
|
+
this.targetScopeElementId,
|
|
2024
|
+
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
2025
|
+
id,
|
|
2026
|
+
]))) {
|
|
2027
|
+
identifierValue = row.Identifier;
|
|
2042
2028
|
}
|
|
2029
|
+
identifierValue =
|
|
2030
|
+
identifierValue ?? mapOfDeletedElemIdToScopeEsas?.get(id)?.Identifier;
|
|
2031
|
+
if (identifierValue)
|
|
2032
|
+
return identifierValue;
|
|
2043
2033
|
}
|
|
2034
|
+
return undefined;
|
|
2044
2035
|
}
|
|
2045
2036
|
async _tryInitChangesetData(args) {
|
|
2046
2037
|
if (!args ||
|
|
@@ -2055,10 +2046,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2055
2046
|
this._csFileProps = [];
|
|
2056
2047
|
return;
|
|
2057
2048
|
}
|
|
2049
|
+
const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
|
|
2058
2050
|
// NOTE: that we do NOT download the changesummary for the last transformed version, we want
|
|
2059
2051
|
// to ignore those already processed changes
|
|
2060
|
-
const startChangesetIndexOrId =
|
|
2061
|
-
|
|
2052
|
+
const startChangesetIndexOrId = startChangeset?.index ??
|
|
2053
|
+
startChangeset?.id ??
|
|
2062
2054
|
this.synchronizationVersion.index + 1;
|
|
2063
2055
|
const endChangesetId = this.sourceDb.changeset.id;
|
|
2064
2056
|
const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
|
|
@@ -2068,11 +2060,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2068
2060
|
iModelId: this.sourceDb.iModelId,
|
|
2069
2061
|
// eslint-disable-next-line deprecation/deprecation
|
|
2070
2062
|
changeset: { id: indexOrId },
|
|
2071
|
-
accessToken: args.accessToken,
|
|
2072
2063
|
})
|
|
2073
2064
|
.then((changeset) => changeset.index)));
|
|
2074
2065
|
const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
|
|
2075
|
-
if (!this._options.
|
|
2066
|
+
if (!this._options.argsForProcessChanges
|
|
2067
|
+
?.ignoreMissingChangesetsInSynchronizations &&
|
|
2076
2068
|
startChangesetIndex !== this.synchronizationVersion.index + 1 &&
|
|
2077
2069
|
this.synchronizationVersion.index !== -1) {
|
|
2078
2070
|
throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
|
|
@@ -2107,13 +2099,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2107
2099
|
this._sourceChangeDataState =
|
|
2108
2100
|
this._csFileProps.length === 0 ? "no-changes" : "has-changes";
|
|
2109
2101
|
}
|
|
2102
|
+
/**
|
|
2103
|
+
* The behavior of process is influenced by [[IModelTransformOptions.argsForProcessChanges]] being defined or not defined during construction passed of the IModelTransformer.
|
|
2104
|
+
* @section When argsForProcessChanges are defined:
|
|
2105
|
+
*
|
|
2106
|
+
* Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2107
|
+
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2108
|
+
*
|
|
2109
|
+
* Notes:
|
|
2110
|
+
* - the transformer assumes that you saveChanges after processing changes. You should not modify the iModel after processChanges until saveChanges,
|
|
2111
|
+
* failure to do so may result in corrupted
|
|
2112
|
+
* data loss in future branch operations
|
|
2113
|
+
* - if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2114
|
+
* will automatically be determined and used
|
|
2115
|
+
* - To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range.
|
|
2116
|
+
*
|
|
2117
|
+
* @section When argsForProcessChanges are undefined:
|
|
2118
|
+
*
|
|
2119
|
+
* Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2120
|
+
*
|
|
2121
|
+
* Notes:
|
|
2122
|
+
* - [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2123
|
+
*
|
|
2124
|
+
*/
|
|
2125
|
+
async process() {
|
|
2126
|
+
await this.initialize();
|
|
2127
|
+
this.logSettings();
|
|
2128
|
+
return this._options.argsForProcessChanges !== undefined
|
|
2129
|
+
? this.processChanges(this._options.argsForProcessChanges)
|
|
2130
|
+
: this.processAll();
|
|
2131
|
+
}
|
|
2110
2132
|
/** Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2111
2133
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2112
2134
|
*/
|
|
2113
|
-
async processAll(
|
|
2114
|
-
this.logSettings();
|
|
2115
|
-
this.initScopeProvenance();
|
|
2116
|
-
await this.initialize();
|
|
2135
|
+
async processAll() {
|
|
2117
2136
|
await this.exporter.exportCodeSpecs();
|
|
2118
2137
|
await this.exporter.exportFonts();
|
|
2119
2138
|
if (this._options.skipPropagateChangesToRootElements) {
|
|
@@ -2125,9 +2144,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2125
2144
|
else {
|
|
2126
2145
|
await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
|
|
2127
2146
|
}
|
|
2147
|
+
this.completePartiallyCommittedElements();
|
|
2128
2148
|
await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
|
|
2149
|
+
this.completePartiallyCommittedAspects();
|
|
2129
2150
|
await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
2130
|
-
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
2131
2151
|
if (this._options.forceExternalSourceAspectProvenance &&
|
|
2132
2152
|
this.shouldDetectDeletes()) {
|
|
2133
2153
|
// eslint-disable-next-line deprecation/deprecation
|
|
@@ -2138,7 +2158,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2138
2158
|
if (this._options.optimizeGeometry)
|
|
2139
2159
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2140
2160
|
this.importer.computeProjectExtents();
|
|
2141
|
-
|
|
2161
|
+
this.finalizeTransformation();
|
|
2142
2162
|
}
|
|
2143
2163
|
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
2144
2164
|
this._lastProvenanceEntityInfo =
|
|
@@ -2155,39 +2175,42 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2155
2175
|
}
|
|
2156
2176
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2157
2177
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2158
|
-
* @note the transformer
|
|
2178
|
+
* @note the transformer assumes that you saveChanges after processing changes. You should not
|
|
2179
|
+
* modify the iModel after processChanges until saveChanges, failure to do so may result in corrupted
|
|
2180
|
+
* data loss in future branch operations
|
|
2159
2181
|
* @note if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2160
2182
|
* will automatically be determined and used
|
|
2161
2183
|
* @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range.
|
|
2162
2184
|
*/
|
|
2163
2185
|
async processChanges(options) {
|
|
2164
|
-
this._isSynchronization = true;
|
|
2165
|
-
this.initScopeProvenance();
|
|
2166
|
-
this.logSettings();
|
|
2167
|
-
await this.initialize(options);
|
|
2168
2186
|
// must wait for initialization of synchronization provenance data
|
|
2169
2187
|
await this.exporter.exportChanges(this.getExportInitOpts(options));
|
|
2170
|
-
|
|
2188
|
+
this.completePartiallyCommittedElements();
|
|
2189
|
+
this.completePartiallyCommittedAspects();
|
|
2171
2190
|
if (this._options.optimizeGeometry)
|
|
2172
2191
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2173
2192
|
this.importer.computeProjectExtents();
|
|
2174
|
-
|
|
2193
|
+
this.finalizeTransformation();
|
|
2194
|
+
const defaultSaveTargetChanges = () => {
|
|
2195
|
+
this.targetDb.saveChanges();
|
|
2196
|
+
};
|
|
2197
|
+
await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
2175
2198
|
}
|
|
2176
2199
|
/** Changeset data must be initialized in order to build correct changeOptions.
|
|
2177
2200
|
* Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
|
|
2178
2201
|
*/
|
|
2179
2202
|
getExportInitOpts(opts) {
|
|
2180
|
-
if (!this.
|
|
2203
|
+
if (!this._options.argsForProcessChanges)
|
|
2181
2204
|
return {};
|
|
2205
|
+
const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
|
|
2182
2206
|
return {
|
|
2183
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements
|
|
2184
|
-
accessToken: opts.accessToken,
|
|
2207
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
2185
2208
|
...(this._csFileProps
|
|
2186
2209
|
? { csFileProps: this._csFileProps }
|
|
2187
2210
|
: this._changesetRanges
|
|
2188
2211
|
? { changesetRanges: this._changesetRanges }
|
|
2189
|
-
:
|
|
2190
|
-
? { startChangeset
|
|
2212
|
+
: startChangeset
|
|
2213
|
+
? { startChangeset }
|
|
2191
2214
|
: {
|
|
2192
2215
|
startChangeset: {
|
|
2193
2216
|
index: this.synchronizationVersion.index + 1,
|
|
@@ -2269,8 +2292,7 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2269
2292
|
}
|
|
2270
2293
|
/** Cloning from a template requires this override of onTransformElement. */
|
|
2271
2294
|
onTransformElement(sourceElement) {
|
|
2272
|
-
|
|
2273
|
-
const referenceIds = sourceElement.getReferenceConcreteIds();
|
|
2295
|
+
const referenceIds = sourceElement.getReferenceIds();
|
|
2274
2296
|
referenceIds.forEach((referenceId) => {
|
|
2275
2297
|
// TODO: consider going through all definition elements at once and remapping them to themselves
|
|
2276
2298
|
if (!core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
|