@itwin/imodel-transformer 2.0.0-dev.2 → 2.0.0-dev.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/lib/cjs/BranchProvenanceInitializer.js +18 -15
  3. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  4. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +1 -1
  5. package/lib/cjs/DetachedExportElementAspectsStrategy.js +8 -6
  6. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
  7. package/lib/cjs/ECReferenceTypesCache.d.ts +3 -0
  8. package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
  9. package/lib/cjs/ECReferenceTypesCache.js +103 -40
  10. package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
  11. package/lib/cjs/ElementCascadingDeleter.d.ts.map +1 -1
  12. package/lib/cjs/ElementCascadingDeleter.js +10 -9
  13. package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
  14. package/lib/cjs/EntityUnifier.d.ts +1 -1
  15. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  16. package/lib/cjs/EntityUnifier.js +11 -12
  17. package/lib/cjs/EntityUnifier.js.map +1 -1
  18. package/lib/cjs/ExportElementAspectsStrategy.d.ts +4 -4
  19. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +1 -1
  20. package/lib/cjs/ExportElementAspectsStrategy.js +1 -1
  21. package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -1
  22. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +1 -1
  23. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +17 -16
  24. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -1
  25. package/lib/cjs/IModelCloneContext.d.ts +3 -3
  26. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  27. package/lib/cjs/IModelCloneContext.js +60 -52
  28. package/lib/cjs/IModelCloneContext.js.map +1 -1
  29. package/lib/cjs/IModelExporter.d.ts +28 -19
  30. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  31. package/lib/cjs/IModelExporter.js +145 -119
  32. package/lib/cjs/IModelExporter.js.map +1 -1
  33. package/lib/cjs/IModelImporter.d.ts +21 -21
  34. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  35. package/lib/cjs/IModelImporter.js +94 -74
  36. package/lib/cjs/IModelImporter.js.map +1 -1
  37. package/lib/cjs/IModelTransformer.d.ts +84 -178
  38. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  39. package/lib/cjs/IModelTransformer.js +371 -997
  40. package/lib/cjs/IModelTransformer.js.map +1 -1
  41. package/lib/cjs/ProvenanceManager.d.ts +159 -0
  42. package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
  43. package/lib/cjs/ProvenanceManager.js +677 -0
  44. package/lib/cjs/ProvenanceManager.js.map +1 -0
  45. package/lib/cjs/SyncTypeResolver.d.ts +34 -0
  46. package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
  47. package/lib/cjs/SyncTypeResolver.js +84 -0
  48. package/lib/cjs/SyncTypeResolver.js.map +1 -0
  49. package/lib/cjs/TransformerLoggerCategory.d.ts +6 -5
  50. package/lib/cjs/TransformerLoggerCategory.d.ts.map +1 -1
  51. package/lib/cjs/TransformerLoggerCategory.js +6 -5
  52. package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
  53. package/lib/cjs/imodel-transformer.js +2 -2
  54. package/lib/cjs/imodel-transformer.js.map +1 -1
  55. package/package.json +38 -33
@@ -21,13 +21,9 @@ const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
21
21
  const IModelCloneContext_1 = require("./IModelCloneContext");
22
22
  const EntityUnifier_1 = require("./EntityUnifier");
23
23
  const Algo_1 = require("./Algo");
24
+ const SyncTypeResolver_1 = require("./SyncTypeResolver");
25
+ const ProvenanceManager_1 = require("./ProvenanceManager");
24
26
  const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelTransformer;
25
- const nullLastProvenanceEntityInfo = {
26
- entityId: core_bentley_1.Id64.invalid,
27
- aspectId: core_bentley_1.Id64.invalid,
28
- aspectVersion: "",
29
- aspectKind: core_backend_1.ExternalSourceAspect.Kind.Element,
30
- };
31
27
  /**
32
28
  * Apply a function to each Id64 in a supported container type of Id64s.
33
29
  * Currently only supports raw Id64String or RelatedElement-like objects containing an `id` property that is a Id64String,
@@ -77,7 +73,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
77
73
  targetDb;
78
74
  /** The IModelTransformContext for this IModelTransformer. */
79
75
  context;
80
- _syncType;
76
+ /** The transform to be applied to the placement of spatial elements
77
+ * This transform should be applied when:
78
+ * - source and target db have different ECEF locations
79
+ * - source and target db have matching GCS/CRS data, but differing `geographicCoordinateSystem.additionalTransform.helmert2DWithZOffset`
80
+ * @note for ECEF transforms, this can only be used when source and target are linearly located imodels
81
+ * @note for non linearly located imodels, this transform will be a linear transform derived from Helmert Transforms from the src and target iModels.
82
+ * @beta
83
+ */
84
+ _linearSpatialTransform;
85
+ _syncTypeResolver;
86
+ _provenanceManager;
81
87
  /** The Id of the Element in the **target** iModel that represents the **source** repository as a whole and scopes its [ExternalSourceAspect]($backend) instances. */
82
88
  get targetScopeElementId() {
83
89
  return this._options.targetScopeElementId;
@@ -86,118 +92,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
86
92
  _elementsWithExplicitlyTrackedProvenance = new Set();
87
93
  _partiallyCommittedElementIds = new Set();
88
94
  _partiallyCommittedAspectIds = new Set();
89
- /** the options that were used to initialize this transformer */
90
- _options;
91
95
  /**
92
- * 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.
93
- * 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.
94
- * A couple ofoutdated tests run their first transform providing a source and targetdb that are slightly different which is no longer supported. In order to not remove these tests which are still providing value
95
- * this private property on the IModelTransformer exists.
96
+ * Tracks target element IDs that were remapped by Code during the current
97
+ * transformation pass. Used to prevent deletion of target elements that have been remapped
98
+ * to a new source element in the same pass (e.g., when a source element is deleted and a
99
+ * new one with the same properties is added, causing a remap to the same target).
96
100
  */
97
- _allowNoScopingESA = false;
98
- static noEsaSyncDirectionErrorMessage = "Couldn't find an external source aspect to determine sync direction. This often means that the master->branch relationship has not been established. Consider running the transformer with wasSourceIModelCopiedToTarget set to true.";
99
- /**
100
- * Queries for an esa which matches the props in the provided aspectProps.
101
- * @param dbToQuery db to run the query on for scope external source
102
- * @param aspectProps aspectProps to search for @see ExternalSourceAspectProps
103
- */
104
- static queryScopeExternalSourceAspect(dbToQuery, aspectProps) {
105
- const sql = `
106
- SELECT ECInstanceId, Version, JsonProperties
107
- FROM ${core_backend_1.ExternalSourceAspect.classFullName}
108
- WHERE Element.Id=:elementId
109
- AND Scope.Id=:scopeId
110
- AND Kind=:kind
111
- AND Identifier=:identifier
112
- LIMIT 1
113
- `;
114
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
115
- return dbToQuery.withPreparedStatement(sql, (statement) => {
116
- statement.bindId("elementId", aspectProps.element.id);
117
- if (aspectProps.scope === undefined)
118
- return undefined; // return instead of binding an invalid id
119
- statement.bindId("scopeId", aspectProps.scope.id);
120
- statement.bindString("kind", aspectProps.kind);
121
- statement.bindString("identifier", aspectProps.identifier);
122
- if (core_bentley_1.DbResult.BE_SQLITE_ROW !== statement.step())
123
- return undefined;
124
- const aspectId = statement.getValue(0).getId();
125
- const versionValue = statement.getValue(1);
126
- const version = versionValue.isNull
127
- ? undefined
128
- : versionValue.getString();
129
- const jsonPropsValue = statement.getValue(2);
130
- const jsonProperties = jsonPropsValue.isNull
131
- ? undefined
132
- : jsonPropsValue.getString();
133
- return { aspectId, version, jsonProperties };
134
- });
135
- }
101
+ _targetElementIdsRemappedByCode = new Set();
136
102
  /**
137
- * Determines the sync direction "forward" or "reverse" of a given sourceDb and targetDb by looking for the scoping ESA.
138
- * If the sourceDb's iModelId is found as the identifier of the expected scoping ESA in the targetDb, then it is a forward synchronization.
139
- * If the targetDb's iModelId is found as the identifier of the expected scoping ESA in the sourceDb, then it is a reverse synchronization.
140
- * @throws if no scoping ESA can be found in either the sourceDb or targetDb which describes a master branch relationship between the two databases.
141
- * @returns "forward" or "reverse"
103
+ * Tracks target model IDs that were imported (inserted or updated) during the current
104
+ * transformation pass. Used to prevent deletion of target models that have been recreated
105
+ * in the same pass (e.g. when a partition element is recreated with a new model).
142
106
  */
143
- static determineSyncType(sourceDb, targetDb,
144
- /** @see [[IModelTransformOptions.targetScopeElementId]] */
145
- targetScopeElementId) {
146
- const aspectProps = {
147
- id: undefined,
148
- version: undefined,
149
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
150
- element: {
151
- id: targetScopeElementId,
152
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
153
- },
154
- scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
155
- identifier: sourceDb.iModelId,
156
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
157
- jsonProperties: undefined,
158
- };
159
- /** First check if the targetDb is the branch (branch is the @see provenanceDb) */
160
- const esaPropsFromTargetDb = this.queryScopeExternalSourceAspect(targetDb, aspectProps);
161
- if (esaPropsFromTargetDb !== undefined) {
162
- return "forward"; // we found an esa assuming targetDb is the provenanceDb/branch so this is a forward sync.
163
- }
164
- // Now check if the sourceDb is the branch
165
- aspectProps.identifier = targetDb.iModelId;
166
- const esaPropsFromSourceDb = this.queryScopeExternalSourceAspect(sourceDb, aspectProps);
167
- if (esaPropsFromSourceDb !== undefined) {
168
- return "reverse"; // we found an esa assuming sourceDb is the provenanceDb/branch so this is a reverse sync.
169
- }
170
- throw new Error(this.noEsaSyncDirectionErrorMessage);
171
- }
172
- determineSyncType() {
173
- if (this._isProvenanceInitTransform) {
174
- return "forward";
175
- }
176
- if (!this._options.argsForProcessChanges) {
177
- return "not-sync";
178
- }
179
- try {
180
- return IModelTransformer.determineSyncType(this.sourceDb, this.targetDb, this.targetScopeElementId);
181
- }
182
- catch (err) {
183
- if (err instanceof Error &&
184
- err.message === IModelTransformer.noEsaSyncDirectionErrorMessage &&
185
- this._allowNoScopingESA) {
186
- return "forward";
187
- }
188
- throw err;
189
- }
190
- }
191
- get isReverseSynchronization() {
192
- if (this._syncType === undefined)
193
- this._syncType = this.determineSyncType();
194
- return this._syncType === "reverse";
195
- }
196
- get isForwardSynchronization() {
197
- if (this._syncType === undefined)
198
- this._syncType = this.determineSyncType();
199
- return this._syncType === "forward";
200
- }
107
+ _targetModelsImportedInCurrentTransform = new Set();
108
+ /** the options that were used to initialize this transformer */
109
+ _options;
201
110
  _changesetRanges = undefined;
202
111
  /**
203
112
  * Set if the transformer is being used to perform the provenance initialization step of a fork initialization.
@@ -206,16 +115,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
206
115
  _isProvenanceInitTransform;
207
116
  /** The element classes that are considered to define provenance in the iModel */
208
117
  static get provenanceElementClasses() {
209
- return [
210
- core_backend_1.FolderLink,
211
- core_backend_1.SynchronizationConfigLink,
212
- core_backend_1.ExternalSource,
213
- core_backend_1.ExternalSourceAttachment,
214
- ];
118
+ return ProvenanceManager_1.ProvenanceManager.provenanceElementClasses;
215
119
  }
216
120
  /** The element aspect classes that are considered to define provenance in the iModel */
217
121
  static get provenanceElementAspectClasses() {
218
- return [core_backend_1.ExternalSourceAspect];
122
+ return ProvenanceManager_1.ProvenanceManager.provenanceElementAspectClasses;
219
123
  }
220
124
  /** Construct a new IModelTransformer
221
125
  * @param source Specifies the source IModelExporter or the source IModelDb that will be used to construct the source IModelExporter.
@@ -230,10 +134,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
230
134
  // non-falsy defaults
231
135
  cloneUsingBinaryGeometry: options?.cloneUsingBinaryGeometry ?? true,
232
136
  targetScopeElementId: options?.targetScopeElementId ?? core_common_1.IModel.rootSubjectId,
233
- // eslint-disable-next-line deprecation/deprecation
234
137
  danglingReferencesBehavior: options?.danglingReferencesBehavior ?? "reject",
235
138
  branchRelationshipDataBehavior: options?.branchRelationshipDataBehavior ?? "reject",
236
139
  skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
140
+ tryAlignGeolocation: options?.tryAlignGeolocation ?? false,
237
141
  };
238
142
  // check if authorization client is defined
239
143
  if (core_backend_1.IModelHost.authorizationClient === undefined) {
@@ -274,14 +178,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
274
178
  this.targetDb = this.importer.targetDb;
275
179
  // create the IModelCloneContext, it must be initialized later
276
180
  this.context = new IModelCloneContext_1.IModelCloneContext(this.sourceDb, this.targetDb);
277
- if (this.sourceDb.isBriefcase && this.targetDb.isBriefcase) {
278
- nodeAssert(this.sourceDb.changeset.index !== undefined &&
279
- this.targetDb.changeset.index !== undefined, "database has no changeset index");
280
- this._startingChangesetIndices = {
281
- target: this.targetDb.changeset.index,
282
- source: this.sourceDb.changeset.index,
283
- };
284
- }
285
181
  // this internal is guaranteed stable for just transformer usage
286
182
  /* eslint-disable @itwin/no-internal */
287
183
  if (("codeValueBehavior" in this.sourceDb)) {
@@ -289,6 +185,22 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
289
185
  this.targetDb.codeValueBehavior = "exact";
290
186
  }
291
187
  /* eslint-enable @itwin/no-internal */
188
+ this._syncTypeResolver = new SyncTypeResolver_1.SyncTypeResolver(this.context, this._options.targetScopeElementId, !!this._isProvenanceInitTransform, !!this._options.argsForProcessChanges);
189
+ this._provenanceManager = new ProvenanceManager_1.ProvenanceManager(this._options.targetScopeElementId, this._options, this._syncTypeResolver);
190
+ if (this._options.tryAlignGeolocation) {
191
+ if (this.sourceDb.geographicCoordinateSystem ||
192
+ this.targetDb.geographicCoordinateSystem) {
193
+ core_bentley_1.Logger.logTrace(loggerCategory, "Aligning Additional transforms between imodels due to imodels containing GeographicCoordinateSystem data");
194
+ this._linearSpatialTransform =
195
+ this.calculateTransformFromHelmertTransforms();
196
+ }
197
+ else if (this.sourceDb.ecefLocation && this.targetDb.ecefLocation) {
198
+ core_bentley_1.Logger.logTrace(loggerCategory, "Aligning ECEF Location's between imodels due to imodels not containing GeographicCoordinateSystem data");
199
+ this._linearSpatialTransform = this.calculateEcefTransform();
200
+ }
201
+ else
202
+ core_bentley_1.Logger.logTrace(loggerCategory, "No Geolcation data to align, both GCS and ECEF are undefined");
203
+ }
292
204
  }
