@itwin/imodel-transformer 2.0.0-dev.4 → 2.0.0-dev.40

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 (88) hide show
  1. package/README.md +12 -26
  2. package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
  3. package/lib/cjs/BranchProvenanceInitializer.js +35 -30
  4. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  5. package/lib/cjs/ChangesetScanner.d.ts +60 -0
  6. package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
  7. package/lib/cjs/ChangesetScanner.js +101 -0
  8. package/lib/cjs/ChangesetScanner.js.map +1 -0
  9. package/lib/cjs/ECReferenceTypesCache.d.ts +0 -3
  10. package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
  11. package/lib/cjs/ECReferenceTypesCache.js +61 -103
  12. package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
  13. package/lib/cjs/ElementAspectCleanup.d.ts +16 -0
  14. package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
  15. package/lib/cjs/ElementAspectCleanup.js +83 -0
  16. package/lib/cjs/ElementAspectCleanup.js.map +1 -0
  17. package/lib/cjs/ElementAspectExportCoordinator.d.ts +44 -0
  18. package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
  19. package/lib/cjs/ElementAspectExportCoordinator.js +148 -0
  20. package/lib/cjs/ElementAspectExportCoordinator.js.map +1 -0
  21. package/lib/cjs/ElementAspectExportProcessor.d.ts +55 -0
  22. package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
  23. package/lib/cjs/ElementAspectExportProcessor.js +186 -0
  24. package/lib/cjs/ElementAspectExportProcessor.js.map +1 -0
  25. package/lib/cjs/ElementCascadingDeleter.d.ts +3 -3
  26. package/lib/cjs/ElementCascadingDeleter.d.ts.map +1 -1
  27. package/lib/cjs/ElementCascadingDeleter.js +12 -12
  28. package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
  29. package/lib/cjs/EntityUnifier.d.ts +4 -8
  30. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  31. package/lib/cjs/EntityUnifier.js +6 -24
  32. package/lib/cjs/EntityUnifier.js.map +1 -1
  33. package/lib/cjs/IModelCloneContext.d.ts +6 -5
  34. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  35. package/lib/cjs/IModelCloneContext.js +74 -57
  36. package/lib/cjs/IModelCloneContext.js.map +1 -1
  37. package/lib/cjs/IModelExporter.d.ts +80 -32
  38. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  39. package/lib/cjs/IModelExporter.js +563 -188
  40. package/lib/cjs/IModelExporter.js.map +1 -1
  41. package/lib/cjs/IModelImporter.d.ts +38 -24
  42. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  43. package/lib/cjs/IModelImporter.js +210 -129
  44. package/lib/cjs/IModelImporter.js.map +1 -1
  45. package/lib/cjs/IModelTransformContext.d.ts +54 -0
  46. package/lib/cjs/IModelTransformContext.d.ts.map +1 -0
  47. package/lib/cjs/IModelTransformContext.js +3 -0
  48. package/lib/cjs/IModelTransformContext.js.map +1 -0
  49. package/lib/cjs/IModelTransformer.d.ts +101 -189
  50. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  51. package/lib/cjs/IModelTransformer.js +558 -1104
  52. package/lib/cjs/IModelTransformer.js.map +1 -1
  53. package/lib/cjs/IModelTransformerError.d.ts +76 -0
  54. package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
  55. package/lib/cjs/IModelTransformerError.js +85 -0
  56. package/lib/cjs/IModelTransformerError.js.map +1 -0
  57. package/lib/cjs/ProvenanceManager.d.ts +170 -0
  58. package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
  59. package/lib/cjs/ProvenanceManager.js +735 -0
  60. package/lib/cjs/ProvenanceManager.js.map +1 -0
  61. package/lib/cjs/SyncTypeResolver.d.ts +34 -0
  62. package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
  63. package/lib/cjs/SyncTypeResolver.js +98 -0
  64. package/lib/cjs/SyncTypeResolver.js.map +1 -0
  65. package/lib/cjs/TransformerLoggerCategory.d.ts +1 -1
  66. package/lib/cjs/TransformerLoggerCategory.js +1 -1
  67. package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
  68. package/lib/cjs/TransformerPackageMetadata.d.ts +8 -0
  69. package/lib/cjs/TransformerPackageMetadata.d.ts.map +1 -0
  70. package/lib/cjs/TransformerPackageMetadata.js +14 -0
  71. package/lib/cjs/TransformerPackageMetadata.js.map +1 -0
  72. package/lib/cjs/imodel-transformer.d.ts +18 -0
  73. package/lib/cjs/imodel-transformer.d.ts.map +1 -1
  74. package/lib/cjs/imodel-transformer.js +29 -33
  75. package/lib/cjs/imodel-transformer.js.map +1 -1
  76. package/package.json +27 -39
  77. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +0 -16
  78. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +0 -1
  79. package/lib/cjs/DetachedExportElementAspectsStrategy.js +0 -106
  80. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +0 -1
  81. package/lib/cjs/ExportElementAspectsStrategy.d.ts +0 -35
  82. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +0 -1
  83. package/lib/cjs/ExportElementAspectsStrategy.js +0 -52
  84. package/lib/cjs/ExportElementAspectsStrategy.js.map +0 -1
  85. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +0 -12
  86. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +0 -1
  87. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +0 -47
  88. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +0 -1
@@ -8,10 +8,11 @@ exports.TemplateModelCloner = exports.IModelTransformer = void 0;
8
8
  /** @packageDocumentation
9
9
  * @module iModels
10
10
  */
11
- const path = require("path");
11
+ const path = require("node:path");
12
12
  const Semver = require("semver");
13
- const nodeAssert = require("assert");
13
+ const node_assert_1 = require("node:assert");
14
14
  const core_bentley_1 = require("@itwin/core-bentley");
15
+ const ECSchemaMetaData = require("@itwin/ecschema-metadata");
15
16
  const core_geometry_1 = require("@itwin/core-geometry");
16
17
  const core_backend_1 = require("@itwin/core-backend");
17
18
  const core_common_1 = require("@itwin/core-common");
@@ -21,13 +22,11 @@ const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
21
22
  const IModelCloneContext_1 = require("./IModelCloneContext");
22
23
  const EntityUnifier_1 = require("./EntityUnifier");
23
24
  const Algo_1 = require("./Algo");
25
+ const SyncTypeResolver_1 = require("./SyncTypeResolver");
26
+ const ProvenanceManager_1 = require("./ProvenanceManager");
27
+ const ChangesetScanner_1 = require("./ChangesetScanner");
28
+ const IModelTransformerError_1 = require("./IModelTransformerError");
24
29
  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
30
  /**
32
31
  * Apply a function to each Id64 in a supported container type of Id64s.
33
32
  * Currently only supports raw Id64String or RelatedElement-like objects containing an `id` property that is a Id64String,
@@ -53,10 +52,16 @@ function mapId64(idContainer, func) {
53
52
  results.push(func(idContainer.id));
54
53
  }
55
54
  else {
56
- throw Error([
57
- `Id64 container '${JSON.stringify(idContainer)}' is unsupported.`,
58
- "Currently only singular Id64 strings or prop-like objects containing an 'id' property are supported.",
59
- ].join("\n"));
55
+ core_bentley_1.ITwinError.throwError({
56
+ iTwinErrorId: {
57
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
58
+ key: IModelTransformerError_1.IModelTransformerError.InvalidEntityReference,
59
+ },
60
+ message: [
61
+ `Id64 container '${JSON.stringify(idContainer)}' is unsupported.`,
62
+ "Currently only singular Id64 strings or prop-like objects containing an 'id' property are supported.",
63
+ ].join("\n"),
64
+ });
60
65
  }
61
66
  return results;
62
67
  }
@@ -75,8 +80,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
75
80
  sourceDb;
76
81
  /** The read/write target iModel. */
77
82
  targetDb;
78
- /** The IModelTransformContext for this IModelTransformer. */
79
- context;
83
+ /** The supported transformation context for this transformer. */
84
+ get context() {
85
+ return this._cloneContext;
86
+ }
87
+ /** The clone context used by the transformer implementation. */
88
+ _cloneContext;
80
89
  /** The transform to be applied to the placement of spatial elements
81
90
  * This transform should be applied when:
82
91
  * - source and target db have different ECEF locations
@@ -86,7 +95,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
86
95
  * @beta
87
96
  */
88
97
  _linearSpatialTransform;
89
- _syncType;
98
+ /** The [[EditTxn]] used for write operations on the target iModel.
99
+ * The caller is responsible for starting, committing or abandoning this transaction.
100
+ * @beta
101
+ */
102
+ _targetEditTxn;
103
+ /** The optional [[EditTxn]] used for write operations on the source iModel during reverse synchronization.
104
+ * @beta
105
+ */
106
+ _sourceEditTxn;
107
+ _syncTypeResolver;
108
+ _provenanceManager;
90
109
  /** The Id of the Element in the **target** iModel that represents the **source** repository as a whole and scopes its [ExternalSourceAspect]($backend) instances. */
