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