293
205
  /** validates that the importer set on the transformer has the same values for its shared options as the transformer.
294
206
  * @note This expects that the importer is already set on the transformer.
@@ -308,7 +220,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
308
220
  /** Dispose any native resources associated with this IModelTransformer. */
309
221
  dispose() {
310
222
  core_bentley_1.Logger.logTrace(loggerCategory, "dispose()");
311
- this.context.dispose();
223
+ this.context[Symbol.dispose]();
312
224
  }
313
225
  /** Log current settings that affect IModelTransformer's behavior. */
314
226
  logSettings() {
@@ -325,74 +237,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
325
237
  core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
326
238
  core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
327
239
  }
328
- /** Return the IModelDb where IModelTransformer will store its provenance.
329
- * @note This will be [[targetDb]] except when it is a reverse synchronization. In that case it be [[sourceDb]].
330
- */
331
- get provenanceDb() {
332
- return this.isReverseSynchronization ? this.sourceDb : this.targetDb;
333
- }
334
- /** Return the IModelDb where IModelTransformer looks for entities referred to by stored provenance.
335
- * @note This will be [[sourceDb]] except when it is a reverse synchronization. In that case it be [[targetDb]].
336
- */
337
- get provenanceSourceDb() {
338
- return this.isReverseSynchronization ? this.targetDb : this.sourceDb;
339
- }
340
- /** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
341
- static initElementProvenanceOptions(sourceElementId, targetElementId, args) {
342
- const elementId = args.isReverseSynchronization
343
- ? sourceElementId
344
- : targetElementId;
345
- const version = args.isReverseSynchronization
346
- ? args.targetDb.elements.queryLastModifiedTime(targetElementId)
347
- : args.sourceDb.elements.queryLastModifiedTime(sourceElementId);
348
- const aspectIdentifier = args.isReverseSynchronization
349
- ? targetElementId
350
- : sourceElementId;
351
- const aspectProps = {
352
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
353
- element: {
354
- id: elementId,
355
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
356
- },
357
- scope: { id: args.targetScopeElementId },
358
- identifier: aspectIdentifier,
359
- kind: core_backend_1.ExternalSourceAspect.Kind.Element,
360
- version,
361
- };
362
- return aspectProps;
363
- }
364
- static initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, args) {
365
- const provenanceDb = args.isReverseSynchronization
366
- ? args.sourceDb
367
- : args.targetDb;
368
- const aspectIdentifier = args.isReverseSynchronization
369
- ? targetRelInstanceId
370
- : sourceRelInstanceId;
371
- const provenanceRelInstanceId = args.isReverseSynchronization
372
- ? sourceRelInstanceId
373
- : targetRelInstanceId;
374
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
375
- const elementId = provenanceDb.withPreparedStatement("SELECT SourceECInstanceId FROM bis.ElementRefersToElements WHERE ECInstanceId=?", (stmt) => {
376
- stmt.bindId(1, provenanceRelInstanceId);
377
- nodeAssert(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW);
378
- return stmt.getValue(0).getId();
379
- });
380
- const jsonProperties = args.forceOldRelationshipProvenanceMethod
381
- ? { targetRelInstanceId }
382
- : { provenanceRelInstanceId };
383
- const aspectProps = {
384
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
385
- element: {
386
- id: elementId,
387
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
388
- },
389
- scope: { id: args.targetScopeElementId },
390
- identifier: aspectIdentifier,
391
- kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
392
- jsonProperties: JSON.stringify(jsonProperties),
393
- };
394
- return aspectProps;
395
- }
396
240
  /**
397
241
  * Previously the transformer would insert provenance always pointing to the "target" relationship.
398
242
  * It should (and now by default does) instead insert provenance pointing to the provenanceSource
@@ -401,43 +245,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
401
245
  */
402
246
  _forceOldRelationshipProvenanceMethod = false;