91
110
  get targetScopeElementId() {
92
111
  return this._options.targetScopeElementId;
@@ -95,118 +114,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
95
114
  _elementsWithExplicitlyTrackedProvenance = new Set();
96
115
  _partiallyCommittedElementIds = new Set();
97
116
  _partiallyCommittedAspectIds = new Set();
98
- /** the options that were used to initialize this transformer */
99
- _options;
100
- /**
101
- * 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.
102
- * 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.
103
- * 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
104
- * this private property on the IModelTransformer exists.
105
- */
106
- _allowNoScopingESA = false;
107
- 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.";
108
117
  /**
109
- * Queries for an esa which matches the props in the provided aspectProps.
110
- * @param dbToQuery db to run the query on for scope external source
111
- * @param aspectProps aspectProps to search for @see ExternalSourceAspectProps
118
+ * Tracks target element IDs that were remapped by Code during the current
119
+ * transformation pass. Used to prevent deletion of target elements that have been remapped
120
+ * to a new source element in the same pass (e.g., when a source element is deleted and a
121
+ * new one with the same properties is added, causing a remap to the same target).
112
122
  */
113
- static queryScopeExternalSourceAspect(dbToQuery, aspectProps) {
114
- const sql = `
115
- SELECT ECInstanceId, Version, JsonProperties
116
- FROM ${core_backend_1.ExternalSourceAspect.classFullName}
117
- WHERE Element.Id=:elementId
118
- AND Scope.Id=:scopeId
119
- AND Kind=:kind
120
- AND Identifier=:identifier
121
- LIMIT 1
122
- `;
123
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
124
- return dbToQuery.withPreparedStatement(sql, (statement) => {
125
- statement.bindId("elementId", aspectProps.element.id);
126
- if (aspectProps.scope === undefined)
127
- return undefined; // return instead of binding an invalid id
128
- statement.bindId("scopeId", aspectProps.scope.id);
129
- statement.bindString("kind", aspectProps.kind);
130
- statement.bindString("identifier", aspectProps.identifier);
131
- if (core_bentley_1.DbResult.BE_SQLITE_ROW !== statement.step())
132
- return undefined;
133
- const aspectId = statement.getValue(0).getId();
134
- const versionValue = statement.getValue(1);
135
- const version = versionValue.isNull
136
- ? undefined
137
- : versionValue.getString();
138
- const jsonPropsValue = statement.getValue(2);
139
- const jsonProperties = jsonPropsValue.isNull
140
- ? undefined
141
- : jsonPropsValue.getString();
142
- return { aspectId, version, jsonProperties };
143
- });
144
- }
123
+ _targetElementIdsRemappedByCode = new Set();
145
124
  /**
146
- * Determines the sync direction "forward" or "reverse" of a given sourceDb and targetDb by looking for the scoping ESA.
147
- * If the sourceDb's iModelId is found as the identifier of the expected scoping ESA in the targetDb, then it is a forward synchronization.
148
- * If the targetDb's iModelId is found as the identifier of the expected scoping ESA in the sourceDb, then it is a reverse synchronization.
149
- * @throws if no scoping ESA can be found in either the sourceDb or targetDb which describes a master branch relationship between the two databases.
150
- * @returns "forward" or "reverse"
125
+ * Tracks target model IDs that were imported (inserted or updated) during the current
126
+ * transformation pass. Used to prevent deletion of target models that have been recreated
127
+ * in the same pass (e.g. when a partition element is recreated with a new model).
151
128
  */
152
- static determineSyncType(sourceDb, targetDb,
153
- /** @see [[IModelTransformOptions.targetScopeElementId]] */
154
- targetScopeElementId) {
155
- const aspectProps = {
156
- id: undefined,
157
- version: undefined,
158
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
159
- element: {
160
- id: targetScopeElementId,
161
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
162
- },
163
- scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
164
- identifier: sourceDb.iModelId,
165
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
166
- jsonProperties: undefined,
167
- };
168
- /** First check if the targetDb is the branch (branch is the @see provenanceDb) */
169
- const esaPropsFromTargetDb = this.queryScopeExternalSourceAspect(targetDb, aspectProps);
170
- if (esaPropsFromTargetDb !== undefined) {
171
- return "forward"; // we found an esa assuming targetDb is the provenanceDb/branch so this is a forward sync.
172
- }
173
- // Now check if the sourceDb is the branch
174
- aspectProps.identifier = targetDb.iModelId;
175
- const esaPropsFromSourceDb = this.queryScopeExternalSourceAspect(sourceDb, aspectProps);
176
- if (esaPropsFromSourceDb !== undefined) {
177
- return "reverse"; // we found an esa assuming sourceDb is the provenanceDb/branch so this is a reverse sync.
178
- }
179
- throw new Error(this.noEsaSyncDirectionErrorMessage);
180
- }
181
- determineSyncType() {
182
- if (this._isProvenanceInitTransform) {
183
- return "forward";
184
- }
185
- if (!this._options.argsForProcessChanges) {
186
- return "not-sync";
187
- }
188
- try {
189
- return IModelTransformer.determineSyncType(this.sourceDb, this.targetDb, this.targetScopeElementId);
190
- }
191
- catch (err) {
192
- if (err instanceof Error &&
193
- err.message === IModelTransformer.noEsaSyncDirectionErrorMessage &&
194
- this._allowNoScopingESA) {
195
- return "forward";
196
- }
197
- throw err;
198
- }
199
- }
200
- get isReverseSynchronization() {
201
- if (this._syncType === undefined)
202
- this._syncType = this.determineSyncType();
203
- return this._syncType === "reverse";
204
- }
205
- get isForwardSynchronization() {
206
- if (this._syncType === undefined)
207
- this._syncType = this.determineSyncType();
208
- return this._syncType === "forward";
209
- }
129
+ _targetModelsImportedInCurrentTransform = new Set();
130
+ /** the options that were used to initialize this transformer */
131
+ _options;
210
132
  _changesetRanges = undefined;
211
133
  /**
212
134
  * Set if the transformer is being used to perform the provenance initialization step of a fork initialization.
@@ -215,31 +137,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
215
137
  _isProvenanceInitTransform;
216
138
  /** The element classes that are considered to define provenance in the iModel */
217
139
  static get provenanceElementClasses() {
218
- return [
219
- core_backend_1.FolderLink,
220
- core_backend_1.SynchronizationConfigLink,
221
- core_backend_1.ExternalSource,
222
- core_backend_1.ExternalSourceAttachment,
223
- ];
140
+ return ProvenanceManager_1.ProvenanceManager.provenanceElementClasses;
224
141
  }
225
142
  /** The element aspect classes that are considered to define provenance in the iModel */
226
143
  static get provenanceElementAspectClasses() {
227
- return [core_backend_1.ExternalSourceAspect];
144
+ return ProvenanceManager_1.ProvenanceManager.provenanceElementAspectClasses;
228
145
  }
229
146
  /** Construct a new IModelTransformer
230
- * @param source Specifies the source IModelExporter or the source IModelDb that will be used to construct the source IModelExporter.
231
- * @param target Specifies the target IModelImporter or the target IModelDb that will be used to construct the target IModelImporter.
147
+ * @param args The required arguments including source and target.
232
148
  * @param options The options that specify how the transformation should be done.
233
149
  */
234
- constructor(source, target, options) {
150
+ constructor(args, options) {
235
151
  super();
152
+ const { source, target } = args;
236
153
  // initialize IModelTransformOptions
237
154
  this._options = {
238
155
  ...options,
239
156
  // non-falsy defaults
240
157
  cloneUsingBinaryGeometry: options?.cloneUsingBinaryGeometry ?? true,
241
158
  targetScopeElementId: options?.targetScopeElementId ?? core_common_1.IModel.rootSubjectId,
242
- // eslint-disable-next-line @typescript-eslint/no-deprecated
243
159
  danglingReferencesBehavior: options?.danglingReferencesBehavior ?? "reject",
244
160
  branchRelationshipDataBehavior: options?.branchRelationshipDataBehavior ?? "reject",
245
161
  skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
@@ -271,7 +187,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
271
187
  this.exporter.excludeElementAspectClass(core_backend_1.ChannelRootAspect.classFullName); // Channel boundaries within the source iModel are not relevant to the target iModel
272
188
  this.exporter.excludeElementAspectClass("BisCore:TextAnnotationData"); // This ElementAspect is auto-created by the BisCore:TextAnnotation2d/3d element handlers
273
189
  // initialize importer and targetDb
274
- if (target instanceof core_backend_1.IModelDb) {
190
+ if (target instanceof core_backend_1.EditTxn) {
275
191
  this.importer = new IModelImporter_1.IModelImporter(target, {
276
192
  preserveElementIdsForFiltering: this._options.preserveElementIdsForFiltering,
277
193
  skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
@@ -282,16 +198,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
282
198
  this.validateSharedOptionsMatch();
283
199
  }
284
200
  this.targetDb = this.importer.targetDb;
201
+ this._targetEditTxn = this.importer.editTxn;
202
+ this._sourceEditTxn = options?.sourceEditTxn;
203
+ this.exporter.elementAspectExportCoordinator.setPreparation(async (excludedClasses, elementIds) => this.prepareElementAspects(excludedClasses, elementIds));
285
204
  // create the IModelCloneContext, it must be initialized later
286
- this.context = new IModelCloneContext_1.IModelCloneContext(this.sourceDb, this.targetDb);
287
- if (this.sourceDb.isBriefcase && this.targetDb.isBriefcase) {
288
- nodeAssert(this.sourceDb.changeset.index !== undefined &&
289
- this.targetDb.changeset.index !== undefined, "database has no changeset index");
290
- this._startingChangesetIndices = {
291
- target: this.targetDb.changeset.index,
292
- source: this.sourceDb.changeset.index,
293
- };
294
- }
205
+ this._cloneContext = new IModelCloneContext_1.IModelCloneContext(this.sourceDb, this.targetDb);
295
206
  // this internal is guaranteed stable for just transformer usage
296
207
  /* eslint-disable @itwin/no-internal */
297
208
  if (("codeValueBehavior" in this.sourceDb)) {
@@ -299,6 +210,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
299
210
  this.targetDb.codeValueBehavior = "exact";
300
211
  }
301
212
  /* eslint-enable @itwin/no-internal */
213
+ this._syncTypeResolver = new SyncTypeResolver_1.SyncTypeResolver(this.sourceDb, this.targetDb, this._options.targetScopeElementId, !!this._isProvenanceInitTransform, !!this._options.argsForProcessChanges);
214
+ this._provenanceManager = new ProvenanceManager_1.ProvenanceManager(this._options.targetScopeElementId, this._options, this._cloneContext, this._syncTypeResolver, this._targetEditTxn, this._sourceEditTxn);
302
215
  if (this._options.tryAlignGeolocation) {
303
216
  if (this.sourceDb.geographicCoordinateSystem ||
304
217
  this.targetDb.geographicCoordinateSystem) {
@@ -320,19 +233,29 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
320
233
  validateSharedOptionsMatch() {
321
234
  if (Boolean(this._options.preserveElementIdsForFiltering) !==
322
235
  this.importer.options.preserveElementIdsForFiltering) {
323
- const errMessage = "A custom importer was passed as a target but its 'preserveElementIdsForFiltering' option is out of sync with the transformer's option.";
324
- throw new Error(errMessage);
236
+ core_bentley_1.ITwinError.throwError({
237
+ iTwinErrorId: {
238
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
239
+ key: IModelTransformerError_1.IModelTransformerError.ImporterOptionMismatch,
240
+ },
241
+ message: "A custom importer was passed as a target but its 'preserveElementIdsForFiltering' option is out of sync with the transformer's option.",
242
+ });
325
243
  }
326
244
  if (Boolean(this._options.skipPropagateChangesToRootElements) !==
327
245
  this.importer.options.skipPropagateChangesToRootElements) {
328
- const errMessage = "A custom importer was passed as a target but its 'skipPropagateChangesToRootElements' option is out of sync with the transformer's option.";
329
- throw new Error(errMessage);
246
+ core_bentley_1.ITwinError.throwError({
247
+ iTwinErrorId: {
248
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
249
+ key: IModelTransformerError_1.IModelTransformerError.ImporterOptionMismatch,
250
+ },
251
+ message: "A custom importer was passed as a target but its 'skipPropagateChangesToRootElements' option is out of sync with the transformer's option.",
252
+ });
330
253
  }
331
254
  }
332
255
  /** Dispose any native resources associated with this IModelTransformer. */
333
256
  dispose() {
334
257
  core_bentley_1.Logger.logTrace(loggerCategory, "dispose()");
335
- this.context.dispose();
258
+ this._cloneContext[Symbol.dispose]();
336
259
  }
337
260
  /** Log current settings that affect IModelTransformer's behavior. */
338
261
  logSettings() {
@@ -349,74 +272,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
349
272
  core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
350
273
  core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
351
274
  }
352
- /** Return the IModelDb where IModelTransformer will store its provenance.
353
- * @note This will be [[targetDb]] except when it is a reverse synchronization. In that case it be [[sourceDb]].
354
- */
355
- get provenanceDb() {
356
- return this.isReverseSynchronization ? this.sourceDb : this.targetDb;
357
- }
358
- /** Return the IModelDb where IModelTransformer looks for entities referred to by stored provenance.
359
- * @note This will be [[sourceDb]] except when it is a reverse synchronization. In that case it be [[targetDb]].
360
- */
361
- get provenanceSourceDb() {
362
- return this.isReverseSynchronization ? this.targetDb : this.sourceDb;
363
- }
364
- /** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
365
- static initElementProvenanceOptions(sourceElementId, targetElementId, args) {
366
- const elementId = args.isReverseSynchronization
367
- ? sourceElementId
368
- : targetElementId;
369
- const version = args.isReverseSynchronization
370
- ? args.targetDb.elements.queryLastModifiedTime(targetElementId)
371
- : args.sourceDb.elements.queryLastModifiedTime(sourceElementId);
372
- const aspectIdentifier = args.isReverseSynchronization
373
- ? targetElementId
374
- : sourceElementId;
375
- const aspectProps = {
376
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
377
- element: {
378
- id: elementId,
379
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
380
- },
381
- scope: { id: args.targetScopeElementId },
382
- identifier: aspectIdentifier,
383
- kind: core_backend_1.ExternalSourceAspect.Kind.Element,
384
- version,
385
- };
386
- return aspectProps;
387
- }
388
- static initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, args) {
389
- const provenanceDb = args.isReverseSynchronization
390
- ? args.sourceDb
391
- : args.targetDb;
392
- const aspectIdentifier = args.isReverseSynchronization
393
- ? targetRelInstanceId
394
- : sourceRelInstanceId;
395
- const provenanceRelInstanceId = args.isReverseSynchronization
396
- ? sourceRelInstanceId
397
- : targetRelInstanceId;
398
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
399
- const elementId = provenanceDb.withPreparedStatement("SELECT SourceECInstanceId FROM bis.ElementRefersToElements WHERE ECInstanceId=?", (stmt) => {
400
- stmt.bindId(1, provenanceRelInstanceId);
401
- nodeAssert(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW);
402
- return stmt.getValue(0).getId();
403
- });
404
- const jsonProperties = args.forceOldRelationshipProvenanceMethod
405
- ? { targetRelInstanceId }
406
- : { provenanceRelInstanceId };
407
- const aspectProps = {
408
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
409
- element: {
410
- id: elementId,
411
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
412
- },
413
- scope: { id: args.targetScopeElementId },
414
- identifier: aspectIdentifier,
415
- kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
416
- jsonProperties: JSON.stringify(jsonProperties),
417
- };
418
- return aspectProps;
419
- }
420
275
  /**
421
276
  * Previously the transformer would insert provenance always pointing to the "target" relationship.
422
277
  * It should (and now by default does) instead insert provenance pointing to the provenanceSource
@@ -425,43 +280,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
425
280
  */
426
281
  _forceOldRelationshipProvenanceMethod = false;
427
282
  /** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
428
- initElementProvenance(sourceElementId, targetElementId) {
429
- return IModelTransformer.initElementProvenanceOptions(sourceElementId, targetElementId, {
430
- isReverseSynchronization: this.isReverseSynchronization,
431
- targetScopeElementId: this.targetScopeElementId,
432
- sourceDb: this.sourceDb,
433
- targetDb: this.targetDb,
434
- });
435
- }
436
- /** Create an ExternalSourceAspectProps in a standard way for a Relationship in an iModel --> iModel transformations.
437
- * The ExternalSourceAspect is meant to be owned by the Element in the target iModel that is the `sourceId` of transformed relationship.
438
- * The `identifier` property of the ExternalSourceAspect will be the ECInstanceId of the relationship in the master iModel.
439
- * The ECInstanceId of the relationship in the branch iModel will be stored in the JsonProperties of the ExternalSourceAspect.
440
- */
441
- initRelationshipProvenance(sourceRelationship, targetRelInstanceId) {
442
- return IModelTransformer.initRelationshipProvenanceOptions(sourceRelationship.id, targetRelInstanceId, {
443
- sourceDb: this.sourceDb,
444
- targetDb: this.targetDb,
445
- isReverseSynchronization: this.isReverseSynchronization,
446
- targetScopeElementId: this.targetScopeElementId,
447
- forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
448
- });
449
- }
450
- /** NOTE: the json properties must be converted to string before insertion */
451
- _targetScopeProvenanceProps = undefined;
452
- /**
453
- * Index of the changeset that the transformer was at when the transformation begins (was constructed).
454
- * Used to determine at the end which changesets were part of a synchronization.
455
- */
456
- _startingChangesetIndices = undefined;
457
- _cachedSynchronizationVersion = undefined;
458
- /**
459
- * We cache the synchronization version to avoid querying the target scoping ESA multiple times.
460
- * If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
461
- * Sets this._cachedSynchronizationVersion to undefined.
462
- */
463
- clearCachedSynchronizationVersion() {
464
- this._cachedSynchronizationVersion = undefined;
283
+ async initElementProvenance(sourceElementId, targetElementId) {
284
+ return this._provenanceManager.initElementProvenance(sourceElementId, targetElementId);
465
285
  }
466
286
  /** the changeset in the scoping element's source version found for this transformation
467
287
  * @note the version depends on whether this is a reverse synchronization or not, as
@@ -470,448 +290,59 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
470
290
  * @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".
471
291
  * @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
472
292
  */
473
- get synchronizationVersion() {
474
- if (this._cachedSynchronizationVersion === undefined) {
475
- const provenanceScopeAspect = this.tryGetProvenanceScopeAspect();
476
- if (!provenanceScopeAspect) {
477
- return { index: -1, id: "" }; // first synchronization.
478
- }
479
- const version = this.isReverseSynchronization
480
- ? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
481
- : provenanceScopeAspect.version;
482
- if (!version &&
483
- this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
484
- return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
485
- }
486
- if (version === undefined) {
487
- 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.
488
- Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
489
- }
490
- const [id, index] = version === "" ? ["", -1] : version.split(";");
491
- if (Number.isNaN(Number(index)))
492
- throw new Error("Could not parse version data from scope aspect");
493
- this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
494
- }
495
- return this._cachedSynchronizationVersion;
293
+ async getSynchronizationVersion() {
294
+ return this._provenanceManager.getSynchronizationVersion();
496
295
  }
497
296
  /**
498
297
  * @returns provenance scope aspect if it exists in the provenanceDb.
499
298
  * Provenance scope aspect is created and inserted into provenanceDb when [[initScopeProvenance]] is invoked.
500
299
  */
501
- tryGetProvenanceScopeAspect() {
502
- const scopeProvenanceAspectProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, {
503
- id: undefined,
504
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
505
- scope: { id: core_common_1.IModel.rootSubjectId },
506
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
507
- element: { id: this.targetScopeElementId ?? core_common_1.IModel.rootSubjectId },
508
- identifier: this.provenanceSourceDb.iModelId,
509
- });
510
- return scopeProvenanceAspectProps !== undefined
511
- ? this.provenanceDb.elements.getAspect(scopeProvenanceAspectProps.aspectId)
512
- : undefined;
300
+ async tryGetProvenanceScopeAspect() {
301
+ return this._provenanceManager.tryGetProvenanceScopeAspect();
513
302
  }
514
303
  /**
515
304
  * Make sure there are no conflicting other scope-type external source aspects on the *target scope element*,
516
305
  * If there are none at all, insert one, then this must be a first synchronization.
517
- * @returns the last synced version (changesetId) on the target scope's external source aspect,
518
- * if this was a [BriefcaseDb]($backend)
519
306
  */
520
- initScopeProvenance() {
521
- const aspectProps = {
522
- id: undefined,
523
- version: undefined,
524
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
525
- element: {
526
- id: this.targetScopeElementId,
527
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
528
- },
529
- scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
530
- identifier: this.provenanceSourceDb.iModelId,
531
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
532
- jsonProperties: undefined,
533
- };
534
- const foundEsaProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, aspectProps); // this query includes "identifier"
535
- if (foundEsaProps === undefined) {
536
- aspectProps.version = ""; // empty since never before transformed. Will be updated in [[finalizeTransformation]]
537
- aspectProps.jsonProperties = {
538
- pendingReverseSyncChangesetIndices: [],
539
- pendingSyncChangesetIndices: [],
540
- reverseSyncVersion: "", // empty since never before transformed. Will be updated in first reverse sync
541
- };
542
- // this query does not include "identifier" to find possible conflicts
543
- const sql = `
544
- SELECT ECInstanceId
545
- FROM ${core_backend_1.ExternalSourceAspect.classFullName}
546
- WHERE Element.Id=:elementId
547
- AND Scope.Id=:scopeId
548
- AND Kind=:kind
549
- LIMIT 1
550
- `;
551
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
552
- const hasConflictingScope = this.provenanceDb.withPreparedStatement(sql,
553
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
554
- (statement) => {
555
- statement.bindId("elementId", aspectProps.element.id);
556
- statement.bindId("scopeId", aspectProps.scope.id); // this scope.id can never be invalid, we create it above
557
- statement.bindString("kind", aspectProps.kind);
558
- return core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step();
559
- });
560
- if (hasConflictingScope) {
561
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Provenance scope conflict");
562
- }
563
- if (!this._options.noProvenance) {
564
- const id = this.provenanceDb.elements.insertAspect({
565
- ...aspectProps,
566
- jsonProperties: JSON.stringify(aspectProps.jsonProperties),
567
- });
568
- aspectProps.id = id;
569
- // Busting a potential cached version
570
- this.clearCachedSynchronizationVersion();
571
- }
572
- }
573
- else {
574
- // foundEsaProps is defined.
575
- aspectProps.id = foundEsaProps.aspectId;
576
- aspectProps.version = foundEsaProps.version;
577
- aspectProps.jsonProperties = foundEsaProps.jsonProperties
578
- ? JSON.parse(foundEsaProps.jsonProperties)
579
- : undefined;
580
- // Clone oldProps incase they're changed for logging purposes
581
- const oldProps = JSON.parse(JSON.stringify(aspectProps));
582
- if (this.handleUnsafeMigrate(aspectProps)) {
583
- 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 });
584
- this.provenanceDb.elements.updateAspect({
585
- ...aspectProps,
586
- jsonProperties: JSON.stringify(aspectProps.jsonProperties),
587
- });
588
- // Busting a potential cached version
589
- this.clearCachedSynchronizationVersion();
590
- }
591
- }
592
- this._targetScopeProvenanceProps =
593
- aspectProps;
594
- }
595
- /** Returns true if a change was made to the aspectProps. */
596
- handleUnsafeMigrate(aspectProps) {
597
- let madeChange = false;
598
- if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
599
- return madeChange;
600
- const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
601
- const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
602
- "";
603
- if (aspectProps.version === undefined ||
604
- (aspectProps.version === "" &&
605
- aspectProps.version !== fallbackSyncVersionToUse)) {
606
- aspectProps.version = fallbackSyncVersionToUse;
607
- madeChange = true;
608
- }
609
- if (aspectProps.jsonProperties === undefined) {
610
- aspectProps.jsonProperties = {
611
- pendingReverseSyncChangesetIndices: [],
612
- pendingSyncChangesetIndices: [],
613
- reverseSyncVersion: fallbackReverseSyncVersionToUse,
614
- };
615
- madeChange = true;
616
- }
617
- else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
618
- (aspectProps.jsonProperties.reverseSyncVersion === "" &&
619
- aspectProps.jsonProperties.reverseSyncVersion !==
620
- fallbackReverseSyncVersionToUse)) {
621
- aspectProps.jsonProperties.reverseSyncVersion =
622
- fallbackReverseSyncVersionToUse;
623
- madeChange = true;
624
- }
625
- /**
626
- * This case will only be hit when:
627
- * - first transformation was performed on pre-fedguid transformer.
628
- * - a second processAll transformation was performed on the same target-source iModels post-fedguid transformer.
629
- * - change processing was invoked on for the second 'initial' transformation.
630
- * NOTE: This case likely does not exist anymore, but we will keep it just to be sure.
631
- */
632
- if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
633
- undefined) {
634
- core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
635
- aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
636
- madeChange = true;
637
- }
638
- if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
639
- core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
640
- aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
641
- madeChange = true;
642
- }
643
- return madeChange;
307
+ async initScopeProvenance() {
308
+ return this._provenanceManager.initScopeProvenance();
644
309
  }
645
310
  /**
646
- * Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
647
- * and call a function for each one.
648
- * @note provenance is done by federation guids where possible
649
- * @note this may execute on each element more than once! Only use in cases where that is handled
311
+ * Get the IModelDb where provenance (ExternalSourceAspects) is stored.
312
+ * This will be targetDb except when it is a reverse synchronization, in which case it will be sourceDb.
650
313
  */
651
- static forEachTrackedElement(args) {
652
- if (args.provenanceDb === args.provenanceSourceDb)
653
- return;
654
- if (!args.provenanceDb.containsClass(core_backend_1.ExternalSourceAspect.classFullName)) {
655
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadSchema, "The BisCore schema version of the target database is too old");
656
- }
657
- const sourceDb = args.isReverseSynchronization
658
- ? args.provenanceDb
659
- : args.provenanceSourceDb;
660
- const targetDb = args.isReverseSynchronization
661
- ? args.provenanceSourceDb
662
- : args.provenanceDb;
663
- // query for provenanceDb
664
- const elementIdByFedGuidQuery = `
665
- SELECT e.ECInstanceId, FederationGuid
666
- FROM bis.Element e
667
- ${args.skipPropagateChangesToRootElements
668
- ? "WHERE e.ECInstanceId NOT IN (0x1, 0xe, 0x10) -- special static elements"
669
- : ""}
670
- ORDER BY FederationGuid
671
- `;
672
- // iterate through sorted list of fed guids from both dbs to get the intersection
673
- // NOTE: if we exposed the native attach database support,
674
- // we could get the intersection of fed guids in one query, not sure if it would be faster
675
- // OR we could do a raw sqlite query...
676
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
677
- sourceDb.withStatement(elementIdByFedGuidQuery, (sourceStmt) =>
678
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
679
- targetDb.withStatement(elementIdByFedGuidQuery, (targetStmt) => {
680
- if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
681
- return;
682
- let sourceRow = sourceStmt.getRow();
683
- if (targetStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
684
- return;
685
- let targetRow = targetStmt.getRow();
686
- // NOTE: these comparisons rely upon the lowercase of the guid,
687
- // and the fact that '0' < '9' < a' < 'f' in ascii/utf8
688
- while (true) {
689
- const currSourceRow = sourceRow, currTargetRow = targetRow;
690
- if (currSourceRow.federationGuid !== undefined &&
691
- currTargetRow.federationGuid !== undefined &&
692
- currSourceRow.federationGuid === currTargetRow.federationGuid) {
693
- // data flow direction is always sourceDb -> targetDb and it does not depend on where the explicit element provenance is stored
694
- args.fn(sourceRow.id, targetRow.id);
695
- }
696
- if (currTargetRow.federationGuid === undefined ||
697
- (currSourceRow.federationGuid !== undefined &&
698
- currSourceRow.federationGuid >= currTargetRow.federationGuid)) {
699
- if (targetStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
700
- return;
701
- targetRow = targetStmt.getRow();
702
- }
703
- if (currSourceRow.federationGuid === undefined ||
704
- (currTargetRow.federationGuid !== undefined &&
705
- currSourceRow.federationGuid <= currTargetRow.federationGuid)) {
706
- if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
707
- return;
708
- sourceRow = sourceStmt.getRow();
709
- }
710
- }
711
- }));
712
- // query for provenanceDb
713
- const provenanceAspectsQuery = `
714
- SELECT esa.Identifier, Element.Id
715
- FROM bis.ExternalSourceAspect esa
716
- WHERE Scope.Id=:scopeId
717
- AND Kind=:kind
718
- `;
719
- // Technically this will a second time call the function (as documented) on
720
- // victims of the old provenance method that have both fedguids and an inserted aspect.
721
- // But this is a private function with one known caller where that doesn't matter
722
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
723
- args.provenanceDb.withPreparedStatement(provenanceAspectsQuery, (stmt) => {
724
- const runFnInDataFlowDirection = (sourceId, targetId) => args.isReverseSynchronization
725
- ? args.fn(sourceId, targetId)
726
- : args.fn(targetId, sourceId);
727
- stmt.bindId("scopeId", args.targetScopeElementId);
728
- stmt.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
729
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
730
- // ExternalSourceAspect.Identifier is of type string
731
- const aspectIdentifier = stmt.getValue(0).getString();
732
- const elementId = stmt.getValue(1).getId();
733
- runFnInDataFlowDirection(elementId, aspectIdentifier);
734
- }
735
- });
736
- }
737
- forEachTrackedElement(fn) {
738
- return IModelTransformer.forEachTrackedElement({
739
- provenanceSourceDb: this.provenanceSourceDb,
740
- provenanceDb: this.provenanceDb,
741
- targetScopeElementId: this.targetScopeElementId,
742
- isReverseSynchronization: this.isReverseSynchronization,
743
- fn,
744
- skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
745
- });
314
+ async getProvenanceDb() {
315
+ return (await this.getIsReverseSynchronization())
316
+ ? this.sourceDb
317
+ : this.targetDb;
746
318
  }
747
319
  /**
748
- * Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
749
- * The identifier on the ESA is the id of the element in the [[IModelTransformer.provenanceSourceDb]]
750
- * Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
751
- * @param entityInProvenanceSourceId
752
- * @returns the elementId that the ESA is stored on, esa.Element.Id
320
+ * Get whether this is a reverse synchronization.
753
321
  */
754
- _queryProvenanceForElement(entityInProvenanceSourceId) {
755
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
756
- return this.provenanceDb.withPreparedStatement(`
757
- SELECT esa.Element.Id
758
- FROM Bis.ExternalSourceAspect esa
759
- WHERE esa.Kind=?
760
- AND esa.Scope.Id=?
761
- AND esa.Identifier=?
762
- `, (stmt) => {
763
- stmt.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Element);
764
- stmt.bindId(2, this.targetScopeElementId);
765
- stmt.bindString(3, entityInProvenanceSourceId);
766
- if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
767
- return stmt.getValue(0).getId();
768
- else
769
- return undefined;
770
- });
322
+ async getIsReverseSynchronization() {
323
+ return (await this._syncTypeResolver.getSyncType()) === "reverse";
771
324
  }
772
325
  /**
773
- * Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
774
- * The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
775
- * Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
776
- * @param entityInProvenanceSourceId
777
- * @returns
326
+ * Get whether this is a forward synchronization.
778
327
  */
779
- _queryProvenanceForRelationship(entityInProvenanceSourceId, sourceRelInfo) {
780
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
781
- return this.provenanceDb.withPreparedStatement(`
782
- SELECT
783
- ECInstanceId,
784
- JSON_EXTRACT(JsonProperties, '$.targetRelInstanceId'),
785
- JSON_EXTRACT(JsonProperties, '$.provenanceRelInstanceId')
786
- FROM Bis.ExternalSourceAspect
787
- WHERE Kind=?
788
- AND Scope.Id=?
789
- AND Identifier=?
790
- `, (stmt) => {
791
- stmt.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Relationship);
792
- stmt.bindId(2, this.targetScopeElementId);
793
- stmt.bindString(3, entityInProvenanceSourceId);
794
- if (stmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
795
- return undefined;
796
- const aspectId = stmt.getValue(0).getId();
797
- const provenanceRelInstIdVal = stmt.getValue(2);
798
- const provenanceRelInstanceId = !provenanceRelInstIdVal.isNull
799
- ? provenanceRelInstIdVal.getString()
800
- : this._queryTargetRelId(sourceRelInfo);
801
- return {
802
- aspectId,
803
- relationshipId: provenanceRelInstanceId,
804
- };
805
- });
328
+ async getIsForwardSynchronization() {
329
+ return (await this._syncTypeResolver.getSyncType()) === "forward";
806
330
  }
807
- _queryTargetRelId(sourceRelInfo) {
808
- const targetRelInfo = {
809
- sourceId: this.context.findTargetElementId(sourceRelInfo.sourceId),
810
- targetId: this.context.findTargetElementId(sourceRelInfo.targetId),
811
- };
812
- if (targetRelInfo.sourceId === undefined ||
813
- targetRelInfo.targetId === undefined)
814
- return undefined; // couldn't find an element, rel is invalid or deleted
815
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
816
- return this.targetDb.withPreparedStatement(`
817
- SELECT ECInstanceId
818
- FROM bis.ElementRefersToElements
819
- WHERE SourceECInstanceId=?
820
- AND TargetECInstanceId=?
821
- AND ECClassId=?
822
- `, (stmt) => {
823
- stmt.bindId(1, targetRelInfo.sourceId);
824
- stmt.bindId(2, targetRelInfo.targetId);
825
- stmt.bindId(3, this._targetClassNameToClassId(sourceRelInfo.classFullName));
826
- if (stmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
827
- return undefined;
828
- return stmt.getValue(0).getId();
829
- });
830
- }
831
- _targetClassNameToClassIdCache = new Map();
832
- _targetClassNameToClassId(classFullName) {
833
- let classId = this._targetClassNameToClassIdCache.get(classFullName);
834
- if (classId === undefined) {
835
- classId = this._getRelClassId(this.targetDb, classFullName);
836
- this._targetClassNameToClassIdCache.set(classFullName, classId);
837
- }
838
- return classId;
839
- }
840
- // NOTE: this doesn't handle remapped element classes,
841
- // but is only used for relationships rn
842
- _getRelClassId(db, classFullName) {
843
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
844
- return db.withPreparedStatement(`
845
- SELECT c.ECInstanceId
846
- FROM ECDbMeta.ECClassDef c
847
- JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
848
- WHERE s.Name=? AND c.Name=?
849
- `, (stmt) => {
850
- const [schemaName, className] = classFullName.indexOf(".") !== -1
851
- ? classFullName.split(".")
852
- : classFullName.split(":");
853
- stmt.bindString(1, schemaName);
854
- stmt.bindString(2, className);
855
- if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
856
- return stmt.getValue(0).getId();
857
- (0, core_bentley_1.assert)(false, "relationship was not found");
858
- });
859
- }
860
- _queryElemIdByFedGuid(db, fedGuid) {
861
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
862
- return db.withPreparedStatement("SELECT ECInstanceId FROM Bis.Element WHERE FederationGuid=?", (stmt) => {
863
- stmt.bindGuid(1, fedGuid);
864
- if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
865
- return stmt.getValue(0).getId();
866
- else
867
- return undefined;
331
+ /**
332
+ * Updates the synchronization version on the scope ESA.
333
+ */
334
+ async updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
335
+ return this._provenanceManager.updateSynchronizationVersion({
336
+ initializeReverseSyncVersion,
337
+ sourceChangeDataState: this._sourceChangeDataState,
868
338
  });
869
339
  }
870
340
  /** Returns `true` if *brute force* delete detections should be run.
871
341
  * @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
872
342
  * @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
873
343
  */
874
- shouldDetectDeletes() {
875
- nodeAssert(this._syncType !== undefined);
876
- return this._syncType === "not-sync";
877
- }
878
- /**
879
- * Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements
880
- * in the source iModel.
881
- * @deprecated in 1.x. Do not use this. // FIXME<MIKE>: how to better explain this?
882
- * This method is only called during [[process]] when [[IModelTransformOptions.argsForProcessChanges]] is undefined and the option
883
- * [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
884
- * necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] defined, since changeset information is sufficient.
885
- * @note you do not need to call this directly unless processing a subset of an iModel.
886
- * @throws [[IModelError]] If the required provenance information is not available to detect deletes.
887
- */
888
- async detectElementDeletes() {
889
- const sql = `
890
- SELECT Identifier, Element.Id
891
- FROM BisCore.ExternalSourceAspect
892
- WHERE Scope.Id=:scopeId
893
- AND Kind=:kind
894
- `;
895
- nodeAssert(!this.isReverseSynchronization, "synchronizations with processChanges already detect element deletes, don't call detectElementDeletes");
896
- // Reported issue: https://github.com/iTwin/itwinjs-core/issues/7989
897
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
898
- this.provenanceDb.withPreparedStatement(sql, (stmt) => {
899
- stmt.bindId("scopeId", this.targetScopeElementId);
900
- stmt.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
901
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
902
- // ExternalSourceAspect.Identifier is of type string
903
- const aspectIdentifier = stmt.getValue(0).getString();
904
- if (!core_bentley_1.Id64.isValidId64(aspectIdentifier)) {
905
- continue;
906
- }
907
- const targetElemId = stmt.getValue(1).getId();
908
- const wasDeletedInSource = !EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
909
- entityReference: `e${aspectIdentifier}`,
910
- });
911
- if (wasDeletedInSource)
912
- this.importer.deleteElement(targetElemId);
913
- }
914
- });
344
+ async shouldDetectDeletes() {
345
+ return (await this._syncTypeResolver.getSyncType()) === "not-sync";
915
346
  }
916
347
  /** Transform the specified sourceElement into ElementProps for the target iModel.
917
348
  * @param sourceElement The Element from the source iModel to transform.
@@ -919,9 +350,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
919
350
  * @note A subclass can override this method to provide custom transform behavior.
920
351
  * @note This can be called more than once for an element in arbitrary order, so it should not have side-effects.
921
352
  */
922
- onTransformElement(sourceElement) {
353
+ async onTransformElement(sourceElement) {
923
354
  core_bentley_1.Logger.logTrace(loggerCategory, `onTransformElement(${sourceElement.id}) "${sourceElement.getDisplayLabel()}"`);
924
- const targetElementProps = this.context.cloneElement(sourceElement, { binaryGeometry: this._options.cloneUsingBinaryGeometry });
355
+ const targetElementProps = await this._cloneContext.cloneElement(sourceElement, {
356
+ binaryGeometry: this._options.cloneUsingBinaryGeometry,
357
+ });
925
358
  // Special case: source element is the root subject
926
359
  if (sourceElement.id === core_common_1.IModel.rootSubjectId) {
927
360
  const targetElementId = this.context.findTargetElementId(sourceElement.id);
@@ -964,7 +397,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
964
397
  const srcEcefLoc = this.sourceDb.ecefLocation;
965
398
  const targetEcefLoc = this.targetDb.ecefLocation;
966
399
  if (srcEcefLoc === undefined || targetEcefLoc === undefined) {
967
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NoGeoLocation, "Both source and target ECEF locations must be defined to calculate the transform.");
400
+ core_bentley_1.ITwinError.throwError({
401
+ iTwinErrorId: {
402
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
403
+ key: IModelTransformerError_1.IModelTransformerError.GeolocationUnavailable,
404
+ },
405
+ message: "Both source and target ECEF locations must be defined to calculate the transform.",
406
+ });
968
407
  }
969
408
  if (srcEcefLoc.getTransform().isAlmostEqual(targetEcefLoc.getTransform())) {
970
409
  core_bentley_1.Logger.logTrace(loggerCategory, "ECEF data is already aligned. No spatial transforms needed.");
@@ -973,7 +412,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
973
412
  const srcSpatialToECEF = srcEcefLoc.getTransform(); // converts relative to ECEF in relation to source
974
413
  const targetECEFToSpatial = targetEcefLoc.getTransform().inverse(); // converts ECEF to relative in relation to target
975
414
  if (!targetECEFToSpatial) {
976
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NoGeoLocation, "Failed to invert target ECEF transform.");
415
+ core_bentley_1.ITwinError.throwError({
416
+ iTwinErrorId: {
417
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
418
+ key: IModelTransformerError_1.IModelTransformerError.GeolocationUnavailable,
419
+ },
420
+ message: "Failed to invert target ECEF transform.",
421
+ });
977
422
  }
978
423
  const ecefTransform = targetECEFToSpatial.multiplyTransformTransform(srcSpatialToECEF); // chain both transforms
979
424
  return ecefTransform;
@@ -991,15 +436,33 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
991
436
  calculateTransformFromHelmertTransforms() {
992
437
  if (this.sourceDb.geographicCoordinateSystem?.horizontalCRS === undefined ||
993
438
  this.sourceDb.geographicCoordinateSystem?.verticalCRS === undefined) {
994
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Source iModel does not have a geographic coordinate system defined.");
439
+ core_bentley_1.ITwinError.throwError({
440
+ iTwinErrorId: {
441
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
442
+ key: IModelTransformerError_1.IModelTransformerError.GeographicCoordinateSystemUnavailable,
443
+ },
444
+ message: "Source iModel does not have a geographic coordinate system defined.",
445
+ });
995
446
  }
996
447
  if (this.targetDb.geographicCoordinateSystem?.horizontalCRS === undefined ||
997
448
  this.targetDb.geographicCoordinateSystem.verticalCRS === undefined) {
998
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Target iModel does not have a geographic coordinate system defined.");
449
+ core_bentley_1.ITwinError.throwError({
450
+ iTwinErrorId: {
451
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
452
+ key: IModelTransformerError_1.IModelTransformerError.GeographicCoordinateSystemUnavailable,
453
+ },
454
+ message: "Target iModel does not have a geographic coordinate system defined.",
455
+ });
999
456
  }
1000
457
  if (!this.sourceDb.geographicCoordinateSystem.horizontalCRS.equals(this.targetDb.geographicCoordinateSystem.horizontalCRS) ||
1001
458
  !this.sourceDb.geographicCoordinateSystem.verticalCRS.equals(this.targetDb.geographicCoordinateSystem.verticalCRS)) {
1002
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.MismatchGcs, "Source and target geographic coordinate systems must match to calculate the spatial transform.");
459
+ core_bentley_1.ITwinError.throwError({
460
+ iTwinErrorId: {
461
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
462
+ key: IModelTransformerError_1.IModelTransformerError.GeographicCoordinateSystemMismatch,
463
+ },
464
+ message: "Source and target geographic coordinate systems must match to calculate the spatial transform.",
465
+ });
1003
466
  }
1004
467
  if (this.sourceDb.geographicCoordinateSystem.additionalTransform ===
1005
468
  this.targetDb.geographicCoordinateSystem.additionalTransform) {
@@ -1011,14 +474,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1011
474
  const targetScale = this.targetDb.geographicCoordinateSystem.additionalTransform
1012
475
  ?.helmert2DWithZOffset?.scale ?? 1;
1013
476
  if (srcScale !== targetScale) {
1014
- 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.");
477
+ core_bentley_1.ITwinError.throwError({
478
+ iTwinErrorId: {
479
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
480
+ key: IModelTransformerError_1.IModelTransformerError.GeographicCoordinateSystemMismatch,
481
+ },
482
+ message: "Spatial transform is non rigid. Source and target Helmert transforms must have the same scale to calculate a rigid spatial transform.",
483
+ });
1015
484
  }
1016
485
  const srcTransform = IModelTransformer.convertHelmertToTransform(this.sourceDb.geographicCoordinateSystem.additionalTransform
1017
486
  ?.helmert2DWithZOffset); // moves elements to where src helmert transform would move them at render time
1018
487
  const targetTransformInv = IModelTransformer.convertHelmertToTransform(this.targetDb.geographicCoordinateSystem.additionalTransform
1019
488
  ?.helmert2DWithZOffset).inverse(); // negates target helmert transform that is applied at render time
1020
489
  if (!targetTransformInv) {
1021
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NoGeoLocation, "Failed to invert target Helmert transform.");
490
+ core_bentley_1.ITwinError.throwError({
491
+ iTwinErrorId: {
492
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
493
+ key: IModelTransformerError_1.IModelTransformerError.GeolocationUnavailable,
494
+ },
495
+ message: "Failed to invert target Helmert transform.",
496
+ });
1022
497
  }
1023
498
  const combinedTransform = targetTransformInv.multiplyTransformTransform(srcTransform);
1024
499
  return combinedTransform;
@@ -1028,6 +503,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1028
503
  /** Returns true if a change within sourceElement is detected.
1029
504
  * @param sourceElement The Element from the source iModel
1030
505
  * @note A subclass can override this method to provide custom change detection behavior.
506
+ * @note During change processing, elements not present in the changeset are short-circuited
507
+ * in the exporter before reaching this method. To add elements to the changeset programmatically,
508
+ * use [[addCustomChanges]] rather than overriding this method to expand processing.
1031
509
  */
1032
510
  hasElementChanged(sourceElement) {
1033
511
  const sourceDbChanges = this.exporter.sourceDbChanges;
@@ -1035,7 +513,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1035
513
  sourceDbChanges.element.insertIds.has(sourceElement.id) ||
1036
514
  sourceDbChanges.element.updateIds.has(sourceElement.id));
1037
515
  }
1038
- completePartiallyCommittedElements() {
516
+ async completePartiallyCommittedElements() {
1039
517
  for (const sourceElementId of this._partiallyCommittedElementIds) {
1040
518
  const sourceElement = this.sourceDb.elements.getElement({
1041
519
  id: sourceElementId,
@@ -1046,25 +524,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1046
524
  if (core_bentley_1.Id64.isInvalid(targetId)) {
1047
525
  throw new Error(`source-target element mapping not found for element "${sourceElementId}" when completing partially committed elements. This is a bug.`);
1048
526
  }
1049
- const targetProps = this.onTransformElement(sourceElement);
1050
- this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
527
+ const targetProps = await this.onTransformElement(sourceElement);
528
+ this._targetEditTxn.updateElement({ ...targetProps, id: targetId });
1051
529
  }
1052
530
  }
1053
- completePartiallyCommittedAspects() {
531
+ async completePartiallyCommittedAspects() {
1054
532
  for (const sourceAspectId of this._partiallyCommittedAspectIds) {
1055
533
  const sourceAspect = this.sourceDb.elements.getAspect(sourceAspectId);
1056
534
  const targetAspectId = this.context.findTargetAspectId(sourceAspectId);
1057
535
  if (core_bentley_1.Id64.isInvalid(targetAspectId)) {
1058
536
  throw new Error(`source-target aspect mapping not found for aspect "${sourceAspectId}" when completing partially committed aspects. This is a bug.`);
1059
537
  }
1060
- const targetAspectProps = this.onTransformElementAspect(sourceAspect);
1061
- this.targetDb.elements.updateAspect({
538
+ const targetAspectProps = await this.onTransformElementAspect(sourceAspect);
539
+ this._targetEditTxn.updateAspect({
1062
540
  ...targetAspectProps,
1063
541
  id: targetAspectId,
1064
542
  });
1065
543
  }
1066
544
  }
1067
- doAllReferencesExistInTarget(entity) {
545
+ async doAllReferencesExistInTarget(entity) {
1068
546
  let allReferencesExist = true;
1069
547
  for (const referenceId of entity.getReferenceIds()) {
1070
548
  const referencedEntityId = core_backend_1.EntityReferences.toId64(referenceId);
@@ -1074,7 +552,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1074
552
  continue;
1075
553
  }
1076
554
  if (allReferencesExist &&
1077
- !core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
555
+ !core_backend_1.EntityReferences.isValid(await this.context.findTargetEntityId(referenceId))) {
1078
556
  // if we care about references existing then we cannot return early and must check all other references.
1079
557
  if (this._options.danglingReferencesBehavior === "ignore") {
1080
558
  return false;
@@ -1082,22 +560,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1082
560
  allReferencesExist = false;
1083
561
  }
1084
562
  if (this._options.danglingReferencesBehavior === "reject") {
1085
- this.assertReferenceExistsInSource(referenceId, entity);
563
+ await this.assertReferenceExistsInSource(referenceId, entity);
1086
564
  }
1087
565
  }
1088
566
  return allReferencesExist;
1089
567
  }
1090
- assertReferenceExistsInSource(referenceId, entity) {
1091
- const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
568
+ async assertReferenceExistsInSource(referenceId, entity) {
569
+ const referencedExistsInSource = await EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
1092
570
  entityReference: referenceId,
1093
571
  });
1094
572
  if (!referencedExistsInSource) {
1095
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, [
1096
- `Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
1097
- "This must have been caused by an upstream application that changed the iModel.",
1098
- "You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this,",
1099
- `and the referenceId found on "${entity.id}" will not be carried over to corresponding target element.`,
1100
- ].join("\n"));
573
+ core_bentley_1.ITwinError.throwError({
574
+ iTwinErrorId: {
575
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
576
+ key: IModelTransformerError_1.IModelTransformerError.DanglingReference,
577
+ },
578
+ message: [
579
+ `Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
580
+ "This must have been caused by an upstream application that changed the iModel.",
581
+ "You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this,",
582
+ `and the referenceId found on "${entity.id}" will not be carried over to corresponding target element.`,
583
+ ].join("\n"),
584
+ });
1101
585
  }
1102
586
  }
1103
587
  /** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
@@ -1107,9 +591,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1107
591
  async processElement(sourceElementId) {
1108
592
  await this.initialize();
1109
593
  if (sourceElementId === core_common_1.IModel.rootSubjectId) {
1110
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "The root Subject should not be directly imported");
594
+ core_bentley_1.ITwinError.throwError({
595
+ iTwinErrorId: {
596
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
597
+ key: IModelTransformerError_1.IModelTransformerError.RootSubjectNotProcessable,
598
+ },
599
+ message: "The root Subject should not be directly imported",
600
+ });
1111
601
  }
1112
- return this.exporter.exportElement(sourceElementId);
602
+ return this.processScopedElementExport(async () => this.exporter.exportElement(sourceElementId));
1113
603
  }
1114
604
  /** Import child elements into the target IModelDb
1115
605
  * @param sourceElementId Import the child elements of this element in the source IModelDb.
@@ -1117,12 +607,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1117
607
  */
1118
608
  async processChildElements(sourceElementId) {
1119
609
  await this.initialize();
1120
- return this.exporter.exportChildElements(sourceElementId);
610
+ return this.processScopedElementExport(async () => this.exporter.exportChildElements(sourceElementId));
611
+ }
612
+ async processScopedElementExport(exportElements) {
613
+ await this.exporter.elementAspectExportCoordinator.run(exportElements);
1121
614
  }
1122
615
  /** Override of [IModelExportHandler.shouldExportElement]($transformer) that is called to determine if an element should be exported from the source iModel.
1123
616
  * @note Reaching this point means that the element has passed the standard exclusion checks in IModelExporter.
1124
617
  */
1125
- shouldExportElement(_sourceElement) {
618
+ async shouldExportElement(_sourceElement) {
1126
619
  return true;
1127
620
  }
1128
621
  /**
@@ -1130,6 +623,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1130
623
  * @internal do not call, override or implement this, it will be removed
1131
624
  */
1132
625
  async preExportElement(sourceElement) {
626
+ if (!this.hasElementChanged(sourceElement)) {
627
+ core_bentley_1.Logger.logTrace(loggerCategory, `Skipping unchanged element (${sourceElement.id}, ${sourceElement.getDisplayLabel()}).`);
628
+ return;
629
+ }
1133
630
  const elemClass = sourceElement.constructor;
1134
631
  const unresolvedReferences = elemClass.requiredReferenceKeys
1135
632
  .map((referenceKey) => {
@@ -1164,7 +661,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1164
661
  .flat();
1165
662
  if (unresolvedReferences.length > 0) {
1166
663
  for (const reference of unresolvedReferences) {
1167
- const processState = this.getElemTransformState(reference);
664
+ const processState = await this.getElemTransformState(reference);
1168
665
  // must export element first
1169
666
  if (processState.needsElemImport)
1170
667
  await this.exporter.exportElement(reference);
@@ -1173,23 +670,55 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1173
670
  }
1174
671
  }
1175
672
  }
1176
- getElemTransformState(elementId) {
1177
- const dbHasModel = (db, id) => {
673
+ async getElemTransformState(elementId) {
674
+ const dbHasModel = async (db, id) => {
1178
675
  const maybeModelId = core_backend_1.EntityReferences.fromEntityType(id, core_common_1.ConcreteEntityTypes.Model);
1179
676
  return EntityUnifier_1.EntityUnifier.exists(db, { entityReference: maybeModelId });
1180
677
  };
1181
- const isSubModeled = dbHasModel(this.sourceDb, elementId);
678
+ const isSubModeled = await dbHasModel(this.sourceDb, elementId);
1182
679
  const idOfElemInTarget = this.context.findTargetElementId(elementId);
1183
680
  const isElemInTarget = core_bentley_1.Id64.invalid !== idOfElemInTarget;
1184
681
  const needsModelImport = isSubModeled &&
1185
- (!isElemInTarget || !dbHasModel(this.targetDb, idOfElemInTarget));
682
+ (!isElemInTarget || !(await dbHasModel(this.targetDb, idOfElemInTarget)));
1186
683
  return { needsElemImport: !isElemInTarget, needsModelImport };
1187
684
  }
685
+ // In iTwin js 5.x Elements.queryElementIdByCode() uses Code class to query id:
686
+ // https://github.com/iTwin/itwinjs-core/blob/master/core/backend/src/IModelDb.ts#L2779
687
+ // Code class constructor trims white spaces from code value.
688
+ // Custom implementation of queryElementIdByCode() was added to support querying elements with code values that have trailing whitespaces.
689
+ // It mimicks 4.x implementation: https://github.com/iTwin/itwinjs-core/blob/9c8b394ec3878a39764be81f928fd8b0b9115d31/core/backend/src/IModelDb.ts#L1882
690
+ async queryElementIdByCode(iModel, code) {
691
+ if (core_bentley_1.Id64.isInvalid(code.spec))
692
+ core_bentley_1.ITwinError.throwError({
693
+ iTwinErrorId: {
694
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
695
+ key: IModelTransformerError_1.IModelTransformerError.InvalidCode,
696
+ },
697
+ message: "Invalid CodeSpec",
698
+ });
699
+ if (code.value === undefined)
700
+ core_bentley_1.ITwinError.throwError({
701
+ iTwinErrorId: {
702
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
703
+ key: IModelTransformerError_1.IModelTransformerError.InvalidCode,
704
+ },
705
+ message: "Invalid Code",
706
+ });
707
+ const query = "SELECT ECInstanceId FROM BisCore:Element WHERE CodeSpec.Id=? AND CodeScope.Id=? AND CodeValue=?";
708
+ const queryBinder = new core_common_1.QueryBinder()
709
+ .bindId(1, code.spec)
710
+ .bindId(2, core_bentley_1.Id64.fromString(code.scope))
711
+ .bindString(3, code.value);
712
+ const queryReader = iModel.createQueryReader(query, queryBinder, {
713
+ usePrimaryConn: true,
714
+ });
715
+ return (await queryReader.step()) ? queryReader.current[0] : undefined;
716
+ }
1188
717
  /** Override of [IModelExportHandler.onExportElement]($transformer) that imports an element into the target iModel when it is exported from the source iModel.
1189
718
  * This override calls [[onTransformElement]] and then [IModelImporter.importElement]($transformer) to update the target iModel.
1190
719
  */
1191
- onExportElement(sourceElement) {
1192
- let targetElementId;
720
+ async onExportElement(sourceElement) {
721
+ let targetElementId = core_bentley_1.Id64.invalid;
1193
722
  let targetElementProps;
1194
723
  if (this._options.wasSourceIModelCopiedToTarget) {
1195
724
  targetElementId = sourceElement.id;
@@ -1198,14 +727,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1198
727
  }
1199
728
  else {
1200
729
  targetElementId = this.context.findTargetElementId(sourceElement.id);
1201
- targetElementProps = this.onTransformElement(sourceElement);
730
+ targetElementProps = await this.onTransformElement(sourceElement);
1202
731
  }
1203
732
  // if an existing remapping was not yet found, check by FederationGuid
1204
733
  if (this.context.isBetweenIModels &&
1205
734
  !core_bentley_1.Id64.isValid(targetElementId) &&
1206
735
  sourceElement.federationGuid !== undefined) {
1207
736
  targetElementId =
1208
- this._queryElemIdByFedGuid(this.targetDb, sourceElement.federationGuid) ?? core_bentley_1.Id64.invalid;
737
+ this.targetDb.elements.getIdFromFederationGuid(sourceElement.federationGuid) ?? core_bentley_1.Id64.invalid;
1209
738
  if (core_bentley_1.Id64.isValid(targetElementId))
1210
739
  this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found
1211
740
  }
@@ -1215,13 +744,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1215
744
  // respond the same way to undefined code value as the @see Code class, but don't use that class because it trims
1216
745
  // whitespace from the value, and there are iModels out there with untrimmed whitespace that we ought not to trim
1217
746
  targetElementProps.code.value = targetElementProps.code.value ?? "";
1218
- const maybeTargetElementId = this.targetDb.elements.queryElementIdByCode(targetElementProps.code);
747
+ const maybeTargetElementId = await this.queryElementIdByCode(this.targetDb, targetElementProps.code);
1219
748
  if (undefined !== maybeTargetElementId) {
1220
749
  const maybeTargetElem = this.targetDb.elements.getElement(maybeTargetElementId);
1221
750
  if (maybeTargetElem.classFullName === targetElementProps.classFullName) {
1222
751
  // ensure code remapping doesn't change the target class
1223
752
  targetElementId = maybeTargetElementId;
1224
753
  this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found by Code
754
+ this._targetElementIdsRemappedByCode.add(targetElementId);
1225
755
  }
1226
756
  else {
1227
757
  targetElementProps.code = core_common_1.Code.createEmpty(); // clear out invalid code
@@ -1232,7 +762,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1232
762
  core_bentley_1.Logger.logTrace(loggerCategory, `Skipping unchanged element (${sourceElement.id}, ${sourceElement.getDisplayLabel()}).`);
1233
763
  return;
1234
764
  }
1235
- if (!this.doAllReferencesExistInTarget(sourceElement)) {
765
+ if (!(await this.doAllReferencesExistInTarget(sourceElement))) {
1236
766
  this._partiallyCommittedElementIds.add(sourceElement.id);
1237
767
  }
1238
768
  // targetElementId will be valid (indicating update) or undefined (indicating insert)
@@ -1243,7 +773,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1243
773
  const isValid = core_bentley_1.Id64.isValid(targetElementId);
1244
774
  if (isValid && targetElementId !== sourceElement.id) {
1245
775
  // Element found with different id
1246
- throw new Error(`Element id(${sourceElement.id}) cannot be preserved. Found a different mapping(${targetElementId}) from source element`);
776
+ core_bentley_1.ITwinError.throwError({
777
+ iTwinErrorId: {
778
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
779
+ key: IModelTransformerError_1.IModelTransformerError.ElementIdNotPreservable,
780
+ },
781
+ message: `Element id(${sourceElement.id}) cannot be preserved. Found a different mapping(${targetElementId}) from source element`,
782
+ });
1247
783
  }
1248
784
  else if (isValid && targetElementId === sourceElement.id) {
1249
785
  // targetElementId is valid (indicating update)
@@ -1254,7 +790,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1254
790
  // if we don't find mapping for source element in target(invalid) but another element with source id exists in target
1255
791
  if (sourceInTargetElemProps) {
1256
792
  // Element id is already taken by another element
1257
- throw new Error(`Element id(${sourceElement.id}) cannot be preserved. An unrelated element in the target already uses id: ${sourceElement.id}`);
793
+ core_bentley_1.ITwinError.throwError({
794
+ iTwinErrorId: {
795
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
796
+ key: IModelTransformerError_1.IModelTransformerError.ElementIdNotPreservable,
797
+ },
798
+ message: `Element id(${sourceElement.id}) cannot be preserved. An unrelated element in the target already uses id: ${sourceElement.id}`,
799
+ });
1258
800
  }
1259
801
  else {
1260
802
  // Element id in target is available to be remapped
@@ -1263,10 +805,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1263
805
  }
1264
806
  }
1265
807
  if (!this._options.wasSourceIModelCopiedToTarget) {
1266
- this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
808
+ await this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
1267
809
  }
1268
810
  if (targetElementProps.id === undefined) {
1269
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "targetElementProps.id should be assigned by importElement");
811
+ throw new Error("targetElementProps.id should be assigned by importElement");
1270
812
  }
1271
813
  this.context.remapElement(sourceElement.id, targetElementProps.id);
1272
814
  // the transformer does not currently 'split' or 'join' any elements, therefore, it does not
@@ -1276,39 +818,43 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1276
818
  // physical consolidation is an example of a 'joining' transform
1277
819
  // FIXME: verify at finalization time that we don't lose provenance on new elements
1278
820
  // FIXME: make public and improve `initElementProvenance` API for usage by consolidators
821
+ const provenanceDb = await this.getProvenanceDb();
822
+ const provenanceEditTxn = await this._provenanceManager.getProvenanceEditTxn();
1279
823
  if (!this._options.noProvenance) {
1280
- let provenance = this._options.forceExternalSourceAspectProvenance ||
824
+ const provenance = this._options.forceExternalSourceAspectProvenance ||
1281
825
  this._elementsWithExplicitlyTrackedProvenance.has(sourceElement.id)
1282
826
  ? undefined
1283
827
  : sourceElement.federationGuid;
1284
828
  if (!provenance) {
1285
- const aspectProps = this.initElementProvenance(sourceElement.id, targetElementProps.id);
1286
- const foundEsaProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, aspectProps);
829
+ const aspectProps = await this.initElementProvenance(sourceElement.id, targetElementProps.id);
830
+ const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
1287
831
  if (foundEsaProps === undefined)
1288
- aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
832
+ aspectProps.id = provenanceEditTxn.insertAspect(aspectProps);
1289
833
  else {
1290
834
  // Since initElementProvenance sets a property 'version' on the aspectProps that we wish to persist in the provenanceDb, only grab the id from the foundEsaProps.
1291
835
  aspectProps.id = foundEsaProps.aspectId;
1292
- this.provenanceDb.elements.updateAspect(aspectProps);
836
+ provenanceEditTxn.updateAspect(aspectProps);
1293
837
  }
1294
- provenance = aspectProps;
1295
838
  }
1296
- this.markLastProvenance(provenance, { isRelationship: false });
1297
839
  }
1298
840
  }
1299
841
  /** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
1300
842
  * This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
1301
843
  */
1302
- onDeleteElement(sourceElementId) {
844
+ async onDeleteElement(sourceElementId) {
1303
845
  const targetElementId = this.context.findTargetElementId(sourceElementId);
1304
846
  if (core_bentley_1.Id64.isValidId64(targetElementId)) {
1305
- this.importer.deleteElement(targetElementId);
847
+ // Skip deletion if new / updated source element was remapped to it by Code during
848
+ // this transformation pass.
849
+ if (!this._targetElementIdsRemappedByCode.has(targetElementId)) {
850
+ await this.importer.deleteElement(targetElementId);
851
+ }
1306
852
  }
1307
853
  }
1308
854
  /** Override of [IModelExportHandler.onExportModel]($transformer) that is called when a Model should be exported from the source iModel.
1309
855
  * This override calls [[onTransformModel]] and then [IModelImporter.importModel]($transformer) to update the target iModel.
1310
856
  */
1311
- onExportModel(sourceModel) {
857
+ async onExportModel(sourceModel) {
1312
858
  if (this._options.skipPropagateChangesToRootElements &&
1313
859
  core_common_1.IModel.repositoryModelId === sourceModel.id)
1314
860
  return; // The RepositoryModel should not be directly imported
@@ -1319,16 +865,27 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1319
865
  if (isRemappedRootSubject)
1320
866
  return;
1321
867
  const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
1322
- this.importer.importModel(targetModelProps);
868
+ await this.importer.importModel(targetModelProps);
869
+ if (targetModelProps.id === undefined) {
870
+ throw new Error("targetModelProps.id should be assigned by now");
871
+ }
872
+ this._targetModelsImportedInCurrentTransform.add(targetModelProps.id);
1323
873
  }
1324
874
  /** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
1325
- onDeleteModel(sourceModelId) {
875
+ async onDeleteModel(sourceModelId) {
1326
876
  // It is possible and apparently occasionally sensical to delete a model without deleting its underlying element.
1327
877
  // - If only the model is deleted, [[initFromExternalSourceAspects]] will have already remapped the underlying element since it still exists.
1328
878
  // - If both were deleted, [[remapDeletedSourceEntities]] will find and remap the deleted element making this operation valid
1329
879
  const targetModelId = this.context.findTargetElementId(sourceModelId);
1330
880
  if (!core_bentley_1.Id64.isValidId64(targetModelId))
1331
881
  return;
882
+ // This handles cases where model with a partition element was remapped to
883
+ // new element by code value after recreation.
884
+ if (this._targetElementIdsRemappedByCode.has(targetModelId) &&
885
+ this._targetModelsImportedInCurrentTransform.has(targetModelId)) {
886
+ 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.`);
887
+ return;
888
+ }
1332
889
  const sql = `
1333
890
  SELECT 1
1334
891
  FROM bis.DefinitionPartition
@@ -1339,19 +896,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1339
896
  WHERE ECInstanceId=:targetModelId
1340
897
  `;
1341
898
  if (this.exporter.sourceDbChanges?.element.deleteIds.has(sourceModelId)) {
1342
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1343
- const isDefinitionPartition = this.targetDb.withPreparedStatement(sql, (stmt) => {
1344
- stmt.bindId("targetModelId", targetModelId);
1345
- const val = stmt.step();
1346
- switch (val) {
1347
- case core_bentley_1.DbResult.BE_SQLITE_ROW:
1348
- return true;
1349
- case core_bentley_1.DbResult.BE_SQLITE_DONE:
1350
- return false;
1351
- default:
1352
- (0, core_bentley_1.assert)(false, `unexpected db result: '${JSON.stringify(stmt)}'`);
1353
- }
899
+ const params = new core_common_1.QueryBinder().bindId("targetModelId", targetModelId);
900
+ const reader = this.targetDb.createQueryReader(sql, params, {
901
+ usePrimaryConn: true,
1354
902
  });
903
+ const isDefinitionPartition = await reader.step();
1355
904
  if (isDefinitionPartition) {
1356
905
  // Skipping model deletion because model's partition will also be deleted.
1357
906
  // It expects that model will be present and will fail if it's missing.
@@ -1360,7 +909,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1360
909
  }
1361
910
  }
1362
911
  try {
1363
- this.importer.deleteModel(targetModelId);
912
+ await this.importer.deleteModel(targetModelId);
1364
913
  }
1365
914
  catch (error) {
1366
915
  const isDeletionProhibitedErr = error instanceof core_common_1.IModelError &&
@@ -1388,7 +937,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1388
937
  */
1389
938
  async processModel(sourceModeledElementId) {
1390
939
  await this.initialize();
1391
- return this.exporter.exportModel(sourceModeledElementId);
940
+ return this.processScopedElementExport(async () => this.exporter.exportModel(sourceModeledElementId));
1392
941
  }
1393
942
  /** Cause the model contents to be exported from the source iModel and imported into the target iModel.
1394
943
  * @param sourceModelId Import the contents of this model from the source IModelDb.
@@ -1400,48 +949,31 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1400
949
  await this.initialize();
1401
950
  this.targetDb.models.getModel(targetModelId); // throws if Model does not exist
1402
951
  this.context.remapElement(sourceModelId, targetModelId); // set remapping in case importModelContents is called directly
1403
- return this.exporter.exportModelContents(sourceModelId, elementClassFullName);
952
+ return this.processScopedElementExport(async () => this.exporter.exportModelContents(sourceModelId, elementClassFullName));
1404
953
  }
1405
954
  /** Cause all sub-models that recursively descend from the specified Subject to be exported from the source iModel and imported into the target iModel. */
1406
955
  async processSubjectSubModels(sourceSubjectId) {
1407
956
  await this.initialize();
1408
957
  // import DefinitionModels first
1409
958
  const childDefinitionPartitionSql = `SELECT ECInstanceId FROM ${core_backend_1.DefinitionPartition.classFullName} WHERE Parent.Id=:subjectId`;
1410
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1411
- await this.sourceDb.withPreparedStatement(childDefinitionPartitionSql,
1412
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1413
- async (statement) => {
1414
- statement.bindId("subjectId", sourceSubjectId);
1415
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1416
- await this.processModel(statement.getValue(0).getId());
1417
- }
1418
- });
959
+ const params = new core_common_1.QueryBinder().bindId("subjectId", sourceSubjectId);
960
+ for await (const row of this.sourceDb.createQueryReader(childDefinitionPartitionSql, params, { usePrimaryConn: true })) {
961
+ await this.processModel(row.id);
962
+ }
1419
963
  // import other partitions next
1420
964
  const childPartitionSql = `SELECT ECInstanceId FROM ${core_backend_1.InformationPartitionElement.classFullName} WHERE Parent.Id=:subjectId`;
1421
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1422
- await this.sourceDb.withPreparedStatement(childPartitionSql,
1423
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1424
- async (statement) => {
1425
- statement.bindId("subjectId", sourceSubjectId);
1426
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1427
- const modelId = statement.getValue(0).getId();
1428
- const model = this.sourceDb.models.getModel(modelId);
1429
- if (!(model instanceof core_backend_1.DefinitionModel)) {
1430
- await this.processModel(modelId);
1431
- }
965
+ for await (const row of this.sourceDb.createQueryReader(childPartitionSql, params, { usePrimaryConn: true })) {
966
+ const modelId = row.id;
967
+ const model = this.sourceDb.models.getModel(modelId);
968
+ if (!(model instanceof core_backend_1.DefinitionModel)) {
969
+ await this.processModel(modelId);
1432
970
  }
1433
- });
971
+ }
1434
972
  // recurse into child Subjects
1435
973
  const childSubjectSql = `SELECT ECInstanceId FROM ${core_backend_1.Subject.classFullName} WHERE Parent.Id=:subjectId`;
1436
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1437
- await this.sourceDb.withPreparedStatement(childSubjectSql,
1438
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1439
- async (statement) => {
1440
- statement.bindId("subjectId", sourceSubjectId);
1441
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1442
- await this.processSubjectSubModels(statement.getValue(0).getId());
1443
- }
1444
- });
974
+ for await (const row of this.sourceDb.createQueryReader(childSubjectSql, params, { usePrimaryConn: true })) {
975
+ await this.processSubjectSubModels(row.id);
976
+ }
1445
977
  }
1446
978
  /** Transform the specified sourceModel into ModelProps for the target iModel.
1447
979
  * @param sourceModel The Model from the source iModel to be transformed.
@@ -1458,104 +990,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1458
990
  };
1459
991
  targetModelProps.id = targetModeledElementId;
1460
992
  if (targetModelProps.parentModel === undefined) {
1461
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "targetElementProps must have a defined parentModel");
993
+ core_bentley_1.ITwinError.throwError({
994
+ iTwinErrorId: {
995
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
996
+ key: IModelTransformerError_1.IModelTransformerError.ParentModelRequired,
997
+ },
998
+ message: "targetElementProps must have a defined parentModel",
999
+ });
1462
1000
  }
1463
1001
  targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
1464
1002
  return targetModelProps;
1465
1003
  }
1466
- /**
1467
- * Called at the end of a transformation,
1468
- * updates the target scope element to say that transformation up through the
1469
- * source's changeset has been performed. Also stores all changesets that occurred
1470
- * during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
1471
- *
1472
- * You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
1473
- * It is public for unsupported use cases of custom synchronization transforms.
1474
- * @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
1475
- * unless the `initializeReverseSyncVersion` option is set to `true`
1476
- *
1477
- * The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
1478
- * 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.
1479
- * 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.
1480
- *
1481
- * Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
1482
- * 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.
1483
- */
1484
- updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
1485
- const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
1486
- this._sourceChangeDataState !== "has-changes";
1487
- if (shouldSkipSyncVersionUpdate)
1488
- return;
1489
- nodeAssert(this._targetScopeProvenanceProps);
1490
- const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
1491
- const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
1492
- if (this.isReverseSynchronization) {
1493
- const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
1494
- core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
1495
- this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
1496
- sourceVersion;
1497
- }
1498
- else {
1499
- core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
1500
- this._targetScopeProvenanceProps.version = sourceVersion;
1501
- // save reverse sync version
1502
- if (initializeReverseSyncVersion) {
1503
- core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
1504
- this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
1505
- targetVersion;
1506
- }
1507
- }
1508
- if (this._options.argsForProcessChanges ||
1509
- (this._startingChangesetIndices && initializeReverseSyncVersion)) {
1510
- nodeAssert(this.targetDb.changeset.index !== undefined &&
1511
- this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
1512
- // Store in a local variable, so typescript knows it's defined (due to the assert above)
1513
- const startingChangesetIndices = this._startingChangesetIndices;
1514
- const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
1515
- core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
1516
- core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
1517
- const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
1518
- const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
1519
- // Determine which keys to clear and update based on the synchronization direction
1520
- let syncChangesetsToClearKey;
1521
- let syncChangesetsToUpdateKey;
1522
- if (this.isReverseSynchronization) {
1523
- syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
1524
- syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
1525
- }
1526
- else {
1527
- syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
1528
- syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
1529
- }
1530
- // NOTE that as documented in [[processChanges]], this assumes that right after
1531
- // transformation finalization, the work will be saved immediately, otherwise we've
1532
- // just marked this changeset as a synchronization to ignore, and the user can add other
1533
- // stuff to it which would break future synchronizations
1534
- for (let i = startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
1535
- jsonProps[syncChangesetsToUpdateKey].push(i);
1536
- // Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
1537
- jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
1538
- return csIndex > startingChangesetIndices.source;
1539
- });
1540
- // if reverse sync then we may have received provenance changes which should be marked as sync changes
1541
- if (this.isReverseSynchronization) {
1542
- nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
1543
- for (let i = startingChangesetIndices.source + 1; i <= this.sourceDb.changeset.index + 1; i++)
1544
- jsonProps.pendingReverseSyncChangesetIndices.push(i);
1545
- }
1546
- core_bentley_1.Logger.logTrace(loggerCategory, `new pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
1547
- core_bentley_1.Logger.logTrace(loggerCategory, `new pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
1548
- }
1549
- this.provenanceDb.elements.updateAspect({
1550
- ...this._targetScopeProvenanceProps,
1551
- jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
1552
- });
1553
- this.clearCachedSynchronizationVersion();
1554
- }
1555
1004
  // FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
1556
- finalizeTransformation() {
1005
+ async finalizeTransformation() {
1557
1006
  this.importer.finalize();
1558
- this.updateSynchronizationVersion({
1007
+ await this.updateSynchronizationVersion({
1559
1008
  initializeReverseSyncVersion: this._isProvenanceInitTransform,
1560
1009
  });
1561
1010
  // TODO: ignore if we remove change cache usage
@@ -1582,39 +1031,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1582
1031
  /** Override of [IModelExportHandler.shouldExportRelationship]($transformer) that is called to determine if a [Relationship]($backend) should be exported.
1583
1032
  * @note Reaching this point means that the relationship has passed the standard exclusion checks in [IModelExporter]($transformer).
1584
1033
  */
1585
- shouldExportRelationship(_sourceRelationship) {
1034
+ async shouldExportRelationship(_sourceRelationship) {
1586
1035
  return true;
1587
1036
  }
1588
1037
  /** Override of [IModelExportHandler.onExportRelationship]($transformer) that imports a relationship into the target iModel when it is exported from the source iModel.
1589
1038
  * This override calls [[onTransformRelationship]] and then [IModelImporter.importRelationship]($transformer) to update the target iModel.
1590
1039
  */
1591
- onExportRelationship(sourceRelationship) {
1592
- const sourceFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.sourceId);
1593
- const targetFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.targetId);
1040
+ async onExportRelationship(sourceRelationship) {
1041
+ const sourceFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.sourceId);
1042
+ const targetFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.targetId);
1594
1043
  const targetRelationshipProps = this.onTransformRelationship(sourceRelationship);
1595
- const targetRelationshipInstanceId = this.importer.importRelationship(targetRelationshipProps);
1044
+ const targetRelationshipInstanceId = await this.importer.importRelationship(targetRelationshipProps);
1045
+ const provenanceDb = await this.getProvenanceDb();
1046
+ const provenanceEditTxn = await this._provenanceManager.getProvenanceEditTxn();
1596
1047
  if (!this._options.noProvenance &&
1597
1048
  core_bentley_1.Id64.isValid(targetRelationshipInstanceId)) {
1598
- let provenance = !this._options.forceExternalSourceAspectProvenance
1599
- ? sourceFedGuid && targetFedGuid && `${sourceFedGuid}/${targetFedGuid}`
1600
- : undefined;
1601
- if (!provenance) {
1602
- const aspectProps = this.initRelationshipProvenance(sourceRelationship, targetRelationshipInstanceId);
1603
- const foundEsaProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, aspectProps);
1049
+ const needsEsaProvenance = !this._options
1050
+ .forceExternalSourceAspectProvenance
1051
+ ? !(sourceFedGuid && targetFedGuid)
1052
+ : true;
1053
+ if (needsEsaProvenance) {
1054
+ const aspectProps = await this._provenanceManager.initRelationshipProvenance(sourceRelationship.id, targetRelationshipInstanceId, this._forceOldRelationshipProvenanceMethod);
1055
+ const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
1604
1056
  // 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).
1605
1057
  if (undefined === foundEsaProps) {
1606
- aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
1058
+ aspectProps.id = provenanceEditTxn.insertAspect(aspectProps);
1607
1059
  }
1608
- provenance = aspectProps;
1609
1060
  }
1610
- this.markLastProvenance(provenance, { isRelationship: true });
1611
1061
  }
1612
1062
  }
1613
1063
  /** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
1614
1064
  * This override propagates the delete to the target iModel via [IModelImporter.deleteRelationship]($transformer).
1615
1065
  */
1616
- onDeleteRelationship(sourceRelInstanceId) {
1617
- nodeAssert(this._deletedSourceRelationshipData, "should be defined at initialization by now");
1066
+ async onDeleteRelationship(sourceRelInstanceId) {
1067
+ (0, node_assert_1.strict)(this._deletedSourceRelationshipData, "should be defined at initialization by now");
1618
1068
  const deletedRelData = this._deletedSourceRelationshipData.get(sourceRelInstanceId);
1619
1069
  if (!deletedRelData) {
1620
1070
  // this can occur if both the source and target deleted it
@@ -1627,14 +1077,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1627
1077
  targetId: deletedRelData.targetIdInTarget,
1628
1078
  })?.id;
1629
1079
  if (id) {
1630
- this.importer.deleteRelationship({
1080
+ await this.importer.deleteRelationship({
1631
1081
  id,
1632
1082
  classFullName: deletedRelData.classFullName,
1633
1083
  });
1634
1084
  }
1635
1085
  if (deletedRelData.provenanceAspectId) {
1636
1086
  try {
1637
- this.provenanceDb.elements.deleteAspect(deletedRelData.provenanceAspectId);
1087
+ (await this._provenanceManager.getProvenanceEditTxn()).deleteAspect(deletedRelData.provenanceAspectId);
1638
1088
  }
1639
1089
  catch (error) {
1640
1090
  // This aspect may no longer exist if it was deleted at some other point during the transformation. This is fine.
@@ -1645,51 +1095,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1645
1095
  }
1646
1096
  }
1647
1097
  _yieldManager = new core_bentley_1.YieldManager();
1648
- /** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
1649
- * @deprecated in 1.x. Don't use this anymore
1650
- * @see [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided.
1651
- * @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.
1652
- * @throws [[IModelError]] If the required provenance information is not available to detect deletes.
1653
- */
1654
- async detectRelationshipDeletes() {
1655
- if (this.isReverseSynchronization) {
1656
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
1657
- }
1658
- const aspectDeleteIds = [];
1659
- const sql = `
1660
- SELECT ECInstanceId, Identifier, JsonProperties
1661
- FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect
1662
- WHERE aspect.Scope.Id=:scopeId
1663
- AND aspect.Kind=:kind
1664
- `;
1665
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1666
- await this.targetDb.withPreparedStatement(sql,
1667
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
1668
- async (statement) => {
1669
- statement.bindId("scopeId", this.targetScopeElementId);
1670
- statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
1671
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1672
- const sourceRelInstanceId = core_bentley_1.Id64.fromJSON(statement.getValue(1).getString());
1673
- if (undefined ===
1674
- this.sourceDb.relationships.tryGetInstanceProps(core_backend_1.ElementRefersToElements.classFullName, sourceRelInstanceId)) {
1675
- // this function exists only to support some in-imodel transformations, which must
1676
- // use the old (external source aspect) provenance method anyway so we don't need to support
1677
- // new provenance
1678
- const json = JSON.parse(statement.getValue(2).getString());
1679
- const targetRelInstanceId = json.targetRelInstanceId ?? json.provenanceRelInstanceId;
1680
- if (targetRelInstanceId) {
1681
- this.importer.deleteRelationship({
1682
- id: targetRelInstanceId,
1683
- classFullName: core_backend_1.ElementRefersToElements.classFullName,
1684
- });
1685
- }
1686
- aspectDeleteIds.push(statement.getValue(0).getId());
1687
- }
1688
- await this._yieldManager.allowYield();
1689
- }
1690
- });
1691
- this.targetDb.elements.deleteAspect(aspectDeleteIds);
1692
- }
1693
1098
  /** Transform the specified sourceRelationship into RelationshipProps for the target iModel.
1694
1099
  * @param sourceRelationship The Relationship from the source iModel to be transformed.
1695
1100
  * @returns RelationshipProps for the target iModel.
@@ -1700,45 +1105,61 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1700
1105
  targetRelationshipProps.sourceId = this.context.findTargetElementId(sourceRelationship.sourceId);
1701
1106
  targetRelationshipProps.targetId = this.context.findTargetElementId(sourceRelationship.targetId);
1702
1107
  // TODO: move to cloneRelationship in IModelCloneContext
1703
- sourceRelationship.forEachProperty((propertyName, propertyMetaData) => {
1704
- if (core_common_1.PrimitiveTypeCode.Long === propertyMetaData.primitiveType &&
1705
- "Id" === propertyMetaData.extendedType) {
1706
- targetRelationshipProps[propertyName] =
1707
- this.context.findTargetElementId(sourceRelationship.asAny[propertyName]);
1108
+ const relationshipClass = sourceRelationship.iModel.schemaContext.getSchemaItemSync(sourceRelationship.schemaItemKey, ECSchemaMetaData.RelationshipClass);
1109
+ (0, core_bentley_1.assert)(relationshipClass !== undefined, `Cannot get metadata for ${sourceRelationship.classFullName}.`);
1110
+ for (const property of relationshipClass.getPropertiesSync()) {
1111
+ const propertyName = property.name;
1112
+ if (property.isPrimitive() && "Id" === property.extendedTypeName) {
1113
+ targetRelationshipProps[core_common_1.ECJsNames.toJsName(propertyName)] =
1114
+ this.context.findTargetElementId(sourceRelationship.asAny[core_common_1.ECJsNames.toJsName(propertyName)]);
1708
1115
  }
1709
- });
1116
+ }
1710
1117
  return targetRelationshipProps;
1711
1118
  }
1712
- shouldExportElementAspect(aspect) {
1713
- // This override is needed to ensure that aspects are not exported if their element is not exported.
1714
- // This is needed in case DetachedExportElementAspectsStrategy is used.
1119
+ async shouldExportElementAspect(aspect) {
1120
+ // An aspect cannot be exported when its owner has no source-to-target
1121
+ // mapping.
1715
1122
  return this.context.findTargetElementId(aspect.element.id) !== core_bentley_1.Id64.invalid;
1716
1123
  }
1124
+ async prepareElementAspects(excludedElementAspectClassFullNames, elementIds) {
1125
+ if (!this.exporter.visitElements)
1126
+ return;
1127
+ if (elementIds === undefined)
1128
+ return;
1129
+ const targetElementIds = new Set();
1130
+ for (const sourceElementId of elementIds) {
1131
+ const targetElementId = this.context.findTargetElementId(sourceElementId);
1132
+ if (core_bentley_1.Id64.isValid(targetElementId)) {
1133
+ targetElementIds.add(targetElementId);
1134
+ }
1135
+ }
1136
+ await this.importer.elementAspectCleanup.delete(targetElementIds, excludedElementAspectClassFullNames, this.targetScopeElementId);
1137
+ }
1717
1138
  /** Override of [IModelExportHandler.onExportElementUniqueAspect]($transformer) that imports an ElementUniqueAspect into the target iModel when it is exported from the source iModel.
1718
1139
  * This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
1719
1140
  */
1720
- onExportElementUniqueAspect(sourceAspect) {
1721
- const targetAspectProps = this.onTransformElementAspect(sourceAspect);
1722
- if (!this.doAllReferencesExistInTarget(sourceAspect)) {
1141
+ async onExportElementUniqueAspect(sourceAspect) {
1142
+ const targetAspectProps = await this.onTransformElementAspect(sourceAspect);
1143
+ if (!(await this.doAllReferencesExistInTarget(sourceAspect))) {
1723
1144
  this._partiallyCommittedAspectIds.add(sourceAspect.id);
1724
1145
  }
1725
- const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
1146
+ const targetId = await this.importer.importElementUniqueAspect(targetAspectProps);
1726
1147
  this.context.remapElementAspect(sourceAspect.id, targetId);
1727
1148
  }
1728
1149
  /** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
1729
1150
  * This override calls [[onTransformElementAspect]] for each ElementMultiAspect and then [IModelImporter.importElementMultiAspects]($transformer) to update the target iModel.
1730
1151
  * @note ElementMultiAspects are handled as a group to make it easier to differentiate between insert, update, and delete.
1731
1152
  */
1732
- onExportElementMultiAspects(sourceAspects) {
1153
+ async onExportElementMultiAspects(sourceAspects) {
1733
1154
  // Transform source ElementMultiAspects into target ElementAspectProps
1734
- const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA));
1735
- sourceAspects.forEach((a) => {
1736
- if (!this.doAllReferencesExistInTarget(a)) {
1155
+ const targetAspectPropsArray = sourceAspects.map(async (srcA) => this.onTransformElementAspect(srcA));
1156
+ for (const a of sourceAspects) {
1157
+ if (!(await this.doAllReferencesExistInTarget(a))) {
1737
1158
  this._partiallyCommittedAspectIds.add(a.id);
1738
1159
  }
1739
- });
1160
+ }
1740
1161
  // const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
1741
- const targetIds = this.importer.importElementMultiAspects(targetAspectPropsArray, (a) => {
1162
+ const targetIds = await this.importer.importElementMultiAspects(await Promise.all(targetAspectPropsArray), (a) => {
1742
1163
  const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
1743
1164
  a.scope?.id === this.targetScopeElementId;
1744
1165
  return (!this._options.includeSourceProvenance ||
@@ -1753,8 +1174,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1753
1174
  * @returns ElementAspectProps for the target iModel.
1754
1175
  * @note A subclass can override this method to provide custom transform behavior.
1755
1176
  */
1756
- onTransformElementAspect(sourceElementAspect) {
1757
- const targetElementAspectProps = this.context.cloneElementAspect(sourceElementAspect);
1177
+ async onTransformElementAspect(sourceElementAspect) {
1178
+ const targetElementAspectProps = await this._cloneContext.cloneElementAspect(sourceElementAspect);
1758
1179
  return targetElementAspectProps;
1759
1180
  }
1760
1181
  /** The directory where schemas will be exported, a random temporary directory */
@@ -1762,7 +1183,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1762
1183
  /** Override of [IModelExportHandler.shouldExportSchema]($transformer) that is called to determine if a schema should be exported
1763
1184
  * @note the default behavior doesn't import schemas older than those already in the target
1764
1185
  */
1765
- shouldExportSchema(schemaKey) {
1186
+ async shouldExportSchema(schemaKey) {
1766
1187
  const versionInTarget = this.targetDb.querySchemaVersion(schemaKey.name);
1767
1188
  if (versionInTarget === undefined)
1768
1189
  return true;
@@ -1793,10 +1214,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1793
1214
  // and that's on the scale of at least petabytes. `Map.prototype.size` shouldn't return floating points, and even
1794
1215
  // if they do they're in scientific notation, size bound and contain no invalid windows path chars
1795
1216
  schemaFileName = `${schema.name.slice(0, 100)}${this._longNamedSchemasMap.size}${ext}`;
1796
- nodeAssert(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
1217
+ (0, node_assert_1.strict)(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
1797
1218
  this._longNamedSchemasMap.set(schema.name, schemaFileName);
1798
1219
  }
1799
- /* eslint-disable-next-line @typescript-eslint/no-deprecated */
1800
1220
  this.sourceDb.exportSchema({
1801
1221
  schemaName: schema.name,
1802
1222
  outputDirectory: this._schemaExportDir,
@@ -1849,8 +1269,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1849
1269
  return this.exporter.exportFonts();
1850
1270
  }
1851
1271
  /** Override of [IModelExportHandler.onExportFont]($transformer) that imports a font into the target iModel when it is exported from the source iModel. */
1852
- onExportFont(font, _isUpdate) {
1853
- this.context.importFont(font.id);
1272
+ async onExportFont(font, _isUpdate) {
1273
+ this._cloneContext.importFont(font.id);
1854
1274
  }
1855
1275
  /** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
1856
1276
  * @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
@@ -1869,12 +1289,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1869
1289
  /** Override of [IModelExportHandler.shouldExportCodeSpec]($transformer) that is called to determine if a CodeSpec should be exported from the source iModel.
1870
1290
  * @note Reaching this point means that the CodeSpec has passed the standard exclusion checks in [IModelExporter]($transformer).
1871
1291
  */
1872
- shouldExportCodeSpec(_sourceCodeSpec) {
1292
+ async shouldExportCodeSpec(_sourceCodeSpec) {
1873
1293
  return true;
1874
1294
  }
1875
1295
  /** Override of [IModelExportHandler.onExportCodeSpec]($transformer) that imports a CodeSpec into the target iModel when it is exported from the source iModel. */
1876
- onExportCodeSpec(sourceCodeSpec) {
1877
- this.context.importCodeSpec(sourceCodeSpec.id);
1296
+ async onExportCodeSpec(sourceCodeSpec) {
1297
+ this._cloneContext.importCodeSpec(sourceCodeSpec.id);
1878
1298
  }
1879
1299
  /** Recursively import all Elements and sub-Models that descend from the specified Subject */
1880
1300
  async processSubject(sourceSubjectId, targetSubjectId) {
@@ -1882,16 +1302,19 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1882
1302
  this.sourceDb.elements.getElement(sourceSubjectId, core_backend_1.Subject); // throws if sourceSubjectId is not a Subject
1883
1303
  this.targetDb.elements.getElement(targetSubjectId, core_backend_1.Subject); // throws if targetSubjectId is not a Subject
1884
1304
  this.context.remapElement(sourceSubjectId, targetSubjectId);
1885
- await this.processChildElements(sourceSubjectId);
1886
- await this.processSubjectSubModels(sourceSubjectId);
1887
- this.completePartiallyCommittedElements();
1888
- this.completePartiallyCommittedAspects();
1305
+ await this.processScopedElementExport(async () => {
1306
+ await this.processChildElements(sourceSubjectId);
1307
+ await this.processSubjectSubModels(sourceSubjectId);
1308
+ });
1309
+ await this.completePartiallyCommittedElements();
1310
+ await this.completePartiallyCommittedAspects();
1889
1311
  }
1890
1312
  /** state to prevent reinitialization, @see [[initialize]] */
1891
1313
  _initialized = false;
1892
1314
  _sourceChangeDataState = "uninited";
1893
1315
  /** length === 0 when _changeDataState = "no-change", length > 0 means "has-changes", otherwise undefined */
1894
1316
  _csFileProps = undefined;
1317
+ _deletionRecordsByChangeset;
1895
1318
  /**
1896
1319
  * Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
1897
1320
  * are intending to process changes. Callers may wish to explicitly call initialize if they need to execute code after initialize but before [[process]] is called.
@@ -1901,15 +1324,37 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1901
1324
  async initialize() {
1902
1325
  if (this._initialized)
1903
1326
  return;
1904
- this.initScopeProvenance();
1327
+ this.assertEditTxnActive();
1328
+ await this.initScopeProvenance();
1905
1329
  await this._tryInitChangesetData(this._options.argsForProcessChanges);
1906
- await this.context.initialize();
1907
- // need exporter initialized to do remapdeletedsourceentities.
1908
- await this.exporter.initialize(this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
1330
+ await this._cloneContext.initialize();
1331
+ const exporterInitOptions = await this.getExportInitOpts(this._options.argsForProcessChanges ?? {});
1332
+ await this.initializeChangesetScanAndExporter(exporterInitOptions);
1909
1333
  // 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).
1910
1334
  await this.processChangesets();
1911
1335
  this._initialized = true;
1912
1336
  }
1337
+ async initializeChangesetScanAndExporter(exporterInitOptions) {
1338
+ if (this._csFileProps !== undefined &&
1339
+ this._csFileProps.length > 0 &&
1340
+ this.sourceDb.isBriefcaseDb()) {
1341
+ const changedInstanceIds = this.exporter.sourceDbChanges ??
1342
+ ("changedInstanceIds" in exporterInitOptions
1343
+ ? exporterInitOptions.changedInstanceIds
1344
+ : new IModelExporter_1.ChangedInstanceIds(this.sourceDb));
1345
+ this._deletionRecordsByChangeset = await ChangesetScanner_1.ChangesetScanner.scan(this.sourceDb, this._csFileProps, changedInstanceIds, {
1346
+ populateChangedInstanceIds: this.exporter.sourceDbChanges === undefined &&
1347
+ !("changedInstanceIds" in exporterInitOptions),
1348
+ });
1349
+ await this.exporter.initialize({
1350
+ changedInstanceIds,
1351
+ skipPropagateChangesToRootElements: exporterInitOptions.skipPropagateChangesToRootElements,
1352
+ });
1353
+ }
1354
+ else {
1355
+ await this.exporter.initialize(exporterInitOptions);
1356
+ }
1357
+ }
1913
1358
  /**
1914
1359
  * Reads all the changeset files in the private member of the transformer: _csFileProps
1915
1360
  * and finds the corresponding target entity for any deleted source entities and remaps the sourceId to the targetId.
@@ -1917,12 +1362,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1917
1362
  * @returns void
1918
1363
  */
1919
1364
  async processChangesets() {
1920
- this.forEachTrackedElement((sourceElementId, targetElementId) => {
1365
+ await this._provenanceManager.forEachTrackedElement((sourceElementId, targetElementId) => {
1921
1366
  this.context.remapElement(sourceElementId, targetElementId);
1922
1367
  });
1923
1368
  if (this.exporter.sourceDbChanges)
1924
1369
  await this.addCustomChanges(this.exporter.sourceDbChanges);
1925
- if (this._csFileProps === undefined || this._csFileProps.length === 0) {
1370
+ const csFileProps = this._csFileProps;
1371
+ if (csFileProps === undefined || csFileProps.length === 0) {
1926
1372
  if (this.exporter.sourceDbChanges === undefined ||
1927
1373
  !this.exporter.sourceDbChanges.hasChanges)
1928
1374
  return;
@@ -1930,12 +1376,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1930
1376
  if (this._sourceChangeDataState === "no-changes")
1931
1377
  this._sourceChangeDataState = "has-changes";
1932
1378
  }
1379
+ const deletionRecordsByChangeset = this._deletionRecordsByChangeset;
1380
+ if (deletionRecordsByChangeset === undefined)
1381
+ return;
1933
1382
  const relationshipECClassIdsToSkip = new Set();
1934
- for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)")) {
1383
+ for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)", undefined, { usePrimaryConn: true })) {
1935
1384
  relationshipECClassIdsToSkip.add(row.ECInstanceId);
1936
1385
  }
1937
1386
  const relationshipECClassIds = new Set();
1938
- for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)")) {
1387
+ for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)", undefined, { usePrimaryConn: true })) {
1939
1388
  relationshipECClassIds.add(row.ECInstanceId);
1940
1389
  }
1941
1390
  // For later use when processing deletes.
@@ -1952,46 +1401,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1952
1401
  alreadyImportedModelInserts.add(targetModelId);
1953
1402
  });
1954
1403
  this._deletedSourceRelationshipData = new Map();
1955
- for (const csFile of this._csFileProps ?? []) {
1956
- const csReader = core_backend_1.SqliteChangesetReader.openFile({
1957
- fileName: csFile.pathname,
1958
- db: this.sourceDb,
1959
- disableSchemaCheck: true,
1960
- });
1961
- const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
1962
- const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier(this.sourceDb);
1963
- while (csAdaptor.step()) {
1964
- ecChangeUnifier.appendFrom(csAdaptor);
1965
- }
1966
- const changes = [...ecChangeUnifier.instances];
1404
+ for (const changes of deletionRecordsByChangeset) {
1967
1405
  /** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
1968
1406
  const elemIdToScopeEsa = new Map();
1969
1407
  for (const change of changes) {
1970
- if (change.ECClassId !== undefined &&
1971
- relationshipECClassIdsToSkip.has(change.ECClassId))
1408
+ if (relationshipECClassIdsToSkip.has(change.ecClassId))
1972
1409
  continue;
1973
- const changeType = change.$meta?.op;
1974
- if (changeType === "Deleted" &&
1975
- change?.$meta?.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
1976
- change.Scope.Id === this.targetScopeElementId &&
1977
- change.Kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
1978
- elemIdToScopeEsa.set(change.Element.Id, change);
1410
+ if (change.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
1411
+ change.scopeId === this.targetScopeElementId &&
1412
+ change.kind === core_backend_1.ExternalSourceAspect.Kind.Element &&
1413
+ change.elementId !== undefined) {
1414
+ elemIdToScopeEsa.set(change.elementId, change);
1979
1415
  }
1980
1416
  }
1981
1417
  // Loop to process deletes.
1982
1418
  for (const change of changes) {
1983
- const changeType = change.$meta?.op;
1984
- const ecClassId = change.ECClassId ?? change.$meta?.fallbackClassId;
1985
- if (ecClassId === undefined)
1986
- throw new Error(`ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change?.$meta?.tables}`);
1987
- if (changeType === undefined)
1988
- throw new Error(`ChangeType was undefined for id: ${change.ECInstanceId}.`);
1989
- if (changeType !== "Deleted" ||
1990
- relationshipECClassIdsToSkip.has(ecClassId))
1419
+ if (relationshipECClassIdsToSkip.has(change.ecClassId))
1991
1420
  continue;
1992
- await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
1421
+ await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(change.ecClassId), alreadyImportedElementInserts, alreadyImportedModelInserts);
1993
1422
  }
1994
- csReader.close();
1995
1423
  }
1996
1424
  return;
1997
1425
  }
@@ -2017,16 +1445,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2017
1445
  async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
2018
1446
  // we need a connected iModel with changes to remap elements with deletions
2019
1447
  const notConnectedModel = this.sourceDb.iTwinId === undefined;
2020
- const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index &&
1448
+ const noChanges = (await this.getSynchronizationVersion()).index ===
1449
+ this.sourceDb.changeset.index &&
2021
1450
  (this.exporter.sourceDbChanges === undefined ||
2022
1451
  !this.exporter.sourceDbChanges.hasChanges);
2023
1452
  if (notConnectedModel || noChanges)
2024
1453
  return;
2025
1454
  /**
2026
- * if our ChangedECInstance is in the provenanceDb, then we can use the ids we find in the ChangedECInstance to query for ESAs.
1455
+ * If the deleted source entity is in the provenanceDb, then we can use its ids to query for ESAs.
2027
1456
  * This is because the ESAs are stored on an element Id thats present in the provenanceDb.
2028
1457
  */
2029
- const changeDataInProvenanceDb = this.sourceDb === this.provenanceDb;
1458
+ const changeDataInProvenanceDb = this.sourceDb === (await this.getProvenanceDb());
2030
1459
  const getTargetIdFromSourceId = async (id) => {
2031
1460
  let identifierValue;
2032
1461
  let element;
@@ -2035,18 +1464,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2035
1464
  }
2036
1465
  const fedGuid = isRelationship
2037
1466
  ? element?.federationGuid
2038
- : change.FederationGuid;
1467
+ : change.federationGuid;
2039
1468
  if (changeDataInProvenanceDb) {
2040
1469
  // TODO: clarify what happens if there are multiple (e.g. elements were merged)
2041
1470
  for await (const row of this.sourceDb.createQueryReader("SELECT esa.Identifier FROM bis.ExternalSourceAspect esa WHERE Scope.Id=:scopeId AND Kind=:kind AND Element.Id=:relatedElementId LIMIT 1", core_common_1.QueryBinder.from([
2042
1471
  this.targetScopeElementId,
2043
1472
  core_backend_1.ExternalSourceAspect.Kind.Element,
2044
1473
  id,
2045
- ]))) {
1474
+ ]), { usePrimaryConn: true })) {
2046
1475
  identifierValue = row.Identifier;
2047
1476
  }
2048
1477
  identifierValue =
2049
- identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.Identifier;
1478
+ identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.identifier;
2050
1479
  }
2051
1480
  // Check for targetId by an esa first
2052
1481
  if (changeDataInProvenanceDb && identifierValue) {
@@ -2055,16 +1484,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2055
1484
  }
2056
1485
  // Check for targetId using sourceId's fedguid if we didn't find an esa.
2057
1486
  if (fedGuid) {
2058
- const targetId = this._queryElemIdByFedGuid(this.targetDb, fedGuid);
1487
+ const targetId = this.targetDb.elements.getIdFromFederationGuid(fedGuid);
2059
1488
  return targetId;
2060
1489
  }
2061
1490
  return undefined;
2062
1491
  };
2063
- const changedInstanceId = change.ECInstanceId;
1492
+ const changedInstanceId = change.ecInstanceId;
2064
1493
  if (isRelationship) {
2065
- const sourceIdOfRelationshipInSource = change.SourceECInstanceId;
2066
- const targetIdOfRelationshipInSource = change.TargetECInstanceId;
2067
- const classFullName = change.$meta?.classFullName;
1494
+ const sourceIdOfRelationshipInSource = change.sourceECInstanceId;
1495
+ const targetIdOfRelationshipInSource = change.targetECInstanceId;
1496
+ const classFullName = change.classFullName;
1497
+ if (sourceIdOfRelationshipInSource === undefined ||
1498
+ targetIdOfRelationshipInSource === undefined) {
1499
+ core_bentley_1.ITwinError.throwError({
1500
+ iTwinErrorId: {
1501
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
1502
+ key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
1503
+ },
1504
+ message: `Relationship deletion ${changedInstanceId} is missing an endpoint.`,
1505
+ });
1506
+ }
2068
1507
  const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
2069
1508
  const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
2070
1509
  if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
@@ -2074,8 +1513,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2074
1513
  targetIdInTarget: targetIdOfRelationshipInTarget,
2075
1514
  });
2076
1515
  }
2077
- else if (this.sourceDb === this.provenanceSourceDb) {
2078
- const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
1516
+ else if (this.sourceDb ===
1517
+ (await this._provenanceManager.getProvenanceSourceDb())) {
1518
+ const relProvenance = await this._provenanceManager.queryProvenanceForRelationship(changedInstanceId, {
2079
1519
  classFullName: classFullName ?? "",
2080
1520
  sourceId: sourceIdOfRelationshipInSource,
2081
1521
  targetId: targetIdOfRelationshipInSource,
@@ -2090,8 +1530,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2090
1530
  }
2091
1531
  else {
2092
1532
  let targetId = await getTargetIdFromSourceId(changedInstanceId);
2093
- if (targetId === undefined && this.sourceDb === this.provenanceSourceDb) {
2094
- targetId = this._queryProvenanceForElement(changedInstanceId);
1533
+ if (targetId === undefined &&
1534
+ this.sourceDb ===
1535
+ (await this._provenanceManager.getProvenanceSourceDb())) {
1536
+ targetId =
1537
+ await this._provenanceManager.queryProvenanceForElement(changedInstanceId);
2095
1538
  }
2096
1539
  // since we are processing one changeset at a time, we can see local source deletes
2097
1540
  // of entities that were never synced and can be safely ignored
@@ -2099,7 +1542,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2099
1542
  if (deletionNotInTarget)
2100
1543
  return;
2101
1544
  if (targetId === undefined) {
2102
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "targetId should aquired from source id or element provenance");
1545
+ throw new Error("targetId should be acquired from source id or element provenance");
2103
1546
  }
2104
1547
  this.context.remapElement(changedInstanceId, targetId);
2105
1548
  // If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
@@ -2119,7 +1562,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2119
1562
  this._sourceChangeDataState = "unconnected";
2120
1563
  return;
2121
1564
  }
2122
- const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
1565
+ const syncVersion = await this.getSynchronizationVersion();
1566
+ const noChanges = syncVersion.index === this.sourceDb.changeset.index;
2123
1567
  if (noChanges) {
2124
1568
  this._sourceChangeDataState = "no-changes";
2125
1569
  this._csFileProps = [];
@@ -2128,36 +1572,34 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2128
1572
  const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
2129
1573
  // NOTE: that we do NOT download the changesummary for the last transformed version, we want
2130
1574
  // to ignore those already processed changes
2131
- const startChangesetIndexOrId = startChangeset?.index ??
2132
- startChangeset?.id ??
2133
- this.synchronizationVersion.index + 1;
1575
+ const startChangesetIndexOrId = startChangeset?.index ?? startChangeset?.id ?? syncVersion.index + 1;
2134
1576
  const endChangesetId = this.sourceDb.changeset.id;
2135
1577
  const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
2136
1578
  ? indexOrId
2137
1579
  : core_backend_1.BriefcaseManager.queryChangeset({
2138
1580
  iModelId: this.sourceDb.iModelId,
2139
- // eslint-disable-next-line @typescript-eslint/no-deprecated
2140
1581
  changeset: { id: indexOrId },
2141
1582
  }).then((changeset) => changeset.index)));
2142
- const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
1583
+ const missingChangesets = startChangesetIndex > syncVersion.index + 1;
2143
1584
  if (!this._options.argsForProcessChanges
2144
1585
  ?.ignoreMissingChangesetsInSynchronizations &&
2145
- startChangesetIndex !== this.synchronizationVersion.index + 1 &&
2146
- this.synchronizationVersion.index !== -1) {
2147
- throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
2148
- " startChangesetId should be" +
2149
- " exactly the first changeset *after* the previous synchronization to not miss data." +
2150
- ` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
2151
- ` but the previous synchronization for this targetScopeElement was '${this.synchronizationVersion.id}'` +
2152
- ` which is changeset #${this.synchronizationVersion.index}. The transformer expected` +
2153
- ` #${this.synchronizationVersion.index + 1}.`);
2154
- }
2155
- nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps should be set by now");
2156
- const changesetsToSkip = this.isReverseSynchronization
2157
- ? this._targetScopeProvenanceProps.jsonProperties
2158
- .pendingReverseSyncChangesetIndices
2159
- : this._targetScopeProvenanceProps.jsonProperties
2160
- .pendingSyncChangesetIndices;
1586
+ startChangesetIndex !== syncVersion.index + 1 &&
1587
+ syncVersion.index !== -1) {
1588
+ core_bentley_1.ITwinError.throwError({
1589
+ iTwinErrorId: {
1590
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
1591
+ key: IModelTransformerError_1.IModelTransformerError.SynchronizationRangeInvalid,
1592
+ },
1593
+ message: `synchronization is ${missingChangesets ? "missing changesets" : ""},` +
1594
+ " startChangesetId should be" +
1595
+ " exactly the first changeset *after* the previous synchronization to not miss data." +
1596
+ ` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
1597
+ ` but the previous synchronization for this targetScopeElem ${syncVersion.id}'` +
1598
+ ` which is changeset #${syncVersion.index}. The transformer expected` +
1599
+ ` #${syncVersion.index + 1}.`,
1600
+ });
1601
+ }
1602
+ const changesetsToSkip = await this._provenanceManager.getChangesetsToSkip();
2161
1603
  core_bentley_1.Logger.logTrace(loggerCategory, `changesets to skip: ${changesetsToSkip}`);
2162
1604
  this._changesetRanges = (0, Algo_1.rangesFromRangeAndSkipped)(startChangesetIndex, endChangesetIndex, changesetsToSkip);
2163
1605
  core_bentley_1.Logger.logTrace(loggerCategory, `ranges: ${this._changesetRanges}`);
@@ -2176,6 +1618,27 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2176
1618
  this._sourceChangeDataState =
2177
1619
  this._csFileProps.length === 0 ? "no-changes" : "has-changes";
2178
1620
  }
1621
+ /** Asserts that the EditTxn is active before any write operations. */
1622
+ assertEditTxnActive() {
1623
+ if (!this._targetEditTxn.isActive) {
1624
+ core_bentley_1.ITwinError.throwError({
1625
+ iTwinErrorId: {
1626
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
1627
+ key: IModelTransformerError_1.IModelTransformerError.EditTxnNotActive,
1628
+ },
1629
+ message: "The target EditTxn must be started before calling process(). Call targetEditTxn.start() before invoking the transformer.",
1630
+ });
1631
+ }
1632
+ if (this._sourceEditTxn && !this._sourceEditTxn.isActive) {
1633
+ core_bentley_1.ITwinError.throwError({
1634
+ iTwinErrorId: {
1635
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
1636
+ key: IModelTransformerError_1.IModelTransformerError.EditTxnNotActive,
1637
+ },
1638
+ message: "The source EditTxn was provided but is not active. Call sourceEditTxn.start() before invoking the transformer.",
1639
+ });
1640
+ }
1641
+ }
2179
1642
  /**
2180
1643
  * The behavior of process is influenced by [[IModelTransformOptions.argsForProcessChanges]] being defined or not defined during construction passed of the IModelTransformer.
2181
1644
  * @section When argsForProcessChanges are defined:
@@ -2210,47 +1673,34 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2210
1673
  * @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
2211
1674
  */
2212
1675
  async processAll() {
1676
+ this._targetElementIdsRemappedByCode.clear();
1677
+ this._targetModelsImportedInCurrentTransform.clear();
1678
+ // processAll always has changes to process, so mark it as such for version tracking
1679
+ this._sourceChangeDataState = "has-changes";
2213
1680
  await this.exporter.exportCodeSpecs();
2214
1681
  await this.exporter.exportFonts();
2215
- if (this._options.skipPropagateChangesToRootElements) {
2216
- // The RepositoryModel and root Subject of the target iModel should not be transformed.
2217
- await this.exporter.exportChildElements(core_common_1.IModel.rootSubjectId); // start below the root Subject
2218
- await this.exporter.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true); // after the Subject hierarchy, process the other elements of the RepositoryModel
2219
- await this.exporter.exportSubModels(core_common_1.IModel.repositoryModelId); // start below the RepositoryModel
2220
- }
2221
- else {
2222
- await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
2223
- }
2224
- this.completePartiallyCommittedElements();
2225
- await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
2226
- this.completePartiallyCommittedAspects();
1682
+ await this.exporter.elementAspectExportCoordinator.run(async () => {
1683
+ if (this._options.skipPropagateChangesToRootElements) {
1684
+ // The RepositoryModel and root Subject of the target iModel should not be transformed.
1685
+ await this.exporter.exportChildElements(core_common_1.IModel.rootSubjectId); // start below the root Subject
1686
+ await this.exporter.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true); // after the Subject hierarchy, process the other elements of the RepositoryModel
1687
+ await this.exporter.exportSubModels(core_common_1.IModel.repositoryModelId); // start below the RepositoryModel
1688
+ }
1689
+ else {
1690
+ await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
1691
+ }
1692
+ await this.completePartiallyCommittedElements();
1693
+ });
1694
+ await this.completePartiallyCommittedAspects();
2227
1695
  await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
2228
1696
  if (this._options.forceExternalSourceAspectProvenance &&
2229
- this.shouldDetectDeletes()) {
2230
- // eslint-disable-next-line @typescript-eslint/no-deprecated
2231
- await this.detectElementDeletes();
2232
- // eslint-disable-next-line @typescript-eslint/no-deprecated
2233
- await this.detectRelationshipDeletes();
1697
+ (await this.shouldDetectDeletes())) {
1698
+ core_bentley_1.Logger.logWarning(loggerCategory, "This workflows was deprecated in v1 and is no longer supported");
2234
1699
  }
2235
1700
  if (this._options.optimizeGeometry)
2236
1701
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
2237
1702
  this.importer.computeProjectExtents();
2238
- this.finalizeTransformation();
2239
- }
2240
- /** previous provenance, either a federation guid, a `${sourceFedGuid}/${targetFedGuid}` pair, or required aspect props */
2241
- _lastProvenanceEntityInfo = nullLastProvenanceEntityInfo;
2242
- markLastProvenance(sourceAspect, { isRelationship = false }) {
2243
- this._lastProvenanceEntityInfo =
2244
- typeof sourceAspect === "string"
2245
- ? sourceAspect
2246
- : {
2247
- entityId: sourceAspect.element.id,
2248
- aspectId: sourceAspect.id,
2249
- aspectVersion: sourceAspect.version ?? "",
2250
- aspectKind: isRelationship
2251
- ? core_backend_1.ExternalSourceAspect.Kind.Relationship
2252
- : core_backend_1.ExternalSourceAspect.Kind.Element,
2253
- };
1703
+ await this.finalizeTransformation();
2254
1704
  }
2255
1705
  /** Export changes from the source iModel and import the transformed entities into the target iModel.
2256
1706
  * Inserts, updates, and deletes are determined by inspecting the changeset(s).
@@ -2262,25 +1712,33 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2262
1712
  * @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.
2263
1713
  */
2264
1714
  async processChanges(options) {
1715
+ this._targetElementIdsRemappedByCode.clear();
1716
+ this._targetModelsImportedInCurrentTransform.clear();
2265
1717
  // must wait for initialization of synchronization provenance data
2266
- await this.exporter.exportChanges(this.getExportInitOpts(options));
2267
- this.completePartiallyCommittedElements();
2268
- this.completePartiallyCommittedAspects();
1718
+ await this.exporter.exportChanges(await this.getExportInitOpts(options));
1719
+ await this.completePartiallyCommittedElements();
1720
+ await this.completePartiallyCommittedAspects();
2269
1721
  if (this._options.optimizeGeometry)
2270
1722
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
2271
1723
  this.importer.computeProjectExtents();
2272
- this.finalizeTransformation();
1724
+ await this.finalizeTransformation();
2273
1725
  const defaultSaveTargetChanges = () => {
2274
- this.targetDb.saveChanges();
1726
+ this._targetEditTxn.saveChanges();
2275
1727
  };
2276
1728
  await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
2277
1729
  }
2278
1730
  /** Changeset data must be initialized in order to build correct changeOptions.
2279
1731
  * Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
2280
1732
  */
2281
- getExportInitOpts(opts) {
1733
+ async getExportInitOpts(opts) {
2282
1734
  if (!this._options.argsForProcessChanges)
2283
1735
  return {};
1736
+ if ("changedInstanceIds" in opts) {
1737
+ return {
1738
+ changedInstanceIds: opts.changedInstanceIds,
1739
+ skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
1740
+ };
1741
+ }
2284
1742
  const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
2285
1743
  return {
2286
1744
  skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
@@ -2292,7 +1750,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2292
1750
  ? { startChangeset }
2293
1751
  : {
2294
1752
  startChangeset: {
2295
- index: this.synchronizationVersion.index + 1,
1753
+ index: (await this.getSynchronizationVersion()).index + 1,
2296
1754
  },
2297
1755
  }),
2298
1756
  };
@@ -2319,16 +1777,14 @@ class TemplateModelCloner extends IModelTransformer {
2319
1777
  /** Accumulates the mapping of sourceElementIds to targetElementIds from the elements in the template model that were cloned. */
2320
1778
  _sourceIdToTargetIdMap;
2321
1779
  /** Construct a new TemplateModelCloner
2322
- * @param sourceDb The source IModelDb that contains the templates to clone
2323
- * @param targetDb Optionally specify the target IModelDb where the cloned template will be inserted.
2324
- * Typically this is left unspecified, and the default is to use the sourceDb as the target
1780
+ * @param editTxn The [[EditTxn]] to use for all read and write operations. The source and target db are both derived from `editTxn.iModel`. Must be started before constructing.
2325
1781
  * @note The expectation is that the template definitions are within the same iModel where instances will be placed.
2326
1782
  */
2327
- constructor(sourceDb, targetDb = sourceDb) {
2328
- const target = new IModelImporter_1.IModelImporter(targetDb, {
1783
+ constructor(editTxn) {
1784
+ const target = new IModelImporter_1.IModelImporter(editTxn, {
2329
1785
  autoExtendProjectExtents: false, // autoExtendProjectExtents is intended for transformation service use cases, not template --> instance cloning
2330
1786
  });
2331
- super(sourceDb, target, { noProvenance: true }); // WIP: need to decide the proper way to handle provenance
1787
+ super({ source: editTxn.iModel, target }, { noProvenance: true }); // WIP: need to decide the proper way to handle provenance
2332
1788
  }
2333
1789
  /** Place a template from the sourceDb at the specified placement in the target model within the targetDb.
2334
1790
  * @param sourceTemplateModelId The Id of the template model in the sourceDb
@@ -2373,13 +1829,19 @@ class TemplateModelCloner extends IModelTransformer {
2373
1829
  return this._sourceIdToTargetIdMap; // return the sourceElementId -> targetElementId Map in case further post-processing is required.
2374
1830
  }
2375
1831
  /** Cloning from a template requires this override of onTransformElement. */
2376
- onTransformElement(sourceElement) {
1832
+ async onTransformElement(sourceElement) {
2377
1833
  const referenceIds = sourceElement.getReferenceIds();
2378
- referenceIds.forEach((referenceId) => {
1834
+ for (const referenceId of referenceIds) {
2379
1835
  // TODO: consider going through all definition elements at once and remapping them to themselves
2380
- if (!core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
1836
+ if (!core_backend_1.EntityReferences.isValid(await this.context.findTargetEntityId(referenceId))) {
2381
1837
  if (this.context.isBetweenIModels) {
2382
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, `Remapping for source dependency ${referenceId} not found for target iModel`);
1838
+ core_bentley_1.ITwinError.throwError({
1839
+ iTwinErrorId: {
1840
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
1841
+ key: IModelTransformerError_1.IModelTransformerError.DependencyMappingMissing,
1842
+ },
1843
+ message: `Remapping for source dependency ${referenceId} not found for target iModel`,
1844
+ });
2383
1845
  }
2384
1846
  else {
2385
1847
  const definitionElement = this.sourceDb.elements.tryGetElement(referenceId, core_backend_1.DefinitionElement);
@@ -2388,12 +1850,18 @@ class TemplateModelCloner extends IModelTransformer {
2388
1850
  this.context.remapElement(referenceId, referenceId); // when in the same iModel, can use existing DefinitionElements without remapping
2389
1851
  }
2390
1852
  else {
2391
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, `Remapping for dependency ${referenceId} not found`);
1853
+ core_bentley_1.ITwinError.throwError({
1854
+ iTwinErrorId: {
1855
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
1856
+ key: IModelTransformerError_1.IModelTransformerError.DependencyMappingMissing,
1857
+ },
1858
+ message: `Remapping for dependency ${referenceId} not found`,
1859
+ });
2392
1860
  }
2393
1861
  }
2394
1862
  }
2395
- });
2396
- const targetElementProps = super.onTransformElement(sourceElement);
1863
+ }
1864
+ const targetElementProps = await super.onTransformElement(sourceElement);
2397
1865
  targetElementProps.federationGuid = core_bentley_1.Guid.createValue(); // clone from template should create a new federationGuid
2398
1866
  targetElementProps.code = core_common_1.Code.createEmpty(); // clone from template should not maintain codes
2399
1867
  if (sourceElement instanceof core_backend_1.GeometricElement) {
@@ -2401,7 +1869,7 @@ class TemplateModelCloner extends IModelTransformer {
2401
1869
  const placementClass = is3d ? core_common_1.Placement3d : core_common_1.Placement2d;
2402
1870
  const placement = placementClass.fromJSON(targetElementProps.placement);
2403
1871
  if (placement.isValid) {
2404
- nodeAssert(this._transform3d);
1872
+ (0, node_assert_1.strict)(this._transform3d);
2405
1873
  placement.multiplyTransform(this._transform3d);
2406
1874
  targetElementProps.placement = placement;
2407
1875
  }
@@ -2411,18 +1879,4 @@ class TemplateModelCloner extends IModelTransformer {
2411
1879
  }
2412
1880
  }
2413
1881
  exports.TemplateModelCloner = TemplateModelCloner;
2414
- function queryElemFedGuid(db, elemId) {
2415
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
2416
- return db.withPreparedStatement(`
2417
- SELECT FederationGuid
2418
- FROM bis.Element
2419
- WHERE ECInstanceId=?
2420
- `, (stmt) => {
2421
- stmt.bindId(1, elemId);
2422
- (0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW);
2423
- const result = stmt.getValue(0).getGuid();
2424
- (0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_DONE);
2425
- return result;
2426
- });
2427
- }
2428
1882
  //# sourceMappingURL=IModelTransformer.js.map