@itwin/imodel-transformer 2.0.0-dev.12 → 2.0.0-dev.14
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/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +8 -3
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +1 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.js +3 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
- package/lib/cjs/ECReferenceTypesCache.js +1 -1
- package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
- package/lib/cjs/ElementCascadingDeleter.d.ts.map +1 -1
- package/lib/cjs/ElementCascadingDeleter.js +2 -1
- package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
- package/lib/cjs/EntityUnifier.d.ts.map +1 -1
- package/lib/cjs/EntityUnifier.js +7 -1
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +2 -0
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +4 -0
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +16 -1
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +26 -135
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +104 -743
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/ProvenanceManager.d.ts +159 -0
- package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
- package/lib/cjs/ProvenanceManager.js +677 -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/package.json +15 -15
|
@@ -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,
|
|
@@ -86,7 +82,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
86
82
|
* @beta
|
|
87
83
|
*/
|
|
88
84
|
_linearSpatialTransform;
|
|
89
|
-
|
|
85
|
+
_syncTypeResolver;
|
|
86
|
+
_provenanceManager;
|
|
90
87
|
/** The Id of the Element in the **target** iModel that represents the **source** repository as a whole and scopes its [ExternalSourceAspect]($backend) instances. */
|
|
91
88
|
get targetScopeElementId() {
|
|
92
89
|
return this._options.targetScopeElementId;
|
|
@@ -95,112 +92,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
95
92
|
_elementsWithExplicitlyTrackedProvenance = new Set();
|
|
96
93
|
_partiallyCommittedElementIds = new Set();
|
|
97
94
|
_partiallyCommittedAspectIds = new Set();
|
|
98
|
-
/** the options that were used to initialize this transformer */
|
|
99
|
-
_options;
|
|
100
|
-
/**
|
|
101
|
-
* A private variable meant to be set by tests which have an outdated way of setting up transforms. In all synchronizations today we expect to find an ESA in the branch db which describes the master -> branch relationship.
|
|
102
|
-
* The exception to this is the first transform aka the provenance initializing transform which requires that the master imodel and the branch imodel are identical at the time of provenance initialization.
|
|
103
|
-
* A couple ofoutdated tests run their first transform providing a source and targetdb that are slightly different which is no longer supported. In order to not remove these tests which are still providing value
|
|
104
|
-
* this private property on the IModelTransformer exists.
|
|
105
|
-
*/
|
|
106
|
-
_allowNoScopingESA = false;
|
|
107
|
-
static noEsaSyncDirectionErrorMessage = "Couldn't find an external source aspect to determine sync direction. This often means that the master->branch relationship has not been established. Consider running the transformer with wasSourceIModelCopiedToTarget set to true.";
|
|
108
95
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
96
|
+
* Tracks target element IDs that were imported (inserted or updated) during the current
|
|
97
|
+
* transformation pass. Used to prevent deletion of target elements that have been remapped
|
|
98
|
+
* to a new source element in the same pass (e.g., when a source element is deleted and a
|
|
99
|
+
* new one with the same properties is added, causing a remap to the same target).
|
|
112
100
|
*/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
117
|
-
WHERE Element.Id=:elementId
|
|
118
|
-
AND Scope.Id=:scopeId
|
|
119
|
-
AND Kind=:kind
|
|
120
|
-
AND Identifier=:identifier
|
|
121
|
-
LIMIT 1
|
|
122
|
-
`;
|
|
123
|
-
if (aspectProps.scope === undefined)
|
|
124
|
-
return undefined;
|
|
125
|
-
const params = new core_common_1.QueryBinder()
|
|
126
|
-
.bindId("elementId", aspectProps.element.id)
|
|
127
|
-
.bindId("scopeId", aspectProps.scope.id)
|
|
128
|
-
.bindString("kind", aspectProps.kind)
|
|
129
|
-
.bindString("identifier", aspectProps.identifier);
|
|
130
|
-
return dbToQuery.withQueryReader(sql, (reader) => {
|
|
131
|
-
if (!reader.step())
|
|
132
|
-
return undefined;
|
|
133
|
-
const aspectId = reader.current[0];
|
|
134
|
-
const version = reader.current[1];
|
|
135
|
-
const jsonProperties = reader.current[2];
|
|
136
|
-
return { aspectId, version, jsonProperties };
|
|
137
|
-
}, params);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Determines the sync direction "forward" or "reverse" of a given sourceDb and targetDb by looking for the scoping ESA.
|
|
141
|
-
* If the sourceDb's iModelId is found as the identifier of the expected scoping ESA in the targetDb, then it is a forward synchronization.
|
|
142
|
-
* If the targetDb's iModelId is found as the identifier of the expected scoping ESA in the sourceDb, then it is a reverse synchronization.
|
|
143
|
-
* @throws if no scoping ESA can be found in either the sourceDb or targetDb which describes a master branch relationship between the two databases.
|
|
144
|
-
* @returns "forward" or "reverse"
|
|
145
|
-
*/
|
|
146
|
-
static async determineSyncType(sourceDb, targetDb,
|
|
147
|
-
/** @see [[IModelTransformOptions.targetScopeElementId]] */
|
|
148
|
-
targetScopeElementId) {
|
|
149
|
-
const aspectProps = {
|
|
150
|
-
id: undefined,
|
|
151
|
-
version: undefined,
|
|
152
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
153
|
-
element: {
|
|
154
|
-
id: targetScopeElementId,
|
|
155
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
156
|
-
},
|
|
157
|
-
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
158
|
-
identifier: sourceDb.iModelId,
|
|
159
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
160
|
-
jsonProperties: undefined,
|
|
161
|
-
};
|
|
162
|
-
/** First check if the targetDb is the branch (branch is the @see provenanceDb) */
|
|
163
|
-
const esaPropsFromTargetDb = await this.queryScopeExternalSourceAspect(targetDb, aspectProps);
|
|
164
|
-
if (esaPropsFromTargetDb !== undefined) {
|
|
165
|
-
return "forward"; // we found an esa assuming targetDb is the provenanceDb/branch so this is a forward sync.
|
|
166
|
-
}
|
|
167
|
-
// Now check if the sourceDb is the branch
|
|
168
|
-
aspectProps.identifier = targetDb.iModelId;
|
|
169
|
-
const esaPropsFromSourceDb = await this.queryScopeExternalSourceAspect(sourceDb, aspectProps);
|
|
170
|
-
if (esaPropsFromSourceDb !== undefined) {
|
|
171
|
-
return "reverse"; // we found an esa assuming sourceDb is the provenanceDb/branch so this is a reverse sync.
|
|
172
|
-
}
|
|
173
|
-
throw new Error(this.noEsaSyncDirectionErrorMessage);
|
|
174
|
-
}
|
|
175
|
-
async determineSyncType() {
|
|
176
|
-
if (this._isProvenanceInitTransform) {
|
|
177
|
-
return "forward";
|
|
178
|
-
}
|
|
179
|
-
if (!this._options.argsForProcessChanges) {
|
|
180
|
-
return "not-sync";
|
|
181
|
-
}
|
|
182
|
-
try {
|
|
183
|
-
return await IModelTransformer.determineSyncType(this.sourceDb, this.targetDb, this.targetScopeElementId);
|
|
184
|
-
}
|
|
185
|
-
catch (err) {
|
|
186
|
-
if (err instanceof Error &&
|
|
187
|
-
err.message === IModelTransformer.noEsaSyncDirectionErrorMessage &&
|
|
188
|
-
this._allowNoScopingESA) {
|
|
189
|
-
return "forward";
|
|
190
|
-
}
|
|
191
|
-
throw err;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
async getIsReverseSynchronization() {
|
|
195
|
-
if (this._syncType === undefined)
|
|
196
|
-
this._syncType = await this.determineSyncType();
|
|
197
|
-
return this._syncType === "reverse";
|
|
198
|
-
}
|
|
199
|
-
async getIsForwardSynchronization() {
|
|
200
|
-
if (this._syncType === undefined)
|
|
201
|
-
this._syncType = await this.determineSyncType();
|
|
202
|
-
return this._syncType === "forward";
|
|
203
|
-
}
|
|
101
|
+
_targetElementsImportedInCurrentTransform = new Set();
|
|
102
|
+
/** the options that were used to initialize this transformer */
|
|
103
|
+
_options;
|
|
204
104
|
_changesetRanges = undefined;
|
|
205
105
|
/**
|
|
206
106
|
* Set if the transformer is being used to perform the provenance initialization step of a fork initialization.
|
|
@@ -209,16 +109,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
209
109
|
_isProvenanceInitTransform;
|
|
210
110
|
/** The element classes that are considered to define provenance in the iModel */
|
|
211
111
|
static get provenanceElementClasses() {
|
|
212
|
-
return
|
|
213
|
-
core_backend_1.FolderLink,
|
|
214
|
-
core_backend_1.SynchronizationConfigLink,
|
|
215
|
-
core_backend_1.ExternalSource,
|
|
216
|
-
core_backend_1.ExternalSourceAttachment,
|
|
217
|
-
];
|
|
112
|
+
return ProvenanceManager_1.ProvenanceManager.provenanceElementClasses;
|
|
218
113
|
}
|
|
219
114
|
/** The element aspect classes that are considered to define provenance in the iModel */
|
|
220
115
|
static get provenanceElementAspectClasses() {
|
|
221
|
-
return
|
|
116
|
+
return ProvenanceManager_1.ProvenanceManager.provenanceElementAspectClasses;
|
|
222
117
|
}
|
|
223
118
|
/** Construct a new IModelTransformer
|
|
224
119
|
* @param source Specifies the source IModelExporter or the source IModelDb that will be used to construct the source IModelExporter.
|
|
@@ -277,14 +172,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
277
172
|
this.targetDb = this.importer.targetDb;
|
|
278
173
|
// create the IModelCloneContext, it must be initialized later
|
|
279
174
|
this.context = new IModelCloneContext_1.IModelCloneContext(this.sourceDb, this.targetDb);
|
|
280
|
-
if (this.sourceDb.isBriefcase && this.targetDb.isBriefcase) {
|
|
281
|
-
nodeAssert(this.sourceDb.changeset.index !== undefined &&
|
|
282
|
-
this.targetDb.changeset.index !== undefined, "database has no changeset index");
|
|
283
|
-
this._startingChangesetIndices = {
|
|
284
|
-
target: this.targetDb.changeset.index,
|
|
285
|
-
source: this.sourceDb.changeset.index,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
175
|
// this internal is guaranteed stable for just transformer usage
|
|
289
176
|
/* eslint-disable @itwin/no-internal */
|
|
290
177
|
if (("codeValueBehavior" in this.sourceDb)) {
|
|
@@ -292,6 +179,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
292
179
|
this.targetDb.codeValueBehavior = "exact";
|
|
293
180
|
}
|
|
294
181
|
/* eslint-enable @itwin/no-internal */
|
|
182
|
+
this._syncTypeResolver = new SyncTypeResolver_1.SyncTypeResolver(this.context, this._options.targetScopeElementId, !!this._isProvenanceInitTransform, !!this._options.argsForProcessChanges);
|
|
183
|
+
this._provenanceManager = new ProvenanceManager_1.ProvenanceManager(this._options.targetScopeElementId, this._options, this._syncTypeResolver);
|
|
295
184
|
if (this._options.tryAlignGeolocation) {
|
|
296
185
|
if (this.sourceDb.geographicCoordinateSystem ||
|
|
297
186
|
this.targetDb.geographicCoordinateSystem) {
|
|
@@ -342,79 +231,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
342
231
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
343
232
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
344
233
|
}
|
|
345
|
-
/** Return the IModelDb where IModelTransformer will store its provenance.
|
|
346
|
-
* @note This will be [[targetDb]] except when it is a reverse synchronization. In that case it be [[sourceDb]].
|
|
347
|
-
*/
|
|
348
|
-
async getProvenanceDb() {
|
|
349
|
-
return (await this.getIsReverseSynchronization())
|
|
350
|
-
? this.sourceDb
|
|
351
|
-
: this.targetDb;
|
|
352
|
-
}
|
|
353
|
-
/** Return the IModelDb where IModelTransformer looks for entities referred to by stored provenance.
|
|
354
|
-
* @note This will be [[sourceDb]] except when it is a reverse synchronization. In that case it be [[targetDb]].
|
|
355
|
-
*/
|
|
356
|
-
async getProvenanceSourceDb() {
|
|
357
|
-
return (await this.getIsReverseSynchronization())
|
|
358
|
-
? this.targetDb
|
|
359
|
-
: this.sourceDb;
|
|
360
|
-
}
|
|
361
|
-
/** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
|
|
362
|
-
static initElementProvenanceOptions(sourceElementId, targetElementId, args) {
|
|
363
|
-
const elementId = args.isReverseSynchronization
|
|
364
|
-
? sourceElementId
|
|
365
|
-
: targetElementId;
|
|
366
|
-
const version = args.isReverseSynchronization
|
|
367
|
-
? args.targetDb.elements.queryLastModifiedTime(targetElementId)
|
|
368
|
-
: args.sourceDb.elements.queryLastModifiedTime(sourceElementId);
|
|
369
|
-
const aspectIdentifier = args.isReverseSynchronization
|
|
370
|
-
? targetElementId
|
|
371
|
-
: sourceElementId;
|
|
372
|
-
const aspectProps = {
|
|
373
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
374
|
-
element: {
|
|
375
|
-
id: elementId,
|
|
376
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
377
|
-
},
|
|
378
|
-
scope: { id: args.targetScopeElementId },
|
|
379
|
-
identifier: aspectIdentifier,
|
|
380
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
381
|
-
version,
|
|
382
|
-
};
|
|
383
|
-
return aspectProps;
|
|
384
|
-
}
|
|
385
|
-
static async initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, args) {
|
|
386
|
-
const provenanceDb = args.isReverseSynchronization
|
|
387
|
-
? args.sourceDb
|
|
388
|
-
: args.targetDb;
|
|
389
|
-
const aspectIdentifier = args.isReverseSynchronization
|
|
390
|
-
? targetRelInstanceId
|
|
391
|
-
: sourceRelInstanceId;
|
|
392
|
-
const provenanceRelInstanceId = args.isReverseSynchronization
|
|
393
|
-
? sourceRelInstanceId
|
|
394
|
-
: targetRelInstanceId;
|
|
395
|
-
const sql = "SELECT SourceECInstanceId FROM bis.ElementRefersToElements WHERE ECInstanceId=?";
|
|
396
|
-
const params = new core_common_1.QueryBinder().bindId(1, provenanceRelInstanceId);
|
|
397
|
-
const reader = provenanceDb.createQueryReader(sql, params, {
|
|
398
|
-
usePrimaryConn: true,
|
|
399
|
-
});
|
|
400
|
-
nodeAssert(await reader.step(), "relationship provenance query returned no rows");
|
|
401
|
-
const elementId = reader.current[0];
|
|
402
|
-
const jsonProperties = args.forceOldRelationshipProvenanceMethod
|
|
403
|
-
? { targetRelInstanceId }
|
|
404
|
-
: { provenanceRelInstanceId };
|
|
405
|
-
const aspectProps = {
|
|
406
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
407
|
-
element: {
|
|
408
|
-
id: elementId,
|
|
409
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
410
|
-
},
|
|
411
|
-
scope: { id: args.targetScopeElementId },
|
|
412
|
-
identifier: aspectIdentifier,
|
|
413
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
|
|
414
|
-
jsonProperties: JSON.stringify(jsonProperties),
|
|
415
|
-
};
|
|
416
|
-
return aspectProps;
|
|
417
|
-
}
|
|
418
234
|
/**
|
|
419
235
|
* Previously the transformer would insert provenance always pointing to the "target" relationship.
|
|
420
236
|
* It should (and now by default does) instead insert provenance pointing to the provenanceSource
|
|
@@ -424,42 +240,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
424
240
|
_forceOldRelationshipProvenanceMethod = false;
|
|
425
241
|
/** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
|
|
426
242
|
async initElementProvenance(sourceElementId, targetElementId) {
|
|
427
|
-
return
|
|
428
|
-
isReverseSynchronization: await this.getIsReverseSynchronization(),
|
|
429
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
430
|
-
sourceDb: this.sourceDb,
|
|
431
|
-
targetDb: this.targetDb,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
/** Create an ExternalSourceAspectProps in a standard way for a Relationship in an iModel --> iModel transformations.
|
|
435
|
-
* The ExternalSourceAspect is meant to be owned by the Element in the target iModel that is the `sourceId` of transformed relationship.
|
|
436
|
-
* The `identifier` property of the ExternalSourceAspect will be the ECInstanceId of the relationship in the master iModel.
|
|
437
|
-
* The ECInstanceId of the relationship in the branch iModel will be stored in the JsonProperties of the ExternalSourceAspect.
|
|
438
|
-
*/
|
|
439
|
-
async initRelationshipProvenance(sourceRelationship, targetRelInstanceId) {
|
|
440
|
-
return IModelTransformer.initRelationshipProvenanceOptions(sourceRelationship.id, targetRelInstanceId, {
|
|
441
|
-
sourceDb: this.sourceDb,
|
|
442
|
-
targetDb: this.targetDb,
|
|
443
|
-
isReverseSynchronization: await this.getIsReverseSynchronization(),
|
|
444
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
445
|
-
forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
/** NOTE: the json properties must be converted to string before insertion */
|
|
449
|
-
_targetScopeProvenanceProps = undefined;
|
|
450
|
-
/**
|
|
451
|
-
* Index of the changeset that the transformer was at when the transformation begins (was constructed).
|
|
452
|
-
* Used to determine at the end which changesets were part of a synchronization.
|
|
453
|
-
*/
|
|
454
|
-
_startingChangesetIndices = undefined;
|
|
455
|
-
_cachedSynchronizationVersion = undefined;
|
|
456
|
-
/**
|
|
457
|
-
* We cache the synchronization version to avoid querying the target scoping ESA multiple times.
|
|
458
|
-
* If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
|
|
459
|
-
* Sets this._cachedSynchronizationVersion to undefined.
|
|
460
|
-
*/
|
|
461
|
-
clearCachedSynchronizationVersion() {
|
|
462
|
-
this._cachedSynchronizationVersion = undefined;
|
|
243
|
+
return this._provenanceManager.initElementProvenance(sourceElementId, targetElementId);
|
|
463
244
|
}
|
|
464
245
|
/** the changeset in the scoping element's source version found for this transformation
|
|
465
246
|
* @note the version depends on whether this is a reverse synchronization or not, as
|
|
@@ -469,394 +250,58 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
469
250
|
* @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
|
|
470
251
|
*/
|
|
471
252
|
async getSynchronizationVersion() {
|
|
472
|
-
|
|
473
|
-
const provenanceScopeAspect = await this.tryGetProvenanceScopeAspect();
|
|
474
|
-
if (!provenanceScopeAspect) {
|
|
475
|
-
return { index: -1, id: "" }; // first synchronization.
|
|
476
|
-
}
|
|
477
|
-
const version = (await this.getIsReverseSynchronization())
|
|
478
|
-
? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
|
|
479
|
-
: provenanceScopeAspect.version;
|
|
480
|
-
if (!version &&
|
|
481
|
-
this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
|
|
482
|
-
return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
|
|
483
|
-
}
|
|
484
|
-
if (version === undefined) {
|
|
485
|
-
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.
|
|
486
|
-
Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
|
|
487
|
-
}
|
|
488
|
-
const [id, index] = version === "" ? ["", -1] : version.split(";");
|
|
489
|
-
if (Number.isNaN(Number(index)))
|
|
490
|
-
throw new Error("Could not parse version data from scope aspect");
|
|
491
|
-
this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
|
|
492
|
-
}
|
|
493
|
-
return this._cachedSynchronizationVersion;
|
|
253
|
+
return this._provenanceManager.getSynchronizationVersion();
|
|
494
254
|
}
|
|
495
255
|
/**
|
|
496
256
|
* @returns provenance scope aspect if it exists in the provenanceDb.
|
|
497
257
|
* Provenance scope aspect is created and inserted into provenanceDb when [[initScopeProvenance]] is invoked.
|
|
498
258
|
*/
|
|
499
259
|
async tryGetProvenanceScopeAspect() {
|
|
500
|
-
|
|
501
|
-
id: undefined,
|
|
502
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
503
|
-
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
504
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
505
|
-
element: { id: this.targetScopeElementId ?? core_common_1.IModel.rootSubjectId },
|
|
506
|
-
identifier: (await this.getProvenanceSourceDb()).iModelId,
|
|
507
|
-
});
|
|
508
|
-
return scopeProvenanceAspectProps !== undefined
|
|
509
|
-
? (await this.getProvenanceDb()).elements.getAspect(scopeProvenanceAspectProps.aspectId)
|
|
510
|
-
: undefined;
|
|
260
|
+
return this._provenanceManager.tryGetProvenanceScopeAspect();
|
|
511
261
|
}
|
|
512
262
|
/**
|
|
513
263
|
* Make sure there are no conflicting other scope-type external source aspects on the *target scope element*,
|
|
514
264
|
* If there are none at all, insert one, then this must be a first synchronization.
|
|
515
|
-
* @returns the last synced version (changesetId) on the target scope's external source aspect,
|
|
516
|
-
* if this was a [BriefcaseDb]($backend)
|
|
517
265
|
*/
|
|
518
266
|
async initScopeProvenance() {
|
|
519
|
-
|
|
520
|
-
const sourceProvenanceDb = await this.getProvenanceSourceDb();
|
|
521
|
-
const aspectProps = {
|
|
522
|
-
id: undefined,
|
|
523
|
-
version: undefined,
|
|
524
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
525
|
-
element: {
|
|
526
|
-
id: this.targetScopeElementId,
|
|
527
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
528
|
-
},
|
|
529
|
-
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
530
|
-
identifier: sourceProvenanceDb.iModelId,
|
|
531
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
532
|
-
jsonProperties: undefined,
|
|
533
|
-
};
|
|
534
|
-
const foundEsaProps = await IModelTransformer.queryScopeExternalSourceAspect(provenanceDb, aspectProps); // this query includes "identifier"
|
|
535
|
-
if (foundEsaProps === undefined) {
|
|
536
|
-
aspectProps.version = ""; // empty since never before transformed. Will be updated in [[finalizeTransformation]]
|
|
537
|
-
aspectProps.jsonProperties = {
|
|
538
|
-
pendingReverseSyncChangesetIndices: [],
|
|
539
|
-
pendingSyncChangesetIndices: [],
|
|
540
|
-
reverseSyncVersion: "", // empty since never before transformed. Will be updated in first reverse sync
|
|
541
|
-
};
|
|
542
|
-
// this query does not include "identifier" to find possible conflicts
|
|
543
|
-
const sql = `
|
|
544
|
-
SELECT ECInstanceId
|
|
545
|
-
FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
546
|
-
WHERE Element.Id=:elementId
|
|
547
|
-
AND Scope.Id=:scopeId
|
|
548
|
-
AND Kind=:kind
|
|
549
|
-
LIMIT 1
|
|
550
|
-
`;
|
|
551
|
-
const params = new core_common_1.QueryBinder();
|
|
552
|
-
params.bindId("elementId", aspectProps.element.id);
|
|
553
|
-
params.bindId("scopeId", aspectProps.scope.id); // this scope.id can never be invalid, we create it above
|
|
554
|
-
params.bindString("kind", aspectProps.kind);
|
|
555
|
-
const reader = provenanceDb.createQueryReader(sql, params, {
|
|
556
|
-
usePrimaryConn: true,
|
|
557
|
-
});
|
|
558
|
-
const hasConflictingScope = await reader.step();
|
|
559
|
-
if (hasConflictingScope) {
|
|
560
|
-
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Provenance scope conflict");
|
|
561
|
-
}
|
|
562
|
-
if (!this._options.noProvenance) {
|
|
563
|
-
const id = provenanceDb.elements.insertAspect({
|
|
564
|
-
...aspectProps,
|
|
565
|
-
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
566
|
-
});
|
|
567
|
-
aspectProps.id = id;
|
|
568
|
-
// Busting a potential cached version
|
|
569
|
-
this.clearCachedSynchronizationVersion();
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
else {
|
|
573
|
-
// foundEsaProps is defined.
|
|
574
|
-
aspectProps.id = foundEsaProps.aspectId;
|
|
575
|
-
aspectProps.version = foundEsaProps.version;
|
|
576
|
-
aspectProps.jsonProperties = foundEsaProps.jsonProperties
|
|
577
|
-
? JSON.parse(foundEsaProps.jsonProperties)
|
|
578
|
-
: undefined;
|
|
579
|
-
// Clone oldProps incase they're changed for logging purposes
|
|
580
|
-
const oldProps = JSON.parse(JSON.stringify(aspectProps));
|
|
581
|
-
if (this.handleUnsafeMigrate(aspectProps)) {
|
|
582
|
-
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 });
|
|
583
|
-
provenanceDb.elements.updateAspect({
|
|
584
|
-
...aspectProps,
|
|
585
|
-
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
586
|
-
});
|
|
587
|
-
// Busting a potential cached version
|
|
588
|
-
this.clearCachedSynchronizationVersion();
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
this._targetScopeProvenanceProps =
|
|
592
|
-
aspectProps;
|
|
593
|
-
}
|
|
594
|
-
/** Returns true if a change was made to the aspectProps. */
|
|
595
|
-
handleUnsafeMigrate(aspectProps) {
|
|
596
|
-
let madeChange = false;
|
|
597
|
-
if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
|
|
598
|
-
return madeChange;
|
|
599
|
-
const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
|
|
600
|
-
const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
|
|
601
|
-
"";
|
|
602
|
-
if (aspectProps.version === undefined ||
|
|
603
|
-
(aspectProps.version === "" &&
|
|
604
|
-
aspectProps.version !== fallbackSyncVersionToUse)) {
|
|
605
|
-
aspectProps.version = fallbackSyncVersionToUse;
|
|
606
|
-
madeChange = true;
|
|
607
|
-
}
|
|
608
|
-
if (aspectProps.jsonProperties === undefined) {
|
|
609
|
-
aspectProps.jsonProperties = {
|
|
610
|
-
pendingReverseSyncChangesetIndices: [],
|
|
611
|
-
pendingSyncChangesetIndices: [],
|
|
612
|
-
reverseSyncVersion: fallbackReverseSyncVersionToUse,
|
|
613
|
-
};
|
|
614
|
-
madeChange = true;
|
|
615
|
-
}
|
|
616
|
-
else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
|
|
617
|
-
(aspectProps.jsonProperties.reverseSyncVersion === "" &&
|
|
618
|
-
aspectProps.jsonProperties.reverseSyncVersion !==
|
|
619
|
-
fallbackReverseSyncVersionToUse)) {
|
|
620
|
-
aspectProps.jsonProperties.reverseSyncVersion =
|
|
621
|
-
fallbackReverseSyncVersionToUse;
|
|
622
|
-
madeChange = true;
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* This case will only be hit when:
|
|
626
|
-
* - first transformation was performed on pre-fedguid transformer.
|
|
627
|
-
* - a second processAll transformation was performed on the same target-source iModels post-fedguid transformer.
|
|
628
|
-
* - change processing was invoked on for the second 'initial' transformation.
|
|
629
|
-
* NOTE: This case likely does not exist anymore, but we will keep it just to be sure.
|
|
630
|
-
*/
|
|
631
|
-
if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
|
|
632
|
-
undefined) {
|
|
633
|
-
core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
|
|
634
|
-
aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
|
|
635
|
-
madeChange = true;
|
|
636
|
-
}
|
|
637
|
-
if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
|
|
638
|
-
core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
|
|
639
|
-
aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
|
|
640
|
-
madeChange = true;
|
|
641
|
-
}
|
|
642
|
-
return madeChange;
|
|
267
|
+
return this._provenanceManager.initScopeProvenance();
|
|
643
268
|
}
|
|
644
269
|
/**
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
* @note provenance is done by federation guids where possible
|
|
648
|
-
* @note this may execute on each element more than once! Only use in cases where that is handled
|
|
270
|
+
* Get the IModelDb where provenance (ExternalSourceAspects) is stored.
|
|
271
|
+
* This will be targetDb except when it is a reverse synchronization, in which case it will be sourceDb.
|
|
649
272
|
*/
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadSchema, "The BisCore schema version of the target database is too old");
|
|
655
|
-
}
|
|
656
|
-
const sourceDb = args.isReverseSynchronization
|
|
657
|
-
? args.provenanceDb
|
|
658
|
-
: args.provenanceSourceDb;
|
|
659
|
-
const targetDb = args.isReverseSynchronization
|
|
660
|
-
? args.provenanceSourceDb
|
|
661
|
-
: args.provenanceDb;
|
|
662
|
-
// query for provenanceDb
|
|
663
|
-
const elementIdByFedGuidQuery = `
|
|
664
|
-
SELECT e.ECInstanceId, FederationGuid
|
|
665
|
-
FROM bis.Element e
|
|
666
|
-
${args.skipPropagateChangesToRootElements
|
|
667
|
-
? "WHERE e.ECInstanceId NOT IN (0x1, 0xe, 0x10) -- special static elements"
|
|
668
|
-
: ""}
|
|
669
|
-
ORDER BY FederationGuid
|
|
670
|
-
`;
|
|
671
|
-
// iterate through sorted list of fed guids from both dbs to get the intersection
|
|
672
|
-
// NOTE: if we exposed the native attach database support,
|
|
673
|
-
// we could get the intersection of fed guids in one query, not sure if it would be faster
|
|
674
|
-
// OR we could do a raw sqlite query...
|
|
675
|
-
const sourceReader = sourceDb.createQueryReader(elementIdByFedGuidQuery, undefined, { usePrimaryConn: true });
|
|
676
|
-
const targetReader = targetDb.createQueryReader(elementIdByFedGuidQuery, undefined, { usePrimaryConn: true });
|
|
677
|
-
let hasSourceRow = await sourceReader.step();
|
|
678
|
-
let hasTargetRow = await targetReader.step();
|
|
679
|
-
while (hasSourceRow && hasTargetRow) {
|
|
680
|
-
const sourceFedGuid = sourceReader.current.federationGuid;
|
|
681
|
-
const targetFedGuid = targetReader.current.federationGuid;
|
|
682
|
-
if (sourceFedGuid !== undefined &&
|
|
683
|
-
targetFedGuid !== undefined &&
|
|
684
|
-
sourceFedGuid === targetFedGuid) {
|
|
685
|
-
// data flow direction is always sourceDb -> targetDb and it does not depend on where the explicit element provenance is stored
|
|
686
|
-
args.fn(sourceReader.current.id, targetReader.current.id);
|
|
687
|
-
}
|
|
688
|
-
if (targetFedGuid === undefined ||
|
|
689
|
-
(sourceFedGuid !== undefined && sourceFedGuid >= targetFedGuid)) {
|
|
690
|
-
hasTargetRow = await targetReader.step();
|
|
691
|
-
}
|
|
692
|
-
if (sourceFedGuid === undefined ||
|
|
693
|
-
(targetFedGuid !== undefined && sourceFedGuid <= targetFedGuid)) {
|
|
694
|
-
hasSourceRow = await sourceReader.step();
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
// query for provenanceDb
|
|
698
|
-
const provenanceAspectsQuery = `
|
|
699
|
-
SELECT esa.Identifier, Element.Id
|
|
700
|
-
FROM bis.ExternalSourceAspect esa
|
|
701
|
-
WHERE Scope.Id=:scopeId
|
|
702
|
-
AND Kind=:kind
|
|
703
|
-
`;
|
|
704
|
-
// Technically this will a second time call the function (as documented) on
|
|
705
|
-
// victims of the old provenance method that have both fedguids and an inserted aspect.
|
|
706
|
-
// But this is a private function with one known caller where that doesn't matter
|
|
707
|
-
const runFnInDataFlowDirection = (sourceId, targetId) => args.isReverseSynchronization
|
|
708
|
-
? args.fn(sourceId, targetId)
|
|
709
|
-
: args.fn(targetId, sourceId);
|
|
710
|
-
const params = new core_common_1.QueryBinder();
|
|
711
|
-
params.bindId("scopeId", args.targetScopeElementId);
|
|
712
|
-
params.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
713
|
-
const provenanceReader = args.provenanceDb.createQueryReader(provenanceAspectsQuery, params, { usePrimaryConn: true });
|
|
714
|
-
for await (const row of provenanceReader) {
|
|
715
|
-
// ExternalSourceAspect.Identifier is of type string
|
|
716
|
-
const aspectIdentifier = row[0];
|
|
717
|
-
const elementId = row.id;
|
|
718
|
-
runFnInDataFlowDirection(elementId, aspectIdentifier);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
async forEachTrackedElement(fn) {
|
|
722
|
-
return IModelTransformer.forEachTrackedElement({
|
|
723
|
-
provenanceSourceDb: await this.getProvenanceSourceDb(),
|
|
724
|
-
provenanceDb: await this.getProvenanceDb(),
|
|
725
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
726
|
-
isReverseSynchronization: await this.getIsReverseSynchronization(),
|
|
727
|
-
fn,
|
|
728
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
|
|
729
|
-
});
|
|
273
|
+
async getProvenanceDb() {
|
|
274
|
+
return (await this.getIsReverseSynchronization())
|
|
275
|
+
? this.sourceDb
|
|
276
|
+
: this.targetDb;
|
|
730
277
|
}
|
|
731
278
|
/**
|
|
732
|
-
*
|
|
733
|
-
* The identifier on the ESA is the id of the element in the [[IModelTransformer.provenanceSourceDb]]
|
|
734
|
-
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
735
|
-
* @param entityInProvenanceSourceId
|
|
736
|
-
* @returns the elementId that the ESA is stored on, esa.Element.Id
|
|
279
|
+
* Get whether this is a reverse synchronization.
|
|
737
280
|
*/
|
|
738
|
-
async
|
|
739
|
-
|
|
740
|
-
SELECT esa.Element.Id
|
|
741
|
-
FROM Bis.ExternalSourceAspect esa
|
|
742
|
-
WHERE esa.Kind=?
|
|
743
|
-
AND esa.Scope.Id=?
|
|
744
|
-
AND esa.Identifier=?
|
|
745
|
-
`;
|
|
746
|
-
const params = new core_common_1.QueryBinder();
|
|
747
|
-
params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
748
|
-
params.bindId(2, this.targetScopeElementId);
|
|
749
|
-
params.bindString(3, entityInProvenanceSourceId);
|
|
750
|
-
const result = (await this.getProvenanceDb()).createQueryReader(sql, params, {
|
|
751
|
-
usePrimaryConn: true,
|
|
752
|
-
});
|
|
753
|
-
if (await result.step()) {
|
|
754
|
-
return result.current.id;
|
|
755
|
-
}
|
|
756
|
-
else
|
|
757
|
-
return undefined;
|
|
281
|
+
async getIsReverseSynchronization() {
|
|
282
|
+
return (await this._syncTypeResolver.getSyncType()) === "reverse";
|
|
758
283
|
}
|
|
759
284
|
/**
|
|
760
|
-
*
|
|
761
|
-
* The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
|
|
762
|
-
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
763
|
-
* @param entityInProvenanceSourceId
|
|
764
|
-
* @returns
|
|
285
|
+
* Get whether this is a forward synchronization.
|
|
765
286
|
*/
|
|
766
|
-
async
|
|
767
|
-
|
|
768
|
-
SELECT
|
|
769
|
-
ECInstanceId,
|
|
770
|
-
JSON_EXTRACT(JsonProperties, '$.provenanceRelInstanceId') AS provenanceRelInstId
|
|
771
|
-
FROM Bis.ExternalSourceAspect
|
|
772
|
-
WHERE Kind=?
|
|
773
|
-
AND Scope.Id=?
|
|
774
|
-
AND Identifier=?
|
|
775
|
-
`;
|
|
776
|
-
const params = new core_common_1.QueryBinder();
|
|
777
|
-
params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
778
|
-
params.bindId(2, this.targetScopeElementId);
|
|
779
|
-
params.bindString(3, entityInProvenanceSourceId);
|
|
780
|
-
const result = (await this.getProvenanceDb()).createQueryReader(sql, params, {
|
|
781
|
-
usePrimaryConn: true,
|
|
782
|
-
});
|
|
783
|
-
if (await result.step()) {
|
|
784
|
-
const aspectId = result.current.id;
|
|
785
|
-
const provenanceRelInstId = result.current.provenanceRelInstId;
|
|
786
|
-
const provenanceRelInstanceId = provenanceRelInstId !== undefined
|
|
787
|
-
? provenanceRelInstId
|
|
788
|
-
: await this._queryTargetRelId(sourceRelInfo);
|
|
789
|
-
return {
|
|
790
|
-
aspectId,
|
|
791
|
-
relationshipId: provenanceRelInstanceId,
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
else
|
|
795
|
-
return undefined;
|
|
287
|
+
async getIsForwardSynchronization() {
|
|
288
|
+
return (await this._syncTypeResolver.getSyncType()) === "forward";
|
|
796
289
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
return undefined; // couldn't find an element, rel is invalid or deleted
|
|
805
|
-
const sql = `
|
|
806
|
-
select ecinstanceid
|
|
807
|
-
from bis.elementreferstoelements
|
|
808
|
-
where sourceecinstanceid=?
|
|
809
|
-
and targetecinstanceid=?
|
|
810
|
-
and ecclassid=?
|
|
811
|
-
`;
|
|
812
|
-
const params = new core_common_1.QueryBinder();
|
|
813
|
-
params.bindId(1, targetRelInfo.sourceId);
|
|
814
|
-
params.bindId(2, targetRelInfo.targetId);
|
|
815
|
-
params.bindId(3, await this._targetClassNameToClassId(sourceRelInfo.classFullName));
|
|
816
|
-
const result = this.targetDb.createQueryReader(sql, params, {
|
|
817
|
-
usePrimaryConn: true,
|
|
290
|
+
/**
|
|
291
|
+
* Updates the synchronization version on the scope ESA.
|
|
292
|
+
*/
|
|
293
|
+
async updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
|
|
294
|
+
return this._provenanceManager.updateSynchronizationVersion({
|
|
295
|
+
initializeReverseSyncVersion,
|
|
296
|
+
sourceChangeDataState: this._sourceChangeDataState,
|
|
818
297
|
});
|
|
819
|
-
if (await result.step())
|
|
820
|
-
return result.current.id;
|
|
821
|
-
else
|
|
822
|
-
return undefined;
|
|
823
|
-
}
|
|
824
|
-
_targetClassNameToClassIdCache = new Map();
|
|
825
|
-
async _targetClassNameToClassId(classFullName) {
|
|
826
|
-
let classId = this._targetClassNameToClassIdCache.get(classFullName);
|
|
827
|
-
if (classId === undefined) {
|
|
828
|
-
classId = await this._getRelClassId(this.targetDb, classFullName);
|
|
829
|
-
this._targetClassNameToClassIdCache.set(classFullName, classId);
|
|
830
|
-
}
|
|
831
|
-
return classId;
|
|
832
|
-
}
|
|
833
|
-
// NOTE: this doesn't handle remapped element classes,
|
|
834
|
-
// but is only used for relationships rn
|
|
835
|
-
async _getRelClassId(db, classFullName) {
|
|
836
|
-
const sql = `
|
|
837
|
-
SELECT c.ECInstanceId
|
|
838
|
-
FROM ECDbMeta.ECClassDef c
|
|
839
|
-
JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
|
|
840
|
-
WHERE s.Name=? AND c.Name=?
|
|
841
|
-
`;
|
|
842
|
-
const params = new core_common_1.QueryBinder();
|
|
843
|
-
const [schemaName, className] = classFullName.indexOf(".") !== -1
|
|
844
|
-
? classFullName.split(".")
|
|
845
|
-
: classFullName.split(":");
|
|
846
|
-
params.bindString(1, schemaName);
|
|
847
|
-
params.bindString(2, className);
|
|
848
|
-
const result = db.createQueryReader(sql, params, { usePrimaryConn: true });
|
|
849
|
-
if (await result.step())
|
|
850
|
-
return result.current.id;
|
|
851
|
-
(0, core_bentley_1.assert)(false, "relationship was not found");
|
|
852
298
|
}
|
|
853
299
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
854
300
|
* @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
|
|
855
301
|
* @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
|
|
856
302
|
*/
|
|
857
303
|
async shouldDetectDeletes() {
|
|
858
|
-
|
|
859
|
-
return this._syncType === "not-sync";
|
|
304
|
+
return (await this._syncTypeResolver.getSyncType()) === "not-sync";
|
|
860
305
|
}
|
|
861
306
|
/** Transform the specified sourceElement into ElementProps for the target iModel.
|
|
862
307
|
* @param sourceElement The Element from the source iModel to transform.
|
|
@@ -992,6 +437,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
992
437
|
throw new Error(`source-target element mapping not found for element "${sourceElementId}" when completing partially committed elements. This is a bug.`);
|
|
993
438
|
}
|
|
994
439
|
const targetProps = await this.onTransformElement(sourceElement);
|
|
440
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
995
441
|
this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
|
|
996
442
|
}
|
|
997
443
|
}
|
|
@@ -1003,6 +449,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1003
449
|
throw new Error(`source-target aspect mapping not found for aspect "${sourceAspectId}" when completing partially committed aspects. This is a bug.`);
|
|
1004
450
|
}
|
|
1005
451
|
const targetAspectProps = await this.onTransformElementAspect(sourceAspect);
|
|
452
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1006
453
|
this.targetDb.elements.updateAspect({
|
|
1007
454
|
...targetAspectProps,
|
|
1008
455
|
id: targetAspectId,
|
|
@@ -1134,6 +581,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1134
581
|
(!isElemInTarget || !(await dbHasModel(this.targetDb, idOfElemInTarget)));
|
|
1135
582
|
return { needsElemImport: !isElemInTarget, needsModelImport };
|
|
1136
583
|
}
|
|
584
|
+
// In iTwin js 5.x Elements.queryElementIdByCode() uses Code class to query id:
|
|
585
|
+
// https://github.com/iTwin/itwinjs-core/blob/master/core/backend/src/IModelDb.ts#L2779
|
|
586
|
+
// Code class constructor trims white spaces from code value.
|
|
587
|
+
// Custom implementation of queryElementIdByCode() was added to support querying elements with code values that have trailing whitespaces.
|
|
588
|
+
// It mimicks 4.x implementation: https://github.com/iTwin/itwinjs-core/blob/9c8b394ec3878a39764be81f928fd8b0b9115d31/core/backend/src/IModelDb.ts#L1882
|
|
589
|
+
async queryElementIdByCode(iModel, code) {
|
|
590
|
+
if (core_bentley_1.Id64.isInvalid(code.spec))
|
|
591
|
+
throw new Error("Invalid CodeSpec");
|
|
592
|
+
if (code.value === undefined)
|
|
593
|
+
throw new Error("Invalid Code");
|
|
594
|
+
const query = "SELECT ECInstanceId FROM BisCore:Element WHERE CodeSpec.Id=? AND CodeScope.Id=? AND CodeValue=?";
|
|
595
|
+
const queryBinder = new core_common_1.QueryBinder()
|
|
596
|
+
.bindId(1, code.spec)
|
|
597
|
+
.bindId(2, core_bentley_1.Id64.fromString(code.scope))
|
|
598
|
+
.bindString(3, code.value);
|
|
599
|
+
const queryReader = iModel.createQueryReader(query, queryBinder, {
|
|
600
|
+
usePrimaryConn: true,
|
|
601
|
+
});
|
|
602
|
+
return (await queryReader.step()) ? queryReader.current[0] : undefined;
|
|
603
|
+
}
|
|
1137
604
|
/** Override of [IModelExportHandler.onExportElement]($transformer) that imports an element into the target iModel when it is exported from the source iModel.
|
|
1138
605
|
* This override calls [[onTransformElement]] and then [IModelImporter.importElement]($transformer) to update the target iModel.
|
|
1139
606
|
*/
|
|
@@ -1217,6 +684,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1217
684
|
if (targetElementProps.id === undefined) {
|
|
1218
685
|
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "targetElementProps.id should be assigned by importElement");
|
|
1219
686
|
}
|
|
687
|
+
this._targetElementsImportedInCurrentTransform.add(targetElementProps.id);
|
|
1220
688
|
this.context.remapElement(sourceElement.id, targetElementProps.id);
|
|
1221
689
|
// the transformer does not currently 'split' or 'join' any elements, therefore, it does not
|
|
1222
690
|
// insert external source aspects because federation guids are sufficient for this.
|
|
@@ -1227,52 +695,36 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1227
695
|
// FIXME: make public and improve `initElementProvenance` API for usage by consolidators
|
|
1228
696
|
const provenanceDb = await this.getProvenanceDb();
|
|
1229
697
|
if (!this._options.noProvenance) {
|
|
1230
|
-
|
|
698
|
+
const provenance = this._options.forceExternalSourceAspectProvenance ||
|
|
1231
699
|
this._elementsWithExplicitlyTrackedProvenance.has(sourceElement.id)
|
|
1232
700
|
? undefined
|
|
1233
701
|
: sourceElement.federationGuid;
|
|
1234
702
|
if (!provenance) {
|
|
1235
703
|
const aspectProps = await this.initElementProvenance(sourceElement.id, targetElementProps.id);
|
|
1236
|
-
const foundEsaProps = await
|
|
704
|
+
const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
|
|
1237
705
|
if (foundEsaProps === undefined)
|
|
706
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1238
707
|
aspectProps.id = provenanceDb.elements.insertAspect(aspectProps);
|
|
1239
708
|
else {
|
|
1240
709
|
// Since initElementProvenance sets a property 'version' on the aspectProps that we wish to persist in the provenanceDb, only grab the id from the foundEsaProps.
|
|
1241
710
|
aspectProps.id = foundEsaProps.aspectId;
|
|
711
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1242
712
|
provenanceDb.elements.updateAspect(aspectProps);
|
|
1243
713
|
}
|
|
1244
|
-
provenance = aspectProps;
|
|
1245
714
|
}
|
|
1246
|
-
this.markLastProvenance(provenance, { isRelationship: false });
|
|
1247
715
|
}
|
|
1248
716
|
}
|
|
1249
|
-
// In iTwin js 5.x Elements.queryElementIdByCode() uses Code class to query id:
|
|
1250
|
-
// https://github.com/iTwin/itwinjs-core/blob/master/core/backend/src/IModelDb.ts#L2779
|
|
1251
|
-
// Code class constructor trims white spaces from code value.
|
|
1252
|
-
// Custom implementation of queryElementIdByCode() was added to support querying elements with code values that have trailing whitespaces.
|
|
1253
|
-
// It mimicks 4.x implementation: https://github.com/iTwin/itwinjs-core/blob/9c8b394ec3878a39764be81f928fd8b0b9115d31/core/backend/src/IModelDb.ts#L1882
|
|
1254
|
-
async queryElementIdByCode(iModel, code) {
|
|
1255
|
-
if (core_bentley_1.Id64.isInvalid(code.spec))
|
|
1256
|
-
throw new Error("Invalid CodeSpec");
|
|
1257
|
-
if (code.value === undefined)
|
|
1258
|
-
throw new Error("Invalid Code");
|
|
1259
|
-
const query = "SELECT ECInstanceId FROM BisCore:Element WHERE CodeSpec.Id=? AND CodeScope.Id=? AND CodeValue=?";
|
|
1260
|
-
const queryBinder = new core_common_1.QueryBinder()
|
|
1261
|
-
.bindId(1, code.spec)
|
|
1262
|
-
.bindId(2, core_bentley_1.Id64.fromString(code.scope))
|
|
1263
|
-
.bindString(3, code.value);
|
|
1264
|
-
const queryReader = iModel.createQueryReader(query, queryBinder, {
|
|
1265
|
-
usePrimaryConn: true,
|
|
1266
|
-
});
|
|
1267
|
-
return (await queryReader.step()) ? queryReader.current[0] : undefined;
|
|
1268
|
-
}
|
|
1269
717
|
/** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
|
|
1270
718
|
* This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
|
|
1271
719
|
*/
|
|
1272
720
|
async onDeleteElement(sourceElementId) {
|
|
1273
721
|
const targetElementId = this.context.findTargetElementId(sourceElementId);
|
|
1274
722
|
if (core_bentley_1.Id64.isValidId64(targetElementId)) {
|
|
1275
|
-
|
|
723
|
+
// Skip deletion if this target element was already imported (inserted/updated) during
|
|
724
|
+
// this transformation pass.
|
|
725
|
+
if (!this._targetElementsImportedInCurrentTransform.has(targetElementId)) {
|
|
726
|
+
await this.importer.deleteElement(targetElementId);
|
|
727
|
+
}
|
|
1276
728
|
}
|
|
1277
729
|
}
|
|
1278
730
|
/** Override of [IModelExportHandler.onExportModel]($transformer) that is called when a Model should be exported from the source iModel.
|
|
@@ -1408,95 +860,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1408
860
|
targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
|
|
1409
861
|
return targetModelProps;
|
|
1410
862
|
}
|
|
1411
|
-
/**
|
|
1412
|
-
* Called at the end of a transformation,
|
|
1413
|
-
* updates the target scope element to say that transformation up through the
|
|
1414
|
-
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1415
|
-
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
1416
|
-
*
|
|
1417
|
-
* You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
|
|
1418
|
-
* It is public for unsupported use cases of custom synchronization transforms.
|
|
1419
|
-
* @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
|
|
1420
|
-
* unless the `initializeReverseSyncVersion` option is set to `true`
|
|
1421
|
-
*
|
|
1422
|
-
* The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
|
|
1423
|
-
* 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.
|
|
1424
|
-
* 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.
|
|
1425
|
-
*
|
|
1426
|
-
* Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
|
|
1427
|
-
* 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.
|
|
1428
|
-
*/
|
|
1429
|
-
async updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
|
|
1430
|
-
const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
|
|
1431
|
-
this._sourceChangeDataState !== "has-changes";
|
|
1432
|
-
if (shouldSkipSyncVersionUpdate)
|
|
1433
|
-
return;
|
|
1434
|
-
nodeAssert(this._targetScopeProvenanceProps);
|
|
1435
|
-
const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
|
|
1436
|
-
const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
|
|
1437
|
-
if (await this.getIsReverseSynchronization()) {
|
|
1438
|
-
const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
|
|
1439
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
|
|
1440
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1441
|
-
sourceVersion;
|
|
1442
|
-
}
|
|
1443
|
-
else {
|
|
1444
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
|
|
1445
|
-
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1446
|
-
// save reverse sync version
|
|
1447
|
-
if (initializeReverseSyncVersion) {
|
|
1448
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
|
|
1449
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1450
|
-
targetVersion;
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
if (this._options.argsForProcessChanges ||
|
|
1454
|
-
(this._startingChangesetIndices && initializeReverseSyncVersion)) {
|
|
1455
|
-
nodeAssert(this.targetDb.changeset.index !== undefined &&
|
|
1456
|
-
this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
|
|
1457
|
-
// Store in a local variable, so typescript knows it's defined (due to the assert above)
|
|
1458
|
-
const startingChangesetIndices = this._startingChangesetIndices;
|
|
1459
|
-
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
1460
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1461
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1462
|
-
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1463
|
-
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1464
|
-
// Determine which keys to clear and update based on the synchronization direction
|
|
1465
|
-
let syncChangesetsToClearKey;
|
|
1466
|
-
let syncChangesetsToUpdateKey;
|
|
1467
|
-
if (await this.getIsReverseSynchronization()) {
|
|
1468
|
-
syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
|
|
1469
|
-
syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
|
|
1470
|
-
}
|
|
1471
|
-
else {
|
|
1472
|
-
syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
|
|
1473
|
-
syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
|
|
1474
|
-
}
|
|
1475
|
-
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1476
|
-
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1477
|
-
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1478
|
-
// stuff to it which would break future synchronizations
|
|
1479
|
-
for (let i = startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1480
|
-
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1481
|
-
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
1482
|
-
jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
|
|
1483
|
-
return csIndex > startingChangesetIndices.source;
|
|
1484
|
-
});
|
|
1485
|
-
// if reverse sync then we may have received provenance changes which should be marked as sync changes
|
|
1486
|
-
if (await this.getIsReverseSynchronization()) {
|
|
1487
|
-
nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
|
|
1488
|
-
for (let i = startingChangesetIndices.source + 1; i <= this.sourceDb.changeset.index + 1; i++)
|
|
1489
|
-
jsonProps.pendingReverseSyncChangesetIndices.push(i);
|
|
1490
|
-
}
|
|
1491
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `new pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1492
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `new pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1493
|
-
}
|
|
1494
|
-
(await this.getProvenanceDb()).elements.updateAspect({
|
|
1495
|
-
...this._targetScopeProvenanceProps,
|
|
1496
|
-
jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
|
|
1497
|
-
});
|
|
1498
|
-
this.clearCachedSynchronizationVersion();
|
|
1499
|
-
}
|
|
1500
863
|
// FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
|
|
1501
864
|
async finalizeTransformation() {
|
|
1502
865
|
this.importer.finalize();
|
|
@@ -1541,19 +904,19 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1541
904
|
const provenanceDb = await this.getProvenanceDb();
|
|
1542
905
|
if (!this._options.noProvenance &&
|
|
1543
906
|
core_bentley_1.Id64.isValid(targetRelationshipInstanceId)) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const
|
|
907
|
+
const needsEsaProvenance = !this._options
|
|
908
|
+
.forceExternalSourceAspectProvenance
|
|
909
|
+
? !(sourceFedGuid && targetFedGuid)
|
|
910
|
+
: true;
|
|
911
|
+
if (needsEsaProvenance) {
|
|
912
|
+
const aspectProps = await this._provenanceManager.initRelationshipProvenance(sourceRelationship.id, targetRelationshipInstanceId, this._forceOldRelationshipProvenanceMethod);
|
|
913
|
+
const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
|
|
1550
914
|
// 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).
|
|
1551
915
|
if (undefined === foundEsaProps) {
|
|
916
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1552
917
|
aspectProps.id = provenanceDb.elements.insertAspect(aspectProps);
|
|
1553
918
|
}
|
|
1554
|
-
provenance = aspectProps;
|
|
1555
919
|
}
|
|
1556
|
-
this.markLastProvenance(provenance, { isRelationship: true });
|
|
1557
920
|
}
|
|
1558
921
|
}
|
|
1559
922
|
/** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
|
|
@@ -1580,6 +943,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1580
943
|
}
|
|
1581
944
|
if (deletedRelData.provenanceAspectId) {
|
|
1582
945
|
try {
|
|
946
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1583
947
|
(await this.getProvenanceDb()).elements.deleteAspect(deletedRelData.provenanceAspectId);
|
|
1584
948
|
}
|
|
1585
949
|
catch (error) {
|
|
@@ -1601,6 +965,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1601
965
|
targetRelationshipProps.sourceId = this.context.findTargetElementId(sourceRelationship.sourceId);
|
|
1602
966
|
targetRelationshipProps.targetId = this.context.findTargetElementId(sourceRelationship.targetId);
|
|
1603
967
|
// TODO: move to cloneRelationship in IModelCloneContext
|
|
968
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1604
969
|
sourceRelationship.forEach((propertyName, property) => {
|
|
1605
970
|
if (property.isPrimitive() && "Id" === property.extendedTypeName) {
|
|
1606
971
|
targetRelationshipProps[core_common_1.ECJsNames.toJsName(propertyName)] =
|
|
@@ -1816,7 +1181,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1816
1181
|
* @returns void
|
|
1817
1182
|
*/
|
|
1818
1183
|
async processChangesets() {
|
|
1819
|
-
await this.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1184
|
+
await this._provenanceManager.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1820
1185
|
this.context.remapElement(sourceElementId, targetElementId);
|
|
1821
1186
|
});
|
|
1822
1187
|
if (this.exporter.sourceDbChanges)
|
|
@@ -1857,13 +1222,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1857
1222
|
db: this.sourceDb,
|
|
1858
1223
|
disableSchemaCheck: true,
|
|
1859
1224
|
});
|
|
1225
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1860
1226
|
const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
|
|
1227
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1861
1228
|
const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier(this.sourceDb);
|
|
1862
1229
|
while (csAdaptor.step()) {
|
|
1863
1230
|
ecChangeUnifier.appendFrom(csAdaptor);
|
|
1864
1231
|
}
|
|
1232
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1865
1233
|
const changes = [...ecChangeUnifier.instances];
|
|
1866
1234
|
/** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
|
|
1235
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1867
1236
|
const elemIdToScopeEsa = new Map();
|
|
1868
1237
|
for (const change of changes) {
|
|
1869
1238
|
if (change.ECClassId !== undefined &&
|
|
@@ -1913,7 +1282,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1913
1282
|
* @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
|
|
1914
1283
|
* @returns void
|
|
1915
1284
|
*/
|
|
1916
|
-
async processDeletedOp(
|
|
1285
|
+
async processDeletedOp(
|
|
1286
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1287
|
+
change,
|
|
1288
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1289
|
+
mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1917
1290
|
// we need a connected iModel with changes to remap elements with deletions
|
|
1918
1291
|
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1919
1292
|
const noChanges = (await this.getSynchronizationVersion()).index ===
|
|
@@ -1974,8 +1347,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1974
1347
|
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
1975
1348
|
});
|
|
1976
1349
|
}
|
|
1977
|
-
else if (this.sourceDb ===
|
|
1978
|
-
|
|
1350
|
+
else if (this.sourceDb ===
|
|
1351
|
+
(await this._provenanceManager.getProvenanceSourceDb())) {
|
|
1352
|
+
const relProvenance = await this._provenanceManager.queryProvenanceForRelationship(changedInstanceId, {
|
|
1979
1353
|
classFullName: classFullName ?? "",
|
|
1980
1354
|
sourceId: sourceIdOfRelationshipInSource,
|
|
1981
1355
|
targetId: targetIdOfRelationshipInSource,
|
|
@@ -1991,8 +1365,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1991
1365
|
else {
|
|
1992
1366
|
let targetId = await getTargetIdFromSourceId(changedInstanceId);
|
|
1993
1367
|
if (targetId === undefined &&
|
|
1994
|
-
this.sourceDb ===
|
|
1995
|
-
|
|
1368
|
+
this.sourceDb ===
|
|
1369
|
+
(await this._provenanceManager.getProvenanceSourceDb())) {
|
|
1370
|
+
targetId =
|
|
1371
|
+
await this._provenanceManager.queryProvenanceForElement(changedInstanceId);
|
|
1996
1372
|
}
|
|
1997
1373
|
// since we are processing one changeset at a time, we can see local source deletes
|
|
1998
1374
|
// of entities that were never synced and can be safely ignored
|
|
@@ -2051,12 +1427,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2051
1427
|
` which is changeset #${syncVersion.index}. The transformer expected` +
|
|
2052
1428
|
` #${syncVersion.index + 1}.`);
|
|
2053
1429
|
}
|
|
2054
|
-
|
|
2055
|
-
const changesetsToSkip = (await this.getIsReverseSynchronization())
|
|
2056
|
-
? this._targetScopeProvenanceProps.jsonProperties
|
|
2057
|
-
.pendingReverseSyncChangesetIndices
|
|
2058
|
-
: this._targetScopeProvenanceProps.jsonProperties
|
|
2059
|
-
.pendingSyncChangesetIndices;
|
|
1430
|
+
const changesetsToSkip = await this._provenanceManager.getChangesetsToSkip();
|
|
2060
1431
|
core_bentley_1.Logger.logTrace(loggerCategory, `changesets to skip: ${changesetsToSkip}`);
|
|
2061
1432
|
this._changesetRanges = (0, Algo_1.rangesFromRangeAndSkipped)(startChangesetIndex, endChangesetIndex, changesetsToSkip);
|
|
2062
1433
|
core_bentley_1.Logger.logTrace(loggerCategory, `ranges: ${this._changesetRanges}`);
|
|
@@ -2109,6 +1480,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2109
1480
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2110
1481
|
*/
|
|
2111
1482
|
async processAll() {
|
|
1483
|
+
this._targetElementsImportedInCurrentTransform.clear();
|
|
1484
|
+
// processAll always has changes to process, so mark it as such for version tracking
|
|
1485
|
+
this._sourceChangeDataState = "has-changes";
|
|
2112
1486
|
await this.exporter.exportCodeSpecs();
|
|
2113
1487
|
await this.exporter.exportFonts();
|
|
2114
1488
|
if (this._options.skipPropagateChangesToRootElements) {
|
|
@@ -2133,21 +1507,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2133
1507
|
this.importer.computeProjectExtents();
|
|
2134
1508
|
await this.finalizeTransformation();
|
|
2135
1509
|
}
|
|
2136
|
-
/** previous provenance, either a federation guid, a `${sourceFedGuid}/${targetFedGuid}` pair, or required aspect props */
|
|
2137
|
-
_lastProvenanceEntityInfo = nullLastProvenanceEntityInfo;
|
|
2138
|
-
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
2139
|
-
this._lastProvenanceEntityInfo =
|
|
2140
|
-
typeof sourceAspect === "string"
|
|
2141
|
-
? sourceAspect
|
|
2142
|
-
: {
|
|
2143
|
-
entityId: sourceAspect.element.id,
|
|
2144
|
-
aspectId: sourceAspect.id,
|
|
2145
|
-
aspectVersion: sourceAspect.version ?? "",
|
|
2146
|
-
aspectKind: isRelationship
|
|
2147
|
-
? core_backend_1.ExternalSourceAspect.Kind.Relationship
|
|
2148
|
-
: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
2149
|
-
};
|
|
2150
|
-
}
|
|
2151
1510
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2152
1511
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2153
1512
|
* @note the transformer assumes that you saveChanges after processing changes. You should not
|
|
@@ -2158,6 +1517,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2158
1517
|
* @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.
|
|
2159
1518
|
*/
|
|
2160
1519
|
async processChanges(options) {
|
|
1520
|
+
this._targetElementsImportedInCurrentTransform.clear();
|
|
2161
1521
|
// must wait for initialization of synchronization provenance data
|
|
2162
1522
|
await this.exporter.exportChanges(await this.getExportInitOpts(options));
|
|
2163
1523
|
await this.completePartiallyCommittedElements();
|
|
@@ -2167,6 +1527,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2167
1527
|
this.importer.computeProjectExtents();
|
|
2168
1528
|
await this.finalizeTransformation();
|
|
2169
1529
|
const defaultSaveTargetChanges = () => {
|
|
1530
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
2170
1531
|
this.targetDb.saveChanges();
|
|
2171
1532
|
};
|
|
2172
1533
|
await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|