403
247
  /** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
404
- initElementProvenance(sourceElementId, targetElementId) {
405
- return IModelTransformer.initElementProvenanceOptions(sourceElementId, targetElementId, {
406
- isReverseSynchronization: this.isReverseSynchronization,
407
- targetScopeElementId: this.targetScopeElementId,
408
- sourceDb: this.sourceDb,
409
- targetDb: this.targetDb,
410
- });
411
- }
412
- /** Create an ExternalSourceAspectProps in a standard way for a Relationship in an iModel --> iModel transformations.
413
- * The ExternalSourceAspect is meant to be owned by the Element in the target iModel that is the `sourceId` of transformed relationship.
414
- * The `identifier` property of the ExternalSourceAspect will be the ECInstanceId of the relationship in the master iModel.
415
- * The ECInstanceId of the relationship in the branch iModel will be stored in the JsonProperties of the ExternalSourceAspect.
416
- */
417
- initRelationshipProvenance(sourceRelationship, targetRelInstanceId) {
418
- return IModelTransformer.initRelationshipProvenanceOptions(sourceRelationship.id, targetRelInstanceId, {
419
- sourceDb: this.sourceDb,
420
- targetDb: this.targetDb,
421
- isReverseSynchronization: this.isReverseSynchronization,
422
- targetScopeElementId: this.targetScopeElementId,
423
- forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
424
- });
425
- }
426
- /** NOTE: the json properties must be converted to string before insertion */
427
- _targetScopeProvenanceProps = undefined;
428
- /**
429
- * Index of the changeset that the transformer was at when the transformation begins (was constructed).
430
- * Used to determine at the end which changesets were part of a synchronization.
431
- */
432
- _startingChangesetIndices = undefined;
433
- _cachedSynchronizationVersion = undefined;
434
- /**
435
- * We cache the synchronization version to avoid querying the target scoping ESA multiple times.
436
- * If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
437
- * Sets this._cachedSynchronizationVersion to undefined.
438
- */
439
- clearCachedSynchronizationVersion() {
440
- this._cachedSynchronizationVersion = undefined;
248
+ async initElementProvenance(sourceElementId, targetElementId) {
249
+ return this._provenanceManager.initElementProvenance(sourceElementId, targetElementId);
441
250
  }
442
251
  /** the changeset in the scoping element's source version found for this transformation
443
252
  * @note the version depends on whether this is a reverse synchronization or not, as
@@ -446,448 +255,59 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
446
255
  * @note empty string and -1 for changeset and index if it was transformed before federation guid update (pre 1.x) and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] === "unsafe-migrate".
447
256
  * @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
448
257
  */
449
- get synchronizationVersion() {
450
- if (this._cachedSynchronizationVersion === undefined) {
451
- const provenanceScopeAspect = this.tryGetProvenanceScopeAspect();
452
- if (!provenanceScopeAspect) {
453
- return { index: -1, id: "" }; // first synchronization.
454
- }
455
- const version = this.isReverseSynchronization
456
- ? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
457
- : provenanceScopeAspect.version;
458
- if (!version &&
459
- this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
460
- return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
461
- }
462
- if (version === undefined) {
463
- throw new Error(`Could not find synchronization version in scope aspect. This may be due to the last successful run of the transformer being done with an older version.
464
- Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
465
- }
466
- const [id, index] = version === "" ? ["", -1] : version.split(";");
467
- if (Number.isNaN(Number(index)))
468
- throw new Error("Could not parse version data from scope aspect");
469
- this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
470
- }
471
- return this._cachedSynchronizationVersion;
258
+ async getSynchronizationVersion() {
259
+ return this._provenanceManager.getSynchronizationVersion();
472
260
  }
473
261
  /**
474
262
  * @returns provenance scope aspect if it exists in the provenanceDb.
475
263
  * Provenance scope aspect is created and inserted into provenanceDb when [[initScopeProvenance]] is invoked.
476
264
  */
477
- tryGetProvenanceScopeAspect() {
478
- const scopeProvenanceAspectProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, {
479
- id: undefined,
480
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
481
- scope: { id: core_common_1.IModel.rootSubjectId },
482
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
483
- element: { id: this.targetScopeElementId ?? core_common_1.IModel.rootSubjectId },
484
- identifier: this.provenanceSourceDb.iModelId,
485
- });
486
- return scopeProvenanceAspectProps !== undefined
487
- ? this.provenanceDb.elements.getAspect(scopeProvenanceAspectProps.aspectId)
488
- : undefined;
265
+ async tryGetProvenanceScopeAspect() {
266
+ return this._provenanceManager.tryGetProvenanceScopeAspect();
489
267
  }
490
268
  /**
491
269
  * Make sure there are no conflicting other scope-type external source aspects on the *target scope element*,
492
270
  * If there are none at all, insert one, then this must be a first synchronization.
493
- * @returns the last synced version (changesetId) on the target scope's external source aspect,
494
- * if this was a [BriefcaseDb]($backend)
495
271
  */
496
- initScopeProvenance() {
497
- const aspectProps = {
498
- id: undefined,
499
- version: undefined,
500
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
501
- element: {
502
- id: this.targetScopeElementId,
503
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
504
- },
505
- scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
506
- identifier: this.provenanceSourceDb.iModelId,
507
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
508
- jsonProperties: undefined,
509
- };
510
- const foundEsaProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, aspectProps); // this query includes "identifier"
511
- if (foundEsaProps === undefined) {
512
- aspectProps.version = ""; // empty since never before transformed. Will be updated in [[finalizeTransformation]]
513
- aspectProps.jsonProperties = {
514
- pendingReverseSyncChangesetIndices: [],
515
- pendingSyncChangesetIndices: [],
516
- reverseSyncVersion: "", // empty since never before transformed. Will be updated in first reverse sync
517
- };
518
- // this query does not include "identifier" to find possible conflicts
519
- const sql = `
520
- SELECT ECInstanceId
521
- FROM ${core_backend_1.ExternalSourceAspect.classFullName}
522
- WHERE Element.Id=:elementId
523
- AND Scope.Id=:scopeId
524
- AND Kind=:kind
525
- LIMIT 1
526
- `;
527
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
528
- const hasConflictingScope = this.provenanceDb.withPreparedStatement(sql,
529
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
530
- (statement) => {
531
- statement.bindId("elementId", aspectProps.element.id);
532
- statement.bindId("scopeId", aspectProps.scope.id); // this scope.id can never be invalid, we create it above
533
- statement.bindString("kind", aspectProps.kind);
534
- return core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step();
535
- });
536
- if (hasConflictingScope) {
537
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Provenance scope conflict");
538
- }
539
- if (!this._options.noProvenance) {
540
- const id = this.provenanceDb.elements.insertAspect({
541
- ...aspectProps,
542
- jsonProperties: JSON.stringify(aspectProps.jsonProperties),
543
- });
544
- aspectProps.id = id;
545
- // Busting a potential cached version
546
- this.clearCachedSynchronizationVersion();
547
- }
548
- }
549
- else {
550
- // foundEsaProps is defined.
551
- aspectProps.id = foundEsaProps.aspectId;
552
- aspectProps.version = foundEsaProps.version;
553
- aspectProps.jsonProperties = foundEsaProps.jsonProperties
554
- ? JSON.parse(foundEsaProps.jsonProperties)
555
- : undefined;
556
- // Clone oldProps incase they're changed for logging purposes
557
- const oldProps = JSON.parse(JSON.stringify(aspectProps));
558
- if (this.handleUnsafeMigrate(aspectProps)) {
559
- core_bentley_1.Logger.logInfo(loggerCategory, "Unsafe migrate made a change to the target scope's external source aspect. Updating aspect in database.", { oldProps, newProps: aspectProps });
560
- this.provenanceDb.elements.updateAspect({
561
- ...aspectProps,
562
- jsonProperties: JSON.stringify(aspectProps.jsonProperties),
563
- });
564
- // Busting a potential cached version
565
- this.clearCachedSynchronizationVersion();
566
- }
567
- }
568
- this._targetScopeProvenanceProps =
569
- aspectProps;
570
- }
571
- /** Returns true if a change was made to the aspectProps. */
572
- handleUnsafeMigrate(aspectProps) {
573
- let madeChange = false;
574
- if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
575
- return madeChange;
576
- const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
577
- const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
578
- "";
579
- if (aspectProps.version === undefined ||
580
- (aspectProps.version === "" &&
581
- aspectProps.version !== fallbackSyncVersionToUse)) {
582
- aspectProps.version = fallbackSyncVersionToUse;
583
- madeChange = true;
584
- }
585
- if (aspectProps.jsonProperties === undefined) {
586
- aspectProps.jsonProperties = {
587
- pendingReverseSyncChangesetIndices: [],
588
- pendingSyncChangesetIndices: [],
589
- reverseSyncVersion: fallbackReverseSyncVersionToUse,
590
- };
591
- madeChange = true;
592
- }
593
- else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
594
- (aspectProps.jsonProperties.reverseSyncVersion === "" &&
595
- aspectProps.jsonProperties.reverseSyncVersion !==
596
- fallbackReverseSyncVersionToUse)) {
597
- aspectProps.jsonProperties.reverseSyncVersion =
598
- fallbackReverseSyncVersionToUse;
599
- madeChange = true;
600
- }
601
- /**
602
- * This case will only be hit when:
603
- * - first transformation was performed on pre-fedguid transformer.
604
- * - a second processAll transformation was performed on the same target-source iModels post-fedguid transformer.
605
- * - change processing was invoked on for the second 'initial' transformation.
606
- * NOTE: This case likely does not exist anymore, but we will keep it just to be sure.
607
- */
608
- if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
609
- undefined) {
610
- core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
611
- aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
612
- madeChange = true;
613
- }
614
- if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
615
- core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
616
- aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
617
- madeChange = true;
618
- }
619
- return madeChange;
272
+ async initScopeProvenance() {
273
+ return this._provenanceManager.initScopeProvenance();
620
274
  }
621
275
  /**
622
- * Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
623
- * and call a function for each one.
624
- * @note provenance is done by federation guids where possible
625
- * @note this may execute on each element more than once! Only use in cases where that is handled
276
+ * Get the IModelDb where provenance (ExternalSourceAspects) is stored.
277
+ * This will be targetDb except when it is a reverse synchronization, in which case it will be sourceDb.
626
278
  */
627
- static forEachTrackedElement(args) {
628
- if (args.provenanceDb === args.provenanceSourceDb)
629
- return;
630
- if (!args.provenanceDb.containsClass(core_backend_1.ExternalSourceAspect.classFullName)) {
631
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadSchema, "The BisCore schema version of the target database is too old");
632
- }
633
- const sourceDb = args.isReverseSynchronization
634
- ? args.provenanceDb
635
- : args.provenanceSourceDb;
636
- const targetDb = args.isReverseSynchronization
637
- ? args.provenanceSourceDb
638
- : args.provenanceDb;
639
- // query for provenanceDb
640
- const elementIdByFedGuidQuery = `
641
- SELECT e.ECInstanceId, FederationGuid
642
- FROM bis.Element e
643
- ${args.skipPropagateChangesToRootElements
644
- ? "WHERE e.ECInstanceId NOT IN (0x1, 0xe, 0x10) -- special static elements"
645
- : ""}
646
- ORDER BY FederationGuid
647
- `;
648
- // iterate through sorted list of fed guids from both dbs to get the intersection
649
- // NOTE: if we exposed the native attach database support,
650
- // we could get the intersection of fed guids in one query, not sure if it would be faster
651
- // OR we could do a raw sqlite query...
652
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
653
- sourceDb.withStatement(elementIdByFedGuidQuery, (sourceStmt) =>
654
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
655
- targetDb.withStatement(elementIdByFedGuidQuery, (targetStmt) => {
656
- if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
657
- return;
658
- let sourceRow = sourceStmt.getRow();
659
- if (targetStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
660
- return;
661
- let targetRow = targetStmt.getRow();
662
- // NOTE: these comparisons rely upon the lowercase of the guid,
663
- // and the fact that '0' < '9' < a' < 'f' in ascii/utf8
664
- while (true) {
665
- const currSourceRow = sourceRow, currTargetRow = targetRow;
666
- if (currSourceRow.federationGuid !== undefined &&
667
- currTargetRow.federationGuid !== undefined &&
668
- currSourceRow.federationGuid === currTargetRow.federationGuid) {
669
- // data flow direction is always sourceDb -> targetDb and it does not depend on where the explicit element provenance is stored
670
- args.fn(sourceRow.id, targetRow.id);
671
- }
672
- if (currTargetRow.federationGuid === undefined ||
673
- (currSourceRow.federationGuid !== undefined &&
674
- currSourceRow.federationGuid >= currTargetRow.federationGuid)) {
675
- if (targetStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
676
- return;
677
- targetRow = targetStmt.getRow();
678
- }
679
- if (currSourceRow.federationGuid === undefined ||
680
- (currTargetRow.federationGuid !== undefined &&
681
- currSourceRow.federationGuid <= currTargetRow.federationGuid)) {
682
- if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
683
- return;
684
- sourceRow = sourceStmt.getRow();
685
- }
686
- }
687
- }));
688
- // query for provenanceDb
689
- const provenanceAspectsQuery = `
690
- SELECT esa.Identifier, Element.Id
691
- FROM bis.ExternalSourceAspect esa
692
- WHERE Scope.Id=:scopeId
693
- AND Kind=:kind
694
- `;
695
- // Technically this will a second time call the function (as documented) on
696
- // victims of the old provenance method that have both fedguids and an inserted aspect.
697
- // But this is a private function with one known caller where that doesn't matter
698
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
699
- args.provenanceDb.withPreparedStatement(provenanceAspectsQuery, (stmt) => {
700
- const runFnInDataFlowDirection = (sourceId, targetId) => args.isReverseSynchronization
701
- ? args.fn(sourceId, targetId)
702
- : args.fn(targetId, sourceId);
703
- stmt.bindId("scopeId", args.targetScopeElementId);
704
- stmt.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
705
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
706
- // ExternalSourceAspect.Identifier is of type string
707
- const aspectIdentifier = stmt.getValue(0).getString();
708
- const elementId = stmt.getValue(1).getId();
709
- runFnInDataFlowDirection(elementId, aspectIdentifier);
710
- }
711
- });
712
- }
713
- forEachTrackedElement(fn) {
714
- return IModelTransformer.forEachTrackedElement({
715
- provenanceSourceDb: this.provenanceSourceDb,
716
- provenanceDb: this.provenanceDb,
717
- targetScopeElementId: this.targetScopeElementId,
718
- isReverseSynchronization: this.isReverseSynchronization,
719
- fn,
720
- skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
721
- });
279
+ async getProvenanceDb() {
280
+ return (await this.getIsReverseSynchronization())
281
+ ? this.sourceDb
282
+ : this.targetDb;
722
283
  }
723
284
  /**
724
- * Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
725
- * The identifier on the ESA is the id of the element in the [[IModelTransformer.provenanceSourceDb]]
726
- * Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
727
- * @param entityInProvenanceSourceId
728
- * @returns the elementId that the ESA is stored on, esa.Element.Id
285
+ * Get whether this is a reverse synchronization.
729
286
  */
730
- _queryProvenanceForElement(entityInProvenanceSourceId) {
731
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
732
- return this.provenanceDb.withPreparedStatement(`
733
- SELECT esa.Element.Id
734
- FROM Bis.ExternalSourceAspect esa
735
- WHERE esa.Kind=?
736
- AND esa.Scope.Id=?
737
- AND esa.Identifier=?
738
- `, (stmt) => {
739
- stmt.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Element);
740
- stmt.bindId(2, this.targetScopeElementId);
741
- stmt.bindString(3, entityInProvenanceSourceId);
742
- if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
743
- return stmt.getValue(0).getId();
744
- else
745
- return undefined;
746
- });
287
+ async getIsReverseSynchronization() {
288
+ return (await this._syncTypeResolver.getSyncType()) === "reverse";
747
289
  }
748
290
  /**
749
- * Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
750
- * The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
751
- * Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
752
- * @param entityInProvenanceSourceId
753
- * @returns
291
+ * Get whether this is a forward synchronization.
754
292
  */
755
- _queryProvenanceForRelationship(entityInProvenanceSourceId, sourceRelInfo) {
756
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
757
- return this.provenanceDb.withPreparedStatement(`
758
- SELECT
759
- ECInstanceId,
760
- JSON_EXTRACT(JsonProperties, '$.targetRelInstanceId'),
761
- JSON_EXTRACT(JsonProperties, '$.provenanceRelInstanceId')
762
- FROM Bis.ExternalSourceAspect
763
- WHERE Kind=?
764
- AND Scope.Id=?
765
- AND Identifier=?
766
- `, (stmt) => {
767
- stmt.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Relationship);
768
- stmt.bindId(2, this.targetScopeElementId);
769
- stmt.bindString(3, entityInProvenanceSourceId);
770
- if (stmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
771
- return undefined;
772
- const aspectId = stmt.getValue(0).getId();
773
- const provenanceRelInstIdVal = stmt.getValue(2);
774
- const provenanceRelInstanceId = !provenanceRelInstIdVal.isNull
775
- ? provenanceRelInstIdVal.getString()
776
- : this._queryTargetRelId(sourceRelInfo);
777
- return {
778
- aspectId,
779
- relationshipId: provenanceRelInstanceId,
780
- };
781
- });
782
- }
783
- _queryTargetRelId(sourceRelInfo) {
784
- const targetRelInfo = {
785
- sourceId: this.context.findTargetElementId(sourceRelInfo.sourceId),
786
- targetId: this.context.findTargetElementId(sourceRelInfo.targetId),
787
- };
788
- if (targetRelInfo.sourceId === undefined ||
789
- targetRelInfo.targetId === undefined)
790
- return undefined; // couldn't find an element, rel is invalid or deleted
791
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
792
- return this.targetDb.withPreparedStatement(`
793
- SELECT ECInstanceId
794
- FROM bis.ElementRefersToElements
795
- WHERE SourceECInstanceId=?
796
- AND TargetECInstanceId=?
797
- AND ECClassId=?
798
- `, (stmt) => {
799
- stmt.bindId(1, targetRelInfo.sourceId);
800
- stmt.bindId(2, targetRelInfo.targetId);
801
- stmt.bindId(3, this._targetClassNameToClassId(sourceRelInfo.classFullName));
802
- if (stmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
803
- return undefined;
804
- return stmt.getValue(0).getId();
805
- });
806
- }
807
- _targetClassNameToClassIdCache = new Map();
808
- _targetClassNameToClassId(classFullName) {
809
- let classId = this._targetClassNameToClassIdCache.get(classFullName);
810
- if (classId === undefined) {
811
- classId = this._getRelClassId(this.targetDb, classFullName);
812
- this._targetClassNameToClassIdCache.set(classFullName, classId);
813
- }
814
- return classId;
815
- }
816
- // NOTE: this doesn't handle remapped element classes,
817
- // but is only used for relationships rn
818
- _getRelClassId(db, classFullName) {
819
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
820
- return db.withPreparedStatement(`
821
- SELECT c.ECInstanceId
822
- FROM ECDbMeta.ECClassDef c
823
- JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
824
- WHERE s.Name=? AND c.Name=?
825
- `, (stmt) => {
826
- const [schemaName, className] = classFullName.indexOf(".") !== -1
827
- ? classFullName.split(".")
828
- : classFullName.split(":");
829
- stmt.bindString(1, schemaName);
830
- stmt.bindString(2, className);
831
- if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
832
- return stmt.getValue(0).getId();
833
- (0, core_bentley_1.assert)(false, "relationship was not found");
834
- });
293
+ async getIsForwardSynchronization() {
294
+ return (await this._syncTypeResolver.getSyncType()) === "forward";
835
295
  }
836
- _queryElemIdByFedGuid(db, fedGuid) {
837
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
838
- return db.withPreparedStatement("SELECT ECInstanceId FROM Bis.Element WHERE FederationGuid=?", (stmt) => {
839
- stmt.bindGuid(1, fedGuid);
840
- if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
841
- return stmt.getValue(0).getId();
842
- else
843
- return undefined;
296
+ /**
297
+ * Updates the synchronization version on the scope ESA.
298
+ */
299
+ async updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
300
+ return this._provenanceManager.updateSynchronizationVersion({
301
+ initializeReverseSyncVersion,
302
+ sourceChangeDataState: this._sourceChangeDataState,
844
303
  });
845
304
  }
846
305
  /** Returns `true` if *brute force* delete detections should be run.
847
306
  * @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
848
307
  * @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
849
308
  */
850
- shouldDetectDeletes() {
851
- nodeAssert(this._syncType !== undefined);
852
- return this._syncType === "not-sync";
853
- }
854
- /**
855
- * Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements
856
- * in the source iModel.
857
- * @deprecated in 1.x. Do not use this. // FIXME<MIKE>: how to better explain this?
858
- * This method is only called during [[process]] when [[IModelTransformOptions.argsForProcessChanges]] is undefined and the option
859
- * [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
860
- * necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] defined, since changeset information is sufficient.
861
- * @note you do not need to call this directly unless processing a subset of an iModel.
862
- * @throws [[IModelError]] If the required provenance information is not available to detect deletes.
863
- */
864
- async detectElementDeletes() {
865
- const sql = `
866
- SELECT Identifier, Element.Id
867
- FROM BisCore.ExternalSourceAspect
868
- WHERE Scope.Id=:scopeId
869
- AND Kind=:kind
870
- `;
871
- nodeAssert(!this.isReverseSynchronization, "synchronizations with processChanges already detect element deletes, don't call detectElementDeletes");
872
- // Reported issue: https://github.com/iTwin/itwinjs-core/issues/7989
873
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
874
- this.provenanceDb.withPreparedStatement(sql, (stmt) => {
875
- stmt.bindId("scopeId", this.targetScopeElementId);
876
- stmt.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
877
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
878
- // ExternalSourceAspect.Identifier is of type string
879
- const aspectIdentifier = stmt.getValue(0).getString();
880
- if (!core_bentley_1.Id64.isValidId64(aspectIdentifier)) {
881
- continue;
882
- }
883
- const targetElemId = stmt.getValue(1).getId();
884
- const wasDeletedInSource = !EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
885
- entityReference: `e${aspectIdentifier}`,
886
- });
887
- if (wasDeletedInSource)
888
- this.importer.deleteElement(targetElemId);
889
- }
890
- });
309
+ async shouldDetectDeletes() {
310
+ return (await this._syncTypeResolver.getSyncType()) === "not-sync";
891
311
  }
892
312
  /** Transform the specified sourceElement into ElementProps for the target iModel.
893
313
  * @param sourceElement The Element from the source iModel to transform.
@@ -895,9 +315,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
895
315
  * @note A subclass can override this method to provide custom transform behavior.
896
316
  * @note This can be called more than once for an element in arbitrary order, so it should not have side-effects.
897
317
  */
898
- onTransformElement(sourceElement) {
318
+ async onTransformElement(sourceElement) {
899
319
  core_bentley_1.Logger.logTrace(loggerCategory, `onTransformElement(${sourceElement.id}) "${sourceElement.getDisplayLabel()}"`);
900
- const targetElementProps = this.context.cloneElement(sourceElement, { binaryGeometry: this._options.cloneUsingBinaryGeometry });
320
+ const targetElementProps = await this.context.cloneElement(sourceElement, { binaryGeometry: this._options.cloneUsingBinaryGeometry });
901
321
  // Special case: source element is the root subject
902
322
  if (sourceElement.id === core_common_1.IModel.rootSubjectId) {
903
323
  const targetElementId = this.context.findTargetElementId(sourceElement.id);
@@ -918,8 +338,87 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
918
338
  targetElementProps.jsonProperties.Subject.Job = undefined;
919
339
  }
920
340
  }
341
+ if (this._linearSpatialTransform !== undefined &&
342
+ sourceElement instanceof core_backend_1.GeometricElement3d) {
343
+ // can check the sourceElement since this IModelTransformer does not remap classes
344
+ const placement = core_common_1.Placement3d.fromJSON(targetElementProps.placement);
345
+ if (placement.isValid) {
346
+ placement.multiplyTransform(this._linearSpatialTransform);
347
+ targetElementProps.placement = placement;
348
+ }
349
+ }
921
350
  return targetElementProps;
922
351
  }
352
+ /**
353
+ * Calculate the transform between two ECEF locations
354
+ * @param srcDb
355
+ * @param targetDb
356
+ * @returns Transform that converts relative coordinates in the source iModel to relative coordinates in the target iModel.
357
+ * @note This can only be used if both source and target iModels are linearly located
358
+ */
359
+ calculateEcefTransform() {
360
+ const srcEcefLoc = this.sourceDb.ecefLocation;
361
+ const targetEcefLoc = this.targetDb.ecefLocation;
362
+ if (srcEcefLoc === undefined || targetEcefLoc === undefined) {
363
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NoGeoLocation, "Both source and target ECEF locations must be defined to calculate the transform.");
364
+ }
365
+ if (srcEcefLoc.getTransform().isAlmostEqual(targetEcefLoc.getTransform())) {
366
+ core_bentley_1.Logger.logTrace(loggerCategory, "ECEF data is already aligned. No spatial transforms needed.");
367
+ return undefined;
368
+ }
369
+ const srcSpatialToECEF = srcEcefLoc.getTransform(); // converts relative to ECEF in relation to source
370
+ const targetECEFToSpatial = targetEcefLoc.getTransform().inverse(); // converts ECEF to relative in relation to target
371
+ if (!targetECEFToSpatial) {
372
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NoGeoLocation, "Failed to invert target ECEF transform.");
373
+ }
374
+ const ecefTransform = targetECEFToSpatial.multiplyTransformTransform(srcSpatialToECEF); // chain both transforms
375
+ return ecefTransform;
376
+ }
377
+ static convertHelmertToTransform(helmert) {
378
+ if (!helmert) {
379
+ return core_geometry_1.Transform.createIdentity();
380
+ }
381
+ const rotationXY = core_geometry_1.Matrix3d.createRotationAroundAxisIndex(core_geometry_1.AxisIndex.Z, core_geometry_1.Angle.createDegrees(helmert?.rotDeg));
382
+ rotationXY.scaleColumnsInPlace(helmert.scale, helmert.scale, 1.0);
383
+ const translation = core_geometry_1.Vector3d.create(helmert.translationX, helmert.translationY, helmert.translationZ);
384
+ const helmertTransform = core_geometry_1.Transform.createRefs(translation, rotationXY);
385
+ return helmertTransform;
386
+ }
387
+ calculateTransformFromHelmertTransforms() {
388
+ if (this.sourceDb.geographicCoordinateSystem?.horizontalCRS === undefined ||
389
+ this.sourceDb.geographicCoordinateSystem?.verticalCRS === undefined) {
390
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Source iModel does not have a geographic coordinate system defined.");
391
+ }
392
+ if (this.targetDb.geographicCoordinateSystem?.horizontalCRS === undefined ||
393
+ this.targetDb.geographicCoordinateSystem.verticalCRS === undefined) {
394
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Target iModel does not have a geographic coordinate system defined.");
395
+ }
396
+ if (!this.sourceDb.geographicCoordinateSystem.horizontalCRS.equals(this.targetDb.geographicCoordinateSystem.horizontalCRS) ||
397
+ !this.sourceDb.geographicCoordinateSystem.verticalCRS.equals(this.targetDb.geographicCoordinateSystem.verticalCRS)) {
398
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.MismatchGcs, "Source and target geographic coordinate systems must match to calculate the spatial transform.");
399
+ }
400
+ if (this.sourceDb.geographicCoordinateSystem.additionalTransform ===
401
+ this.targetDb.geographicCoordinateSystem.additionalTransform) {
402
+ core_bentley_1.Logger.logTrace(loggerCategory, "Geolocation data is already aligned. No spatial transforms needed.");
403
+ return undefined;
404
+ }
405
+ const srcScale = this.sourceDb.geographicCoordinateSystem.additionalTransform
406
+ ?.helmert2DWithZOffset?.scale ?? 1;
407
+ const targetScale = this.targetDb.geographicCoordinateSystem.additionalTransform
408
+ ?.helmert2DWithZOffset?.scale ?? 1;
409
+ if (srcScale !== targetScale) {
410
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.MismatchGcs, "Spatial transform is non rigid. Source and target Helmert transforms must have the same scale to calculate a rigid spatial transform.");
411
+ }
412
+ const srcTransform = IModelTransformer.convertHelmertToTransform(this.sourceDb.geographicCoordinateSystem.additionalTransform
413
+ ?.helmert2DWithZOffset); // moves elements to where src helmert transform would move them at render time
414
+ const targetTransformInv = IModelTransformer.convertHelmertToTransform(this.targetDb.geographicCoordinateSystem.additionalTransform
415
+ ?.helmert2DWithZOffset).inverse(); // negates target helmert transform that is applied at render time
416
+ if (!targetTransformInv) {
417
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NoGeoLocation, "Failed to invert target Helmert transform.");
418
+ }
419
+ const combinedTransform = targetTransformInv.multiplyTransformTransform(srcTransform);
420
+ return combinedTransform;
421
+ }
923
422
  // if undefined, it can be initialized by calling [[this.processChangesets]]
924
423
  _deletedSourceRelationshipData = undefined;
925
424
  /** Returns true if a change within sourceElement is detected.
@@ -932,7 +431,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
932
431
  sourceDbChanges.element.insertIds.has(sourceElement.id) ||
933
432
  sourceDbChanges.element.updateIds.has(sourceElement.id));
934
433
  }
935
- completePartiallyCommittedElements() {
434
+ async completePartiallyCommittedElements() {
936
435
  for (const sourceElementId of this._partiallyCommittedElementIds) {
937
436
  const sourceElement = this.sourceDb.elements.getElement({
938
437
  id: sourceElementId,
@@ -943,25 +442,27 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
943
442
  if (core_bentley_1.Id64.isInvalid(targetId)) {
944
443
  throw new Error(`source-target element mapping not found for element "${sourceElementId}" when completing partially committed elements. This is a bug.`);
945
444
  }
946
- const targetProps = this.onTransformElement(sourceElement);
445
+ const targetProps = await this.onTransformElement(sourceElement);
446
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
947
447
  this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
948
448
  }
949
449
  }
950
- completePartiallyCommittedAspects() {
450
+ async completePartiallyCommittedAspects() {
951
451
  for (const sourceAspectId of this._partiallyCommittedAspectIds) {
952
452
  const sourceAspect = this.sourceDb.elements.getAspect(sourceAspectId);
953
453
  const targetAspectId = this.context.findTargetAspectId(sourceAspectId);
954
454
  if (core_bentley_1.Id64.isInvalid(targetAspectId)) {
955
455
  throw new Error(`source-target aspect mapping not found for aspect "${sourceAspectId}" when completing partially committed aspects. This is a bug.`);
956
456
  }
957
- const targetAspectProps = this.onTransformElementAspect(sourceAspect);
457
+ const targetAspectProps = await this.onTransformElementAspect(sourceAspect);
458
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
958
459
  this.targetDb.elements.updateAspect({
959
460
  ...targetAspectProps,
960
461
  id: targetAspectId,
961
462
  });
962
463
  }
963
464
  }
964
- doAllReferencesExistInTarget(entity) {
465
+ async doAllReferencesExistInTarget(entity) {
965
466
  let allReferencesExist = true;
966
467
  for (const referenceId of entity.getReferenceIds()) {
967
468
  const referencedEntityId = core_backend_1.EntityReferences.toId64(referenceId);
@@ -971,7 +472,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
971
472
  continue;
972
473
  }
973
474
  if (allReferencesExist &&
974
- !core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
475
+ !core_backend_1.EntityReferences.isValid(await this.context.findTargetEntityId(referenceId))) {
975
476
  // if we care about references existing then we cannot return early and must check all other references.
976
477
  if (this._options.danglingReferencesBehavior === "ignore") {
977
478
  return false;
@@ -979,13 +480,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
979
480
  allReferencesExist = false;
980
481
  }
981
482
  if (this._options.danglingReferencesBehavior === "reject") {
982
- this.assertReferenceExistsInSource(referenceId, entity);
483
+ await this.assertReferenceExistsInSource(referenceId, entity);
983
484
  }
984
485
  }
985
486
  return allReferencesExist;
986
487
  }
987
- assertReferenceExistsInSource(referenceId, entity) {
988
- const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
488
+ async assertReferenceExistsInSource(referenceId, entity) {
489
+ const referencedExistsInSource = await EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
989
490
  entityReference: referenceId,
990
491
  });
991
492
  if (!referencedExistsInSource) {
@@ -1019,7 +520,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1019
520
  /** Override of [IModelExportHandler.shouldExportElement]($transformer) that is called to determine if an element should be exported from the source iModel.
1020
521
  * @note Reaching this point means that the element has passed the standard exclusion checks in IModelExporter.
1021
522
  */
1022
- shouldExportElement(_sourceElement) {
523
+ async shouldExportElement(_sourceElement) {
1023
524
  return true;
1024
525
  }
1025
526
  /**
@@ -1027,6 +528,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1027
528
  * @internal do not call, override or implement this, it will be removed
1028
529
  */
1029
530
  async preExportElement(sourceElement) {
531
+ if (!this.hasElementChanged(sourceElement)) {
532
+ core_bentley_1.Logger.logTrace(loggerCategory, `Skipping unchanged element (${sourceElement.id}, ${sourceElement.getDisplayLabel()}).`);
533
+ return;
534
+ }
1030
535
  const elemClass = sourceElement.constructor;
1031
536
  const unresolvedReferences = elemClass.requiredReferenceKeys
1032
537
  .map((referenceKey) => {
@@ -1061,7 +566,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1061
566
  .flat();
1062
567
  if (unresolvedReferences.length > 0) {
1063
568
  for (const reference of unresolvedReferences) {
1064
- const processState = this.getElemTransformState(reference);
569
+ const processState = await this.getElemTransformState(reference);
1065
570
  // must export element first
1066
571
  if (processState.needsElemImport)
1067
572
  await this.exporter.exportElement(reference);
@@ -1070,23 +575,43 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1070
575
  }
1071
576
  }
1072
577
  }
1073
- getElemTransformState(elementId) {
1074
- const dbHasModel = (db, id) => {
578
+ async getElemTransformState(elementId) {
579
+ const dbHasModel = async (db, id) => {
1075
580
  const maybeModelId = core_backend_1.EntityReferences.fromEntityType(id, core_common_1.ConcreteEntityTypes.Model);
1076
581
  return EntityUnifier_1.EntityUnifier.exists(db, { entityReference: maybeModelId });
1077
582
  };
1078
- const isSubModeled = dbHasModel(this.sourceDb, elementId);
583
+ const isSubModeled = await dbHasModel(this.sourceDb, elementId);
1079
584
  const idOfElemInTarget = this.context.findTargetElementId(elementId);
1080
585
  const isElemInTarget = core_bentley_1.Id64.invalid !== idOfElemInTarget;
1081
586
  const needsModelImport = isSubModeled &&
1082
- (!isElemInTarget || !dbHasModel(this.targetDb, idOfElemInTarget));
587
+ (!isElemInTarget || !(await dbHasModel(this.targetDb, idOfElemInTarget)));
1083
588
  return { needsElemImport: !isElemInTarget, needsModelImport };
1084
589
  }
590
+ // In iTwin js 5.x Elements.queryElementIdByCode() uses Code class to query id:
591
+ // https://github.com/iTwin/itwinjs-core/blob/master/core/backend/src/IModelDb.ts#L2779
592
+ // Code class constructor trims white spaces from code value.
593
+ // Custom implementation of queryElementIdByCode() was added to support querying elements with code values that have trailing whitespaces.
594
+ // It mimicks 4.x implementation: https://github.com/iTwin/itwinjs-core/blob/9c8b394ec3878a39764be81f928fd8b0b9115d31/core/backend/src/IModelDb.ts#L1882
595
+ async queryElementIdByCode(iModel, code) {
596
+ if (core_bentley_1.Id64.isInvalid(code.spec))
597
+ throw new Error("Invalid CodeSpec");
598
+ if (code.value === undefined)
599
+ throw new Error("Invalid Code");
600
+ const query = "SELECT ECInstanceId FROM BisCore:Element WHERE CodeSpec.Id=? AND CodeScope.Id=? AND CodeValue=?";
601
+ const queryBinder = new core_common_1.QueryBinder()
602
+ .bindId(1, code.spec)
603
+ .bindId(2, core_bentley_1.Id64.fromString(code.scope))
604
+ .bindString(3, code.value);
605
+ const queryReader = iModel.createQueryReader(query, queryBinder, {
606
+ usePrimaryConn: true,
607
+ });
608
+ return (await queryReader.step()) ? queryReader.current[0] : undefined;
609
+ }
1085
610
  /** Override of [IModelExportHandler.onExportElement]($transformer) that imports an element into the target iModel when it is exported from the source iModel.
1086
611
  * This override calls [[onTransformElement]] and then [IModelImporter.importElement]($transformer) to update the target iModel.
1087
612
  */
1088
- onExportElement(sourceElement) {
1089
- let targetElementId;
613
+ async onExportElement(sourceElement) {
614
+ let targetElementId = core_bentley_1.Id64.invalid;
1090
615
  let targetElementProps;
1091
616
  if (this._options.wasSourceIModelCopiedToTarget) {
1092
617
  targetElementId = sourceElement.id;
@@ -1095,14 +620,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1095
620
  }
1096
621
  else {
1097
622
  targetElementId = this.context.findTargetElementId(sourceElement.id);
1098
- targetElementProps = this.onTransformElement(sourceElement);
623
+ targetElementProps = await this.onTransformElement(sourceElement);
1099
624
  }
1100
625
  // if an existing remapping was not yet found, check by FederationGuid
1101
626
  if (this.context.isBetweenIModels &&
1102
627
  !core_bentley_1.Id64.isValid(targetElementId) &&
1103
628
  sourceElement.federationGuid !== undefined) {
1104
629
  targetElementId =
1105
- this._queryElemIdByFedGuid(this.targetDb, sourceElement.federationGuid) ?? core_bentley_1.Id64.invalid;
630
+ this.targetDb.elements.getIdFromFederationGuid(sourceElement.federationGuid) ?? core_bentley_1.Id64.invalid;
1106
631
  if (core_bentley_1.Id64.isValid(targetElementId))
1107
632
  this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found
1108
633
  }
@@ -1112,13 +637,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1112
637
  // respond the same way to undefined code value as the @see Code class, but don't use that class because it trims
1113
638
  // whitespace from the value, and there are iModels out there with untrimmed whitespace that we ought not to trim
1114
639
  targetElementProps.code.value = targetElementProps.code.value ?? "";
1115
- const maybeTargetElementId = this.targetDb.elements.queryElementIdByCode(targetElementProps.code);
640
+ const maybeTargetElementId = await this.queryElementIdByCode(this.targetDb, targetElementProps.code);
1116
641
  if (undefined !== maybeTargetElementId) {
1117
642
  const maybeTargetElem = this.targetDb.elements.getElement(maybeTargetElementId);
1118
643
  if (maybeTargetElem.classFullName === targetElementProps.classFullName) {
1119
644
  // ensure code remapping doesn't change the target class
1120
645
  targetElementId = maybeTargetElementId;
1121
646
  this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found by Code
647
+ this._targetElementIdsRemappedByCode.add(targetElementId);
1122
648
  }
1123
649
  else {
1124
650
  targetElementProps.code = core_common_1.Code.createEmpty(); // clear out invalid code
@@ -1129,7 +655,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1129
655
  core_bentley_1.Logger.logTrace(loggerCategory, `Skipping unchanged element (${sourceElement.id}, ${sourceElement.getDisplayLabel()}).`);
1130
656
  return;
1131
657
  }
1132
- if (!this.doAllReferencesExistInTarget(sourceElement)) {
658
+ if (!(await this.doAllReferencesExistInTarget(sourceElement))) {
1133
659
  this._partiallyCommittedElementIds.add(sourceElement.id);
1134
660
  }
1135
661
  // targetElementId will be valid (indicating update) or undefined (indicating insert)
@@ -1160,9 +686,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1160
686
  }
1161
687
  }
1162
688
  if (!this._options.wasSourceIModelCopiedToTarget) {
1163
- this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
689
+ await this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
690
+ }
691
+ if (targetElementProps.id === undefined) {
692
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "targetElementProps.id should be assigned by importElement");
1164
693
  }
1165
- this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
694
+ this.context.remapElement(sourceElement.id, targetElementProps.id);
1166
695
  // the transformer does not currently 'split' or 'join' any elements, therefore, it does not
1167
696
  // insert external source aspects because federation guids are sufficient for this.
1168
697
  // Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
@@ -1170,39 +699,44 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1170
699
  // physical consolidation is an example of a 'joining' transform
1171
700
  // FIXME: verify at finalization time that we don't lose provenance on new elements
1172
701
  // FIXME: make public and improve `initElementProvenance` API for usage by consolidators
702
+ const provenanceDb = await this.getProvenanceDb();
1173
703
  if (!this._options.noProvenance) {
1174
- let provenance = this._options.forceExternalSourceAspectProvenance ||
704
+ const provenance = this._options.forceExternalSourceAspectProvenance ||
1175
705
  this._elementsWithExplicitlyTrackedProvenance.has(sourceElement.id)
1176
706
  ? undefined
1177
707
  : sourceElement.federationGuid;
1178
708
  if (!provenance) {
1179
- const aspectProps = this.initElementProvenance(sourceElement.id, targetElementProps.id);
1180
- const foundEsaProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, aspectProps);
709
+ const aspectProps = await this.initElementProvenance(sourceElement.id, targetElementProps.id);
710
+ const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
1181
711
  if (foundEsaProps === undefined)
1182
- aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
712
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
713
+ aspectProps.id = provenanceDb.elements.insertAspect(aspectProps);
1183
714
  else {
1184
715
  // Since initElementProvenance sets a property 'version' on the aspectProps that we wish to persist in the provenanceDb, only grab the id from the foundEsaProps.
1185
716
  aspectProps.id = foundEsaProps.aspectId;
1186
- this.provenanceDb.elements.updateAspect(aspectProps);
717
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
718
+ provenanceDb.elements.updateAspect(aspectProps);
1187
719
  }
1188
- provenance = aspectProps;
1189
720
  }
1190
- this.markLastProvenance(provenance, { isRelationship: false });
1191
721
  }
1192
722
  }
1193
723
  /** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
1194
724
  * This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
1195
725
  */
1196
- onDeleteElement(sourceElementId) {
726
+ async onDeleteElement(sourceElementId) {
1197
727
  const targetElementId = this.context.findTargetElementId(sourceElementId);
1198
728
  if (core_bentley_1.Id64.isValidId64(targetElementId)) {
1199
- this.importer.deleteElement(targetElementId);
729
+ // Skip deletion if new / updated source element was remapped to it by Code during
730
+ // this transformation pass.
731
+ if (!this._targetElementIdsRemappedByCode.has(targetElementId)) {
732
+ await this.importer.deleteElement(targetElementId);
733
+ }
1200
734
  }
1201
735
  }
1202
736
  /** Override of [IModelExportHandler.onExportModel]($transformer) that is called when a Model should be exported from the source iModel.
1203
737
  * This override calls [[onTransformModel]] and then [IModelImporter.importModel]($transformer) to update the target iModel.
1204
738
  */
1205
- onExportModel(sourceModel) {
739
+ async onExportModel(sourceModel) {
1206
740
  if (this._options.skipPropagateChangesToRootElements &&
1207
741
  core_common_1.IModel.repositoryModelId === sourceModel.id)
1208
742
  return; // The RepositoryModel should not be directly imported
@@ -1213,16 +747,27 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1213
747
  if (isRemappedRootSubject)
1214
748
  return;
1215
749
  const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
1216
- this.importer.importModel(targetModelProps);
750
+ await this.importer.importModel(targetModelProps);
751
+ if (targetModelProps.id === undefined) {
752
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadModel, "targetModelProps.id should be assigned by now");
753
+ }
754
+ this._targetModelsImportedInCurrentTransform.add(targetModelProps.id);
1217
755
  }
1218
756
  /** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
1219
- onDeleteModel(sourceModelId) {
757
+ async onDeleteModel(sourceModelId) {
1220
758
  // It is possible and apparently occasionally sensical to delete a model without deleting its underlying element.
1221
759
  // - If only the model is deleted, [[initFromExternalSourceAspects]] will have already remapped the underlying element since it still exists.
1222
760
  // - If both were deleted, [[remapDeletedSourceEntities]] will find and remap the deleted element making this operation valid
1223
761
  const targetModelId = this.context.findTargetElementId(sourceModelId);
1224
762
  if (!core_bentley_1.Id64.isValidId64(targetModelId))
1225
763
  return;
764
+ // This handles cases where model with a partition element was remapped to
765
+ // new element by code value after recreation.
766
+ if (this._targetElementIdsRemappedByCode.has(targetModelId) &&
767
+ this._targetModelsImportedInCurrentTransform.has(targetModelId)) {
768
+ core_bentley_1.Logger.logTrace(loggerCategory, `Skipping delete operation for model (source id: ${sourceModelId}) because the target model (id: ${targetModelId}) was remapped by Code and re-imported during this transformation pass.`);
769
+ return;
770
+ }
1226
771
  const sql = `
1227
772
  SELECT 1
1228
773
  FROM bis.DefinitionPartition
@@ -1233,19 +778,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1233
778
  WHERE ECInstanceId=:targetModelId
1234
779
  `;
1235
780
  if (this.exporter.sourceDbChanges?.element.deleteIds.has(sourceModelId)) {
1236
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1237
- const isDefinitionPartition = this.targetDb.withPreparedStatement(sql, (stmt) => {
1238
- stmt.bindId("targetModelId", targetModelId);
1239
- const val = stmt.step();
1240
- switch (val) {
1241
- case core_bentley_1.DbResult.BE_SQLITE_ROW:
1242
- return true;
1243
- case core_bentley_1.DbResult.BE_SQLITE_DONE:
1244
- return false;
1245
- default:
1246
- (0, core_bentley_1.assert)(false, `unexpected db result: '${JSON.stringify(stmt)}'`);
1247
- }
781
+ const params = new core_common_1.QueryBinder().bindId("targetModelId", targetModelId);
782
+ const reader = this.targetDb.createQueryReader(sql, params, {
783
+ usePrimaryConn: true,
1248
784
  });
785
+ const isDefinitionPartition = await reader.step();
1249
786
  if (isDefinitionPartition) {
1250
787
  // Skipping model deletion because model's partition will also be deleted.
1251
788
  // It expects that model will be present and will fail if it's missing.
@@ -1254,7 +791,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1254
791
  }
1255
792
  }
1256
793
  try {
1257
- this.importer.deleteModel(targetModelId);
794
+ await this.importer.deleteModel(targetModelId);
1258
795
  }
1259
796
  catch (error) {
1260
797
  const isDeletionProhibitedErr = error instanceof core_common_1.IModelError &&
@@ -1301,41 +838,24 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1301
838
  await this.initialize();
1302
839
  // import DefinitionModels first
1303
840
  const childDefinitionPartitionSql = `SELECT ECInstanceId FROM ${core_backend_1.DefinitionPartition.classFullName} WHERE Parent.Id=:subjectId`;
1304
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1305
- await this.sourceDb.withPreparedStatement(childDefinitionPartitionSql,
1306
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1307
- async (statement) => {
1308
- statement.bindId("subjectId", sourceSubjectId);
1309
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1310
- await this.processModel(statement.getValue(0).getId());
1311
- }
1312
- });
841
+ const params = new core_common_1.QueryBinder().bindId("subjectId", sourceSubjectId);
842
+ for await (const row of this.sourceDb.createQueryReader(childDefinitionPartitionSql, params, { usePrimaryConn: true })) {
843
+ await this.processModel(row.id);
844
+ }
1313
845
  // import other partitions next
1314
846
  const childPartitionSql = `SELECT ECInstanceId FROM ${core_backend_1.InformationPartitionElement.classFullName} WHERE Parent.Id=:subjectId`;
1315
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1316
- await this.sourceDb.withPreparedStatement(childPartitionSql,
1317
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1318
- async (statement) => {
1319
- statement.bindId("subjectId", sourceSubjectId);
1320
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1321
- const modelId = statement.getValue(0).getId();
1322
- const model = this.sourceDb.models.getModel(modelId);
1323
- if (!(model instanceof core_backend_1.DefinitionModel)) {
1324
- await this.processModel(modelId);
1325
- }
847
+ for await (const row of this.sourceDb.createQueryReader(childPartitionSql, params, { usePrimaryConn: true })) {
848
+ const modelId = row.id;
849
+ const model = this.sourceDb.models.getModel(modelId);
850
+ if (!(model instanceof core_backend_1.DefinitionModel)) {
851
+ await this.processModel(modelId);
1326
852
  }
1327
- });
853
+ }
1328
854
  // recurse into child Subjects
1329
855
  const childSubjectSql = `SELECT ECInstanceId FROM ${core_backend_1.Subject.classFullName} WHERE Parent.Id=:subjectId`;
1330
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1331
- await this.sourceDb.withPreparedStatement(childSubjectSql,
1332
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1333
- async (statement) => {
1334
- statement.bindId("subjectId", sourceSubjectId);
1335
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1336
- await this.processSubjectSubModels(statement.getValue(0).getId());
1337
- }
1338
- });
856
+ for await (const row of this.sourceDb.createQueryReader(childSubjectSql, params, { usePrimaryConn: true })) {
857
+ await this.processSubjectSubModels(row.id);
858
+ }
1339
859
  }
1340
860
  /** Transform the specified sourceModel into ModelProps for the target iModel.
1341
861
  * @param sourceModel The Model from the source iModel to be transformed.
@@ -1351,100 +871,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1351
871
  id: targetModeledElementId,
1352
872
  };
1353
873
  targetModelProps.id = targetModeledElementId;
874
+ if (targetModelProps.parentModel === undefined) {
875
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "targetElementProps must have a defined parentModel");
876
+ }
1354
877
  targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
1355
878
  return targetModelProps;
1356
879
  }
1357
- /**
1358
- * Called at the end of a transformation,
1359
- * updates the target scope element to say that transformation up through the
1360
- * source's changeset has been performed. Also stores all changesets that occurred
1361
- * during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
1362
- *
1363
- * You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
1364
- * It is public for unsupported use cases of custom synchronization transforms.
1365
- * @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
1366
- * unless the `initializeReverseSyncVersion` option is set to `true`
1367
- *
1368
- * The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
1369
- * 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.
1370
- * 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.
1371
- *
1372
- * Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
1373
- * 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.
1374
- */
1375
- updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
1376
- const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
1377
- this._sourceChangeDataState !== "has-changes";
1378
- if (shouldSkipSyncVersionUpdate)
1379
- return;
1380
- nodeAssert(this._targetScopeProvenanceProps);
1381
- const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
1382
- const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
1383
- if (this.isReverseSynchronization) {
1384
- const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
1385
- core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
1386
- this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
1387
- sourceVersion;
1388
- }
1389
- else {
1390
- core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
1391
- this._targetScopeProvenanceProps.version = sourceVersion;
1392
- // save reverse sync version
1393
- if (initializeReverseSyncVersion) {
1394
- core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
1395
- this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
1396
- targetVersion;
1397
- }
1398
- }
1399
- if (this._options.argsForProcessChanges ||
1400
- (this._startingChangesetIndices && initializeReverseSyncVersion)) {
1401
- nodeAssert(this.targetDb.changeset.index !== undefined &&
1402
- this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
1403
- const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
1404
- core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
1405
- core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
1406
- const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
1407
- const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
1408
- // Determine which keys to clear and update based on the synchronization direction
1409
- let syncChangesetsToClearKey;
1410
- let syncChangesetsToUpdateKey;
1411
- if (this.isReverseSynchronization) {
1412
- syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
1413
- syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
1414
- }
1415
- else {
1416
- syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
1417
- syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
1418
- }
1419
- // NOTE that as documented in [[processChanges]], this assumes that right after
1420
- // transformation finalization, the work will be saved immediately, otherwise we've
1421
- // just marked this changeset as a synchronization to ignore, and the user can add other
1422
- // stuff to it which would break future synchronizations
1423
- for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
1424
- jsonProps[syncChangesetsToUpdateKey].push(i);
1425
- // Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
1426
- jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
1427
- return csIndex > this._startingChangesetIndices.source;
1428
- });
1429
- // if reverse sync then we may have received provenance changes which should be marked as sync changes
1430
- if (this.isReverseSynchronization) {
1431
- nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
1432
- for (let i = this._startingChangesetIndices.source + 1; i <= this.sourceDb.changeset.index + 1; i++)
1433
- jsonProps.pendingReverseSyncChangesetIndices.push(i);
1434
- }
1435
- core_bentley_1.Logger.logTrace(loggerCategory, `new pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
1436
- core_bentley_1.Logger.logTrace(loggerCategory, `new pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
1437
- }
1438
- this.provenanceDb.elements.updateAspect({
1439
- ...this._targetScopeProvenanceProps,
1440
- jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
1441
- });
1442
- this.clearCachedSynchronizationVersion();
1443
- }
1444
880
  // FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
1445
- finalizeTransformation() {
881
+ async finalizeTransformation() {
1446
882
  this.importer.finalize();
1447
- this.updateSynchronizationVersion({
883
+ await this.updateSynchronizationVersion({
1448
884
  initializeReverseSyncVersion: this._isProvenanceInitTransform,
1449
885
  });
1450
886
  // TODO: ignore if we remove change cache usage
@@ -1471,38 +907,39 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1471
907
  /** Override of [IModelExportHandler.shouldExportRelationship]($transformer) that is called to determine if a [Relationship]($backend) should be exported.
1472
908
  * @note Reaching this point means that the relationship has passed the standard exclusion checks in [IModelExporter]($transformer).
1473
909
  */
1474
- shouldExportRelationship(_sourceRelationship) {
910
+ async shouldExportRelationship(_sourceRelationship) {
1475
911
  return true;
1476
912
  }
1477
913
  /** Override of [IModelExportHandler.onExportRelationship]($transformer) that imports a relationship into the target iModel when it is exported from the source iModel.
1478
914
  * This override calls [[onTransformRelationship]] and then [IModelImporter.importRelationship]($transformer) to update the target iModel.
1479
915
  */
1480
- onExportRelationship(sourceRelationship) {
1481
- const sourceFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.sourceId);
1482
- const targetFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.targetId);
916
+ async onExportRelationship(sourceRelationship) {
917
+ const sourceFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.sourceId);
918
+ const targetFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.targetId);
1483
919
  const targetRelationshipProps = this.onTransformRelationship(sourceRelationship);
1484
- const targetRelationshipInstanceId = this.importer.importRelationship(targetRelationshipProps);
920
+ const targetRelationshipInstanceId = await this.importer.importRelationship(targetRelationshipProps);
921
+ const provenanceDb = await this.getProvenanceDb();
1485
922
  if (!this._options.noProvenance &&
1486
923
  core_bentley_1.Id64.isValid(targetRelationshipInstanceId)) {
1487
- let provenance = !this._options.forceExternalSourceAspectProvenance
1488
- ? sourceFedGuid && targetFedGuid && `${sourceFedGuid}/${targetFedGuid}`
1489
- : undefined;
1490
- if (!provenance) {
1491
- const aspectProps = this.initRelationshipProvenance(sourceRelationship, targetRelationshipInstanceId);
1492
- const foundEsaProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, aspectProps);
924
+ const needsEsaProvenance = !this._options
925
+ .forceExternalSourceAspectProvenance
926
+ ? !(sourceFedGuid && targetFedGuid)
927
+ : true;
928
+ if (needsEsaProvenance) {
929
+ const aspectProps = await this._provenanceManager.initRelationshipProvenance(sourceRelationship.id, targetRelationshipInstanceId, this._forceOldRelationshipProvenanceMethod);
930
+ const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
1493
931
  // onExportRelationship doesn't need to call updateAspect if esaProps were found, because relationship provenance doesn't have the same concept of a version as element provenance (which uses last mod time on the elements).
1494
932
  if (undefined === foundEsaProps) {
1495
- aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
933
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
934
+ aspectProps.id = provenanceDb.elements.insertAspect(aspectProps);
1496
935
  }
1497
- provenance = aspectProps;
1498
936
  }
1499
- this.markLastProvenance(provenance, { isRelationship: true });
1500
937
  }
1501
938
  }
1502
939
  /** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
1503
940
  * This override propagates the delete to the target iModel via [IModelImporter.deleteRelationship]($transformer).
1504
941
  */
1505
- onDeleteRelationship(sourceRelInstanceId) {
942
+ async onDeleteRelationship(sourceRelInstanceId) {
1506
943
  nodeAssert(this._deletedSourceRelationshipData, "should be defined at initialization by now");
1507
944
  const deletedRelData = this._deletedSourceRelationshipData.get(sourceRelInstanceId);
1508
945
  if (!deletedRelData) {
@@ -1516,14 +953,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1516
953
  targetId: deletedRelData.targetIdInTarget,
1517
954
  })?.id;
1518
955
  if (id) {
1519
- this.importer.deleteRelationship({
956
+ await this.importer.deleteRelationship({
1520
957
  id,
1521
958
  classFullName: deletedRelData.classFullName,
1522
959
  });
1523
960
  }
1524
961
  if (deletedRelData.provenanceAspectId) {
1525
962
  try {
1526
- this.provenanceDb.elements.deleteAspect(deletedRelData.provenanceAspectId);
963
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
964
+ (await this.getProvenanceDb()).elements.deleteAspect(deletedRelData.provenanceAspectId);
1527
965
  }
1528
966
  catch (error) {
1529
967
  // This aspect may no longer exist if it was deleted at some other point during the transformation. This is fine.
@@ -1534,51 +972,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1534
972
  }
1535
973
  }
1536
974
  _yieldManager = new core_bentley_1.YieldManager();
1537
- /** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
1538
- * @deprecated in 1.x. Don't use this anymore
1539
- * @see [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided.
1540
- * @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.
1541
- * @throws [[IModelError]] If the required provenance information is not available to detect deletes.
1542
- */
1543
- async detectRelationshipDeletes() {
1544
- if (this.isReverseSynchronization) {
1545
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
1546
- }
1547
- const aspectDeleteIds = [];
1548
- const sql = `
1549
- SELECT ECInstanceId, Identifier, JsonProperties
1550
- FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect
1551
- WHERE aspect.Scope.Id=:scopeId
1552
- AND aspect.Kind=:kind
1553
- `;
1554
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1555
- await this.targetDb.withPreparedStatement(sql,
1556
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
1557
- async (statement) => {
1558
- statement.bindId("scopeId", this.targetScopeElementId);
1559
- statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
1560
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1561
- const sourceRelInstanceId = core_bentley_1.Id64.fromJSON(statement.getValue(1).getString());
1562
- if (undefined ===
1563
- this.sourceDb.relationships.tryGetInstanceProps(core_backend_1.ElementRefersToElements.classFullName, sourceRelInstanceId)) {
1564
- // this function exists only to support some in-imodel transformations, which must
1565
- // use the old (external source aspect) provenance method anyway so we don't need to support
1566
- // new provenance
1567
- const json = JSON.parse(statement.getValue(2).getString());
1568
- const targetRelInstanceId = json.targetRelInstanceId ?? json.provenanceRelInstanceId;
1569
- if (targetRelInstanceId) {
1570
- this.importer.deleteRelationship({
1571
- id: targetRelInstanceId,
1572
- classFullName: core_backend_1.ElementRefersToElements.classFullName,
1573
- });
1574
- }
1575
- aspectDeleteIds.push(statement.getValue(0).getId());
1576
- }
1577
- await this._yieldManager.allowYield();
1578
- }
1579
- });
1580
- this.targetDb.elements.deleteAspect(aspectDeleteIds);
1581
- }
1582
975
  /** Transform the specified sourceRelationship into RelationshipProps for the target iModel.
1583
976
  * @param sourceRelationship The Relationship from the source iModel to be transformed.
1584
977
  * @returns RelationshipProps for the target iModel.
@@ -1589,16 +982,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1589
982
  targetRelationshipProps.sourceId = this.context.findTargetElementId(sourceRelationship.sourceId);
1590
983
  targetRelationshipProps.targetId = this.context.findTargetElementId(sourceRelationship.targetId);
1591
984
  // TODO: move to cloneRelationship in IModelCloneContext
1592
- sourceRelationship.forEachProperty((propertyName, propertyMetaData) => {
1593
- if (core_common_1.PrimitiveTypeCode.Long === propertyMetaData.primitiveType &&
1594
- "Id" === propertyMetaData.extendedType) {
1595
- targetRelationshipProps[propertyName] =
1596
- this.context.findTargetElementId(sourceRelationship.asAny[propertyName]);
985
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
986
+ sourceRelationship.forEach((propertyName, property) => {
987
+ if (property.isPrimitive() && "Id" === property.extendedTypeName) {
988
+ targetRelationshipProps[core_common_1.ECJsNames.toJsName(propertyName)] =
989
+ this.context.findTargetElementId(sourceRelationship.asAny[core_common_1.ECJsNames.toJsName(propertyName)]);
1597
990
  }
1598
991
  });
1599
992
  return targetRelationshipProps;
1600
993
  }
1601
- shouldExportElementAspect(aspect) {
994
+ async shouldExportElementAspect(aspect) {
1602
995
  // This override is needed to ensure that aspects are not exported if their element is not exported.
1603
996
  // This is needed in case DetachedExportElementAspectsStrategy is used.
1604
997
  return this.context.findTargetElementId(aspect.element.id) !== core_bentley_1.Id64.invalid;
@@ -1606,28 +999,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1606
999
  /** Override of [IModelExportHandler.onExportElementUniqueAspect]($transformer) that imports an ElementUniqueAspect into the target iModel when it is exported from the source iModel.
1607
1000
  * This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
1608
1001
  */
1609
- onExportElementUniqueAspect(sourceAspect) {
1610
- const targetAspectProps = this.onTransformElementAspect(sourceAspect);
1611
- if (!this.doAllReferencesExistInTarget(sourceAspect)) {
1002
+ async onExportElementUniqueAspect(sourceAspect) {
1003
+ const targetAspectProps = await this.onTransformElementAspect(sourceAspect);
1004
+ if (!(await this.doAllReferencesExistInTarget(sourceAspect))) {
1612
1005
  this._partiallyCommittedAspectIds.add(sourceAspect.id);
1613
1006
  }
1614
- const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
1007
+ const targetId = await this.importer.importElementUniqueAspect(targetAspectProps);
1615
1008
  this.context.remapElementAspect(sourceAspect.id, targetId);
1616
1009
  }
1617
1010
  /** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
1618
1011
  * This override calls [[onTransformElementAspect]] for each ElementMultiAspect and then [IModelImporter.importElementMultiAspects]($transformer) to update the target iModel.
1619
1012
  * @note ElementMultiAspects are handled as a group to make it easier to differentiate between insert, update, and delete.
1620
1013
  */
1621
- onExportElementMultiAspects(sourceAspects) {
1014
+ async onExportElementMultiAspects(sourceAspects) {
1622
1015
  // Transform source ElementMultiAspects into target ElementAspectProps
1623
- const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA));
1624
- sourceAspects.forEach((a) => {
1625
- if (!this.doAllReferencesExistInTarget(a)) {
1016
+ const targetAspectPropsArray = sourceAspects.map(async (srcA) => this.onTransformElementAspect(srcA));
1017
+ for (const a of sourceAspects) {
1018
+ if (!(await this.doAllReferencesExistInTarget(a))) {
1626
1019
  this._partiallyCommittedAspectIds.add(a.id);
1627
1020
  }
1628
- });
1021
+ }
1629
1022
  // const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
1630
- const targetIds = this.importer.importElementMultiAspects(targetAspectPropsArray, (a) => {
1023
+ const targetIds = await this.importer.importElementMultiAspects(await Promise.all(targetAspectPropsArray), (a) => {
1631
1024
  const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
1632
1025
  a.scope?.id === this.targetScopeElementId;
1633
1026
  return (!this._options.includeSourceProvenance ||
@@ -1642,8 +1035,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1642
1035
  * @returns ElementAspectProps for the target iModel.
1643
1036
  * @note A subclass can override this method to provide custom transform behavior.
1644
1037
  */
1645
- onTransformElementAspect(sourceElementAspect) {
1646
- const targetElementAspectProps = this.context.cloneElementAspect(sourceElementAspect);
1038
+ async onTransformElementAspect(sourceElementAspect) {
1039
+ const targetElementAspectProps = await this.context.cloneElementAspect(sourceElementAspect);
1647
1040
  return targetElementAspectProps;
1648
1041
  }
1649
1042
  /** The directory where schemas will be exported, a random temporary directory */
@@ -1651,7 +1044,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1651
1044
  /** Override of [IModelExportHandler.shouldExportSchema]($transformer) that is called to determine if a schema should be exported
1652
1045
  * @note the default behavior doesn't import schemas older than those already in the target
1653
1046
  */
1654
- shouldExportSchema(schemaKey) {
1047
+ async shouldExportSchema(schemaKey) {
1655
1048
  const versionInTarget = this.targetDb.querySchemaVersion(schemaKey.name);
1656
1049
  if (versionInTarget === undefined)
1657
1050
  return true;
@@ -1685,7 +1078,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1685
1078
  nodeAssert(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
1686
1079
  this._longNamedSchemasMap.set(schema.name, schemaFileName);
1687
1080
  }
1688
- /* eslint-disable-next-line deprecation/deprecation */
1689
1081
  this.sourceDb.exportSchema({
1690
1082
  schemaName: schema.name,
1691
1083
  outputDirectory: this._schemaExportDir,
@@ -1738,7 +1130,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1738
1130
  return this.exporter.exportFonts();
1739
1131
  }
1740
1132
  /** Override of [IModelExportHandler.onExportFont]($transformer) that imports a font into the target iModel when it is exported from the source iModel. */
1741
- onExportFont(font, _isUpdate) {
1133
+ async onExportFont(font, _isUpdate) {
1742
1134
  this.context.importFont(font.id);
1743
1135
  }
1744
1136
  /** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
@@ -1758,11 +1150,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1758
1150
  /** Override of [IModelExportHandler.shouldExportCodeSpec]($transformer) that is called to determine if a CodeSpec should be exported from the source iModel.
1759
1151
  * @note Reaching this point means that the CodeSpec has passed the standard exclusion checks in [IModelExporter]($transformer).
1760
1152
  */
1761
- shouldExportCodeSpec(_sourceCodeSpec) {
1153
+ async shouldExportCodeSpec(_sourceCodeSpec) {
1762
1154
  return true;
1763
1155
  }
1764
1156
  /** Override of [IModelExportHandler.onExportCodeSpec]($transformer) that imports a CodeSpec into the target iModel when it is exported from the source iModel. */
1765
- onExportCodeSpec(sourceCodeSpec) {
1157
+ async onExportCodeSpec(sourceCodeSpec) {
1766
1158
  this.context.importCodeSpec(sourceCodeSpec.id);
1767
1159
  }
1768
1160
  /** Recursively import all Elements and sub-Models that descend from the specified Subject */
@@ -1773,8 +1165,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1773
1165
  this.context.remapElement(sourceSubjectId, targetSubjectId);
1774
1166
  await this.processChildElements(sourceSubjectId);
1775
1167
  await this.processSubjectSubModels(sourceSubjectId);
1776
- this.completePartiallyCommittedElements();
1777
- this.completePartiallyCommittedAspects();
1168
+ await this.completePartiallyCommittedElements();
1169
+ await this.completePartiallyCommittedAspects();
1778
1170
  }
1779
1171
  /** state to prevent reinitialization, @see [[initialize]] */
1780
1172
  _initialized = false;
@@ -1790,11 +1182,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1790
1182
  async initialize() {
1791
1183
  if (this._initialized)
1792
1184
  return;
1793
- this.initScopeProvenance();
1185
+ await this.initScopeProvenance();
1794
1186
  await this._tryInitChangesetData(this._options.argsForProcessChanges);
1795
1187
  await this.context.initialize();
1796
1188
  // need exporter initialized to do remapdeletedsourceentities.
1797
- await this.exporter.initialize(this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
1189
+ await this.exporter.initialize(await this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
1798
1190
  // 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).
1799
1191
  await this.processChangesets();
1800
1192
  this._initialized = true;
@@ -1806,7 +1198,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1806
1198
  * @returns void
1807
1199
  */
1808
1200
  async processChangesets() {
1809
- this.forEachTrackedElement((sourceElementId, targetElementId) => {
1201
+ await this._provenanceManager.forEachTrackedElement((sourceElementId, targetElementId) => {
1810
1202
  this.context.remapElement(sourceElementId, targetElementId);
1811
1203
  });
1812
1204
  if (this.exporter.sourceDbChanges)
@@ -1820,11 +1212,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1820
1212
  this._sourceChangeDataState = "has-changes";
1821
1213
  }
1822
1214
  const relationshipECClassIdsToSkip = new Set();
1823
- for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)")) {
1215
+ for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)", undefined, { usePrimaryConn: true })) {
1824
1216
  relationshipECClassIdsToSkip.add(row.ECInstanceId);
1825
1217
  }
1826
1218
  const relationshipECClassIds = new Set();
1827
- for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)")) {
1219
+ for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)", undefined, { usePrimaryConn: true })) {
1828
1220
  relationshipECClassIds.add(row.ECInstanceId);
1829
1221
  }
1830
1222
  // For later use when processing deletes.
@@ -1847,13 +1239,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1847
1239
  db: this.sourceDb,
1848
1240
  disableSchemaCheck: true,
1849
1241
  });
1242
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1850
1243
  const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
1851
- const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier();
1244
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1245
+ const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier(this.sourceDb);
1852
1246
  while (csAdaptor.step()) {
1853
1247
  ecChangeUnifier.appendFrom(csAdaptor);
1854
1248
  }
1249
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1855
1250
  const changes = [...ecChangeUnifier.instances];
1856
1251
  /** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
1252
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1857
1253
  const elemIdToScopeEsa = new Map();
1858
1254
  for (const change of changes) {
1859
1255
  if (change.ECClassId !== undefined &&
@@ -1903,10 +1299,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1903
1299
  * @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
1904
1300
  * @returns void
1905
1301
  */
1906
- async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
1302
+ async processDeletedOp(
1303
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1304
+ change,
1305
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1306
+ mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
1907
1307
  // we need a connected iModel with changes to remap elements with deletions
1908
1308
  const notConnectedModel = this.sourceDb.iTwinId === undefined;
1909
- const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index &&
1309
+ const noChanges = (await this.getSynchronizationVersion()).index ===
1310
+ this.sourceDb.changeset.index &&
1910
1311
  (this.exporter.sourceDbChanges === undefined ||
1911
1312
  !this.exporter.sourceDbChanges.hasChanges);
1912
1313
  if (notConnectedModel || noChanges)
@@ -1915,7 +1316,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1915
1316
  * if our ChangedECInstance is in the provenanceDb, then we can use the ids we find in the ChangedECInstance to query for ESAs.
1916
1317
  * This is because the ESAs are stored on an element Id thats present in the provenanceDb.
1917
1318
  */
1918
- const changeDataInProvenanceDb = this.sourceDb === this.provenanceDb;
1319
+ const changeDataInProvenanceDb = this.sourceDb === (await this.getProvenanceDb());
1919
1320
  const getTargetIdFromSourceId = async (id) => {
1920
1321
  let identifierValue;
1921
1322
  let element;
@@ -1931,7 +1332,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1931
1332
  this.targetScopeElementId,
1932
1333
  core_backend_1.ExternalSourceAspect.Kind.Element,
1933
1334
  id,
1934
- ]))) {
1335
+ ]), { usePrimaryConn: true })) {
1935
1336
  identifierValue = row.Identifier;
1936
1337
  }
1937
1338
  identifierValue =
@@ -1944,7 +1345,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1944
1345
  }
1945
1346
  // Check for targetId using sourceId's fedguid if we didn't find an esa.
1946
1347
  if (fedGuid) {
1947
- const targetId = this._queryElemIdByFedGuid(this.targetDb, fedGuid);
1348
+ const targetId = this.targetDb.elements.getIdFromFederationGuid(fedGuid);
1948
1349
  return targetId;
1949
1350
  }
1950
1351
  return undefined;
@@ -1957,20 +1358,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1957
1358
  const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
1958
1359
  const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
1959
1360
  if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
1960
- this._deletedSourceRelationshipData.set(changedInstanceId, {
1361
+ this._deletedSourceRelationshipData?.set(changedInstanceId, {
1961
1362
  classFullName: classFullName ?? "",
1962
1363
  sourceIdInTarget: sourceIdOfRelationshipInTarget,
1963
1364
  targetIdInTarget: targetIdOfRelationshipInTarget,
1964
1365
  });
1965
1366
  }
1966
- else if (this.sourceDb === this.provenanceSourceDb) {
1967
- const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
1367
+ else if (this.sourceDb ===
1368
+ (await this._provenanceManager.getProvenanceSourceDb())) {
1369
+ const relProvenance = await this._provenanceManager.queryProvenanceForRelationship(changedInstanceId, {
1968
1370
  classFullName: classFullName ?? "",
1969
1371
  sourceId: sourceIdOfRelationshipInSource,
1970
1372
  targetId: targetIdOfRelationshipInSource,
1971
1373
  });
1972
1374
  if (relProvenance && relProvenance.relationshipId)
1973
- this._deletedSourceRelationshipData.set(changedInstanceId, {
1375
+ this._deletedSourceRelationshipData?.set(changedInstanceId, {
1974
1376
  classFullName: classFullName ?? "",
1975
1377
  relId: relProvenance.relationshipId,
1976
1378
  provenanceAspectId: relProvenance.aspectId,
@@ -1979,14 +1381,20 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1979
1381
  }
1980
1382
  else {
1981
1383
  let targetId = await getTargetIdFromSourceId(changedInstanceId);
1982
- if (targetId === undefined && this.sourceDb === this.provenanceSourceDb) {
1983
- targetId = this._queryProvenanceForElement(changedInstanceId);
1384
+ if (targetId === undefined &&
1385
+ this.sourceDb ===
1386
+ (await this._provenanceManager.getProvenanceSourceDb())) {
1387
+ targetId =
1388
+ await this._provenanceManager.queryProvenanceForElement(changedInstanceId);
1984
1389
  }
1985
1390
  // since we are processing one changeset at a time, we can see local source deletes
1986
1391
  // of entities that were never synced and can be safely ignored
1987
1392
  const deletionNotInTarget = !targetId;
1988
1393
  if (deletionNotInTarget)
1989
1394
  return;
1395
+ if (targetId === undefined) {
1396
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "targetId should be acquired from source id or element provenance");
1397
+ }
1990
1398
  this.context.remapElement(changedInstanceId, targetId);
1991
1399
  // If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
1992
1400
  // In such case an entity update will be triggered and we no longer need to delete the entity.
@@ -2005,7 +1413,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2005
1413
  this._sourceChangeDataState = "unconnected";
2006
1414
  return;
2007
1415
  }
2008
- const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
1416
+ const syncVersion = await this.getSynchronizationVersion();
1417
+ const noChanges = syncVersion.index === this.sourceDb.changeset.index;
2009
1418
  if (noChanges) {
2010
1419
  this._sourceChangeDataState = "no-changes";
2011
1420
  this._csFileProps = [];
@@ -2014,38 +1423,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2014
1423
  const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
2015
1424
  // NOTE: that we do NOT download the changesummary for the last transformed version, we want
2016
1425
  // to ignore those already processed changes
2017
- const startChangesetIndexOrId = startChangeset?.index ??
2018
- startChangeset?.id ??
2019
- this.synchronizationVersion.index + 1;
1426
+ const startChangesetIndexOrId = startChangeset?.index ?? startChangeset?.id ?? syncVersion.index + 1;
2020
1427
  const endChangesetId = this.sourceDb.changeset.id;
2021
1428
  const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
2022
1429
  ? indexOrId
2023
- : core_backend_1.BriefcaseManager
2024
- .queryChangeset({
1430
+ : core_backend_1.BriefcaseManager.queryChangeset({
2025
1431
  iModelId: this.sourceDb.iModelId,
2026
- // eslint-disable-next-line deprecation/deprecation
2027
1432
  changeset: { id: indexOrId },
2028
- })
2029
- .then((changeset) => changeset.index)));
2030
- const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
1433
+ }).then((changeset) => changeset.index)));
1434
+ const missingChangesets = startChangesetIndex > syncVersion.index + 1;
2031
1435
  if (!this._options.argsForProcessChanges
2032
1436
  ?.ignoreMissingChangesetsInSynchronizations &&
2033
- startChangesetIndex !== this.synchronizationVersion.index + 1 &&
2034
- this.synchronizationVersion.index !== -1) {
1437
+ startChangesetIndex !== syncVersion.index + 1 &&
1438
+ syncVersion.index !== -1) {
2035
1439
  throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
2036
1440
  " startChangesetId should be" +
2037
1441
  " exactly the first changeset *after* the previous synchronization to not miss data." +
2038
1442
  ` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
2039
- ` but the previous synchronization for this targetScopeElement was '${this.synchronizationVersion.id}'` +
2040
- ` which is changeset #${this.synchronizationVersion.index}. The transformer expected` +
2041
- ` #${this.synchronizationVersion.index + 1}.`);
2042
- }
2043
- nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps should be set by now");
2044
- const changesetsToSkip = this.isReverseSynchronization
2045
- ? this._targetScopeProvenanceProps.jsonProperties
2046
- .pendingReverseSyncChangesetIndices
2047
- : this._targetScopeProvenanceProps.jsonProperties
2048
- .pendingSyncChangesetIndices;
1443
+ ` but the previous synchronization for this targetScopeElem ${syncVersion.id}'` +
1444
+ ` which is changeset #${syncVersion.index}. The transformer expected` +
1445
+ ` #${syncVersion.index + 1}.`);
1446
+ }
1447
+ const changesetsToSkip = await this._provenanceManager.getChangesetsToSkip();
2049
1448
  core_bentley_1.Logger.logTrace(loggerCategory, `changesets to skip: ${changesetsToSkip}`);
2050
1449
  this._changesetRanges = (0, Algo_1.rangesFromRangeAndSkipped)(startChangesetIndex, endChangesetIndex, changesetsToSkip);
2051
1450
  core_bentley_1.Logger.logTrace(loggerCategory, `ranges: ${this._changesetRanges}`);
@@ -2098,6 +1497,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2098
1497
  * @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
2099
1498
  */
2100
1499
  async processAll() {
1500
+ this._targetElementIdsRemappedByCode.clear();
1501
+ this._targetModelsImportedInCurrentTransform.clear();
1502
+ // processAll always has changes to process, so mark it as such for version tracking
1503
+ this._sourceChangeDataState = "has-changes";
2101
1504
  await this.exporter.exportCodeSpecs();
2102
1505
  await this.exporter.exportFonts();
2103
1506
  if (this._options.skipPropagateChangesToRootElements) {
@@ -2109,36 +1512,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2109
1512
  else {
2110
1513
  await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
2111
1514
  }
2112
- this.completePartiallyCommittedElements();
1515
+ await this.completePartiallyCommittedElements();
2113
1516
  await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
2114
- this.completePartiallyCommittedAspects();
1517
+ await this.completePartiallyCommittedAspects();
2115
1518
  await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
2116
1519
  if (this._options.forceExternalSourceAspectProvenance &&
2117
- this.shouldDetectDeletes()) {
2118
- // eslint-disable-next-line deprecation/deprecation
2119
- await this.detectElementDeletes();
2120
- // eslint-disable-next-line deprecation/deprecation
2121
- await this.detectRelationshipDeletes();
1520
+ (await this.shouldDetectDeletes())) {
1521
+ core_bentley_1.Logger.logWarning(loggerCategory, "This workflows was deprecated in v1 and is no longer supported");
2122
1522
  }
2123
1523
  if (this._options.optimizeGeometry)
2124
1524
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
2125
1525
  this.importer.computeProjectExtents();
2126
- this.finalizeTransformation();
2127
- }
2128
- /** previous provenance, either a federation guid, a `${sourceFedGuid}/${targetFedGuid}` pair, or required aspect props */
2129
- _lastProvenanceEntityInfo = nullLastProvenanceEntityInfo;
2130
- markLastProvenance(sourceAspect, { isRelationship = false }) {
2131
- this._lastProvenanceEntityInfo =
2132
- typeof sourceAspect === "string"
2133
- ? sourceAspect
2134
- : {
2135
- entityId: sourceAspect.element.id,
2136
- aspectId: sourceAspect.id,
2137
- aspectVersion: sourceAspect.version ?? "",
2138
- aspectKind: isRelationship
2139
- ? core_backend_1.ExternalSourceAspect.Kind.Relationship
2140
- : core_backend_1.ExternalSourceAspect.Kind.Element,
2141
- };
1526
+ await this.finalizeTransformation();
2142
1527
  }
2143
1528
  /** Export changes from the source iModel and import the transformed entities into the target iModel.
2144
1529
  * Inserts, updates, and deletes are determined by inspecting the changeset(s).
@@ -2150,15 +1535,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2150
1535
  * @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.
2151
1536
  */
2152
1537
  async processChanges(options) {
1538
+ this._targetElementIdsRemappedByCode.clear();
1539
+ this._targetModelsImportedInCurrentTransform.clear();
2153
1540
  // must wait for initialization of synchronization provenance data
2154
- await this.exporter.exportChanges(this.getExportInitOpts(options));
2155
- this.completePartiallyCommittedElements();
2156
- this.completePartiallyCommittedAspects();
1541
+ await this.exporter.exportChanges(await this.getExportInitOpts(options));
1542
+ await this.completePartiallyCommittedElements();
1543
+ await this.completePartiallyCommittedAspects();
2157
1544
  if (this._options.optimizeGeometry)
2158
1545
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
2159
1546
  this.importer.computeProjectExtents();
2160
- this.finalizeTransformation();
1547
+ await this.finalizeTransformation();
2161
1548
  const defaultSaveTargetChanges = () => {
1549
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
2162
1550
  this.targetDb.saveChanges();
2163
1551
  };
2164
1552
  await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
@@ -2166,7 +1554,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2166
1554
  /** Changeset data must be initialized in order to build correct changeOptions.
2167
1555
  * Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
2168
1556
  */
2169
- getExportInitOpts(opts) {
1557
+ async getExportInitOpts(opts) {
2170
1558
  if (!this._options.argsForProcessChanges)
2171
1559
  return {};
2172
1560
  const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
@@ -2180,7 +1568,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2180
1568
  ? { startChangeset }
2181
1569
  : {
2182
1570
  startChangeset: {
2183
- index: this.synchronizationVersion.index + 1,
1571
+ index: (await this.getSynchronizationVersion()).index + 1,
2184
1572
  },
2185
1573
  }),
2186
1574
  };
@@ -2261,11 +1649,11 @@ class TemplateModelCloner extends IModelTransformer {
2261
1649
  return this._sourceIdToTargetIdMap; // return the sourceElementId -> targetElementId Map in case further post-processing is required.
2262
1650
  }
2263
1651
  /** Cloning from a template requires this override of onTransformElement. */
2264
- onTransformElement(sourceElement) {
1652
+ async onTransformElement(sourceElement) {
2265
1653
  const referenceIds = sourceElement.getReferenceIds();
2266
- referenceIds.forEach((referenceId) => {
1654
+ for (const referenceId of referenceIds) {
2267
1655
  // TODO: consider going through all definition elements at once and remapping them to themselves
2268
- if (!core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
1656
+ if (!core_backend_1.EntityReferences.isValid(await this.context.findTargetEntityId(referenceId))) {
2269
1657
  if (this.context.isBetweenIModels) {
2270
1658
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, `Remapping for source dependency ${referenceId} not found for target iModel`);
2271
1659
  }
@@ -2280,8 +1668,8 @@ class TemplateModelCloner extends IModelTransformer {
2280
1668
  }
2281
1669
  }
2282
1670
  }
2283
- });
2284
- const targetElementProps = super.onTransformElement(sourceElement);
1671
+ }
1672
+ const targetElementProps = await super.onTransformElement(sourceElement);
2285
1673
  targetElementProps.federationGuid = core_bentley_1.Guid.createValue(); // clone from template should create a new federationGuid
2286
1674
  targetElementProps.code = core_common_1.Code.createEmpty(); // clone from template should not maintain codes
2287
1675
  if (sourceElement instanceof core_backend_1.GeometricElement) {
@@ -2294,23 +1682,9 @@ class TemplateModelCloner extends IModelTransformer {
2294
1682
  targetElementProps.placement = placement;
2295
1683
  }
2296
1684
  }
2297
- this._sourceIdToTargetIdMap.set(sourceElement.id, core_bentley_1.Id64.invalid); // keep track of (source) elementIds from the template model, but the target hasn't been inserted yet
1685
+ this._sourceIdToTargetIdMap?.set(sourceElement.id, core_bentley_1.Id64.invalid); // keep track of (source) elementIds from the template model, but the target hasn't been inserted yet
2298
1686
  return targetElementProps;
2299
1687
  }
2300
1688
  }
2301
1689
  exports.TemplateModelCloner = TemplateModelCloner;
2302
- function queryElemFedGuid(db, elemId) {
2303
- // eslint-disable-next-line @itwin/no-internal, deprecation/deprecation
2304
- return db.withPreparedStatement(`
2305
- SELECT FederationGuid
2306
- FROM bis.Element
2307
- WHERE ECInstanceId=?
2308
- `, (stmt) => {
2309
- stmt.bindId(1, elemId);
2310
- (0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW);
2311
- const result = stmt.getValue(0).getGuid();
2312
- (0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_DONE);
2313
- return result;
2314
- });
2315
- }
2316
1690
  //# sourceMappingURL=IModelTransformer.js.map