@itwin/imodel-transformer 1.0.0-dev.2 → 1.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/Algo.d.ts +7 -0
- package/lib/cjs/Algo.d.ts.map +1 -1
- package/lib/cjs/Algo.js +7 -0
- package/lib/cjs/Algo.js.map +1 -1
- package/lib/cjs/BigMap.d.ts +6 -1
- package/lib/cjs/BigMap.d.ts.map +1 -1
- package/lib/cjs/BigMap.js +28 -2
- package/lib/cjs/BigMap.js.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +2 -0
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
- package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
- package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
- package/lib/cjs/EntityUnifier.d.ts.map +1 -1
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +16 -1
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +5 -6
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +15 -12
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +27 -4
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +61 -23
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +113 -150
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +377 -340
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/transformer.js +2 -1
- package/lib/cjs/transformer.js.map +1 -1
- package/package.json +17 -16
- package/lib/cjs/PendingReferenceMap.d.ts +0 -37
- package/lib/cjs/PendingReferenceMap.d.ts.map +0 -1
- package/lib/cjs/PendingReferenceMap.js +0 -92
- package/lib/cjs/PendingReferenceMap.js.map +0 -1
|
@@ -13,13 +13,12 @@ const Semver = require("semver");
|
|
|
13
13
|
const nodeAssert = require("assert");
|
|
14
14
|
const core_bentley_1 = require("@itwin/core-bentley");
|
|
15
15
|
const core_geometry_1 = require("@itwin/core-geometry");
|
|
16
|
+
const coreBackendPkgJson = require("@itwin/core-backend/package.json");
|
|
16
17
|
const core_backend_1 = require("@itwin/core-backend");
|
|
17
18
|
const core_common_1 = require("@itwin/core-common");
|
|
18
19
|
const IModelExporter_1 = require("./IModelExporter");
|
|
19
20
|
const IModelImporter_1 = require("./IModelImporter");
|
|
20
21
|
const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
|
|
21
|
-
const PendingReferenceMap_1 = require("./PendingReferenceMap");
|
|
22
|
-
const EntityMap_1 = require("./EntityMap");
|
|
23
22
|
const IModelCloneContext_1 = require("./IModelCloneContext");
|
|
24
23
|
const EntityUnifier_1 = require("./EntityUnifier");
|
|
25
24
|
const Algo_1 = require("./Algo");
|
|
@@ -30,30 +29,6 @@ const nullLastProvenanceEntityInfo = {
|
|
|
30
29
|
aspectVersion: "",
|
|
31
30
|
aspectKind: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
32
31
|
};
|
|
33
|
-
/**
|
|
34
|
-
* A container for tracking the state of a partially committed entity and finalizing it when it's ready to be fully committed
|
|
35
|
-
* @internal
|
|
36
|
-
*/
|
|
37
|
-
class PartiallyCommittedEntity {
|
|
38
|
-
constructor(
|
|
39
|
-
/**
|
|
40
|
-
* A set of "model|element ++ ID64" pairs, (e.g. `model0x11` or `element0x12`)
|
|
41
|
-
* It is possible for the submodel of an element to be separately resolved from the actual element,
|
|
42
|
-
* so its resolution must be tracked separately
|
|
43
|
-
*/
|
|
44
|
-
_missingReferences, _onComplete) {
|
|
45
|
-
this._missingReferences = _missingReferences;
|
|
46
|
-
this._onComplete = _onComplete;
|
|
47
|
-
}
|
|
48
|
-
resolveReference(id) {
|
|
49
|
-
this._missingReferences.delete(id);
|
|
50
|
-
if (this._missingReferences.size === 0)
|
|
51
|
-
this._onComplete();
|
|
52
|
-
}
|
|
53
|
-
forceComplete() {
|
|
54
|
-
this._onComplete();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
32
|
/**
|
|
58
33
|
* Apply a function to each Id64 in a supported container type of Id64s.
|
|
59
34
|
* Currently only supports raw Id64String or RelatedElement-like objects containing an `id` property that is a Id64String,
|
|
@@ -80,7 +55,7 @@ function mapId64(idContainer, func) {
|
|
|
80
55
|
}
|
|
81
56
|
else {
|
|
82
57
|
throw Error([
|
|
83
|
-
`Id64 container '${idContainer}' is unsupported.`,
|
|
58
|
+
`Id64 container '${JSON.stringify(idContainer)}' is unsupported.`,
|
|
84
59
|
"Currently only singular Id64 strings or prop-like objects containing an 'id' property are supported.",
|
|
85
60
|
].join("\n"));
|
|
86
61
|
}
|
|
@@ -149,7 +124,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
149
124
|
id: targetScopeElementId,
|
|
150
125
|
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
151
126
|
},
|
|
152
|
-
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
127
|
+
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
153
128
|
identifier: sourceDb.iModelId,
|
|
154
129
|
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
155
130
|
jsonProperties: undefined,
|
|
@@ -171,7 +146,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
171
146
|
if (this._isProvenanceInitTransform) {
|
|
172
147
|
return "forward";
|
|
173
148
|
}
|
|
174
|
-
if (!this.
|
|
149
|
+
if (!this._options.argsForProcessChanges) {
|
|
175
150
|
return "not-sync";
|
|
176
151
|
}
|
|
177
152
|
try {
|
|
@@ -216,14 +191,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
216
191
|
*/
|
|
217
192
|
constructor(source, target, options) {
|
|
218
193
|
super();
|
|
219
|
-
/** map of (unprocessed element, referencing processed element) pairs to the partially committed element that needs the reference resolved
|
|
220
|
-
* and have some helper methods below for now */
|
|
221
|
-
this._pendingReferences = new PendingReferenceMap_1.PendingReferenceMap();
|
|
222
194
|
/** a set of elements for which source provenance will be explicitly tracked by ExternalSourceAspects */
|
|
223
195
|
this._elementsWithExplicitlyTrackedProvenance = new Set();
|
|
224
|
-
|
|
225
|
-
this.
|
|
226
|
-
this._isSynchronization = false;
|
|
196
|
+
this._partiallyCommittedElementIds = new Set();
|
|
197
|
+
this._partiallyCommittedAspectIds = new Set();
|
|
227
198
|
/**
|
|
228
199
|
* A private variable meant to be set by tests which have an outdated way of setting up transforms. In all synchronizations today we expect to find an ESA in the branch db which describes the master -> branch relationship.
|
|
229
200
|
* The exception to this is the first transform aka the provenance initializing transform which requires that the master imodel and the branch imodel are identical at the time of provenance initialization.
|
|
@@ -232,10 +203,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
232
203
|
*/
|
|
233
204
|
this._allowNoScopingESA = false;
|
|
234
205
|
this._changesetRanges = undefined;
|
|
235
|
-
/** Set of entity keys which were not exported and don't need to be tracked for pending reference resolution.
|
|
236
|
-
* @note Currently only tracks elements which were not exported.
|
|
237
|
-
*/
|
|
238
|
-
this._skippedEntities = new Set();
|
|
239
206
|
/**
|
|
240
207
|
* Previously the transformer would insert provenance always pointing to the "target" relationship.
|
|
241
208
|
* It should (and now by default does) instead insert provenance pointing to the provenanceSource
|
|
@@ -273,11 +240,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
273
240
|
cloneUsingBinaryGeometry: options?.cloneUsingBinaryGeometry ?? true,
|
|
274
241
|
targetScopeElementId: options?.targetScopeElementId ?? core_common_1.IModel.rootSubjectId,
|
|
275
242
|
// eslint-disable-next-line deprecation/deprecation
|
|
276
|
-
danglingReferencesBehavior: options?.danglingReferencesBehavior ??
|
|
277
|
-
options?.danglingPredecessorsBehavior ??
|
|
278
|
-
"reject",
|
|
243
|
+
danglingReferencesBehavior: options?.danglingReferencesBehavior ?? "reject",
|
|
279
244
|
branchRelationshipDataBehavior: options?.branchRelationshipDataBehavior ?? "reject",
|
|
245
|
+
skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
|
|
280
246
|
};
|
|
247
|
+
// check if authorization client is defined
|
|
248
|
+
if (core_backend_1.IModelHost.authorizationClient === undefined) {
|
|
249
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "Authorization client is not set in IModelHost. If the transformer needs an accessToken, then it will fail.");
|
|
250
|
+
}
|
|
281
251
|
this._isProvenanceInitTransform = this._options
|
|
282
252
|
.wasSourceIModelCopiedToTarget
|
|
283
253
|
? true
|
|
@@ -361,8 +331,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
361
331
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._includeSourceProvenance=${this._options.includeSourceProvenance}`);
|
|
362
332
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._cloneUsingBinaryGeometry=${this._options.cloneUsingBinaryGeometry}`);
|
|
363
333
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._wasSourceIModelCopiedToTarget=${this._options.wasSourceIModelCopiedToTarget}`);
|
|
364
|
-
core_bentley_1.Logger.logInfo(
|
|
365
|
-
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${this.importer.options.autoExtendProjectExtents}`);
|
|
334
|
+
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
366
335
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
367
336
|
}
|
|
368
337
|
/** Return the IModelDb where IModelTransformer will store its provenance.
|
|
@@ -455,29 +424,30 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
455
424
|
forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
|
|
456
425
|
});
|
|
457
426
|
}
|
|
458
|
-
/**
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* @note: must call [[initScopeProvenance]] before using this property.
|
|
462
|
-
* @note: empty string and -1 for changeset and index if it has never been transformed or was transformed before federation guid update (pre 1.x).
|
|
427
|
+
/**
|
|
428
|
+
* As of itwinjs 4.6.0, definitionContainers are now deleted as if they were DefinitionPartitions as opposed to Definitions.
|
|
429
|
+
* This variable being true will be used to special case the deletion of DefinitionContainers the same way DefinitionPartitions are deleted.
|
|
463
430
|
*/
|
|
464
|
-
get
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
const version = this.isReverseSynchronization
|
|
468
|
-
? this._targetScopeProvenanceProps.jsonProperties?.reverseSyncVersion
|
|
469
|
-
: this._targetScopeProvenanceProps.version;
|
|
470
|
-
nodeAssert(version !== undefined, "no version contained in target scope");
|
|
471
|
-
const [id, index] = version === "" ? ["", -1] : version.split(";");
|
|
472
|
-
this._cachedSynchronizationVersion = { index: Number(index), id };
|
|
473
|
-
nodeAssert(!Number.isNaN(this._cachedSynchronizationVersion.index), "bad parse: invalid index in version");
|
|
431
|
+
get hasDefinitionContainerDeletionFeature() {
|
|
432
|
+
if (this._hasDefinitionContainerDeletionFeature === undefined) {
|
|
433
|
+
this._hasDefinitionContainerDeletionFeature = Semver.satisfies(coreBackendPkgJson.version, "^4.6.0");
|
|
474
434
|
}
|
|
475
|
-
return this.
|
|
435
|
+
return this._hasDefinitionContainerDeletionFeature;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* We cache the synchronization version to avoid querying the target scoping ESA multiple times.
|
|
439
|
+
* If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
|
|
440
|
+
* Sets this._cachedSynchronizationVersion to undefined.
|
|
441
|
+
*/
|
|
442
|
+
clearCachedSynchronizationVersion() {
|
|
443
|
+
this._cachedSynchronizationVersion = undefined;
|
|
476
444
|
}
|
|
477
445
|
/** the changeset in the scoping element's source version found for this transformation
|
|
478
|
-
* @note
|
|
446
|
+
* @note the version depends on whether this is a reverse synchronization or not, as
|
|
479
447
|
* it is stored separately for both synchronization directions.
|
|
480
|
-
* @note
|
|
448
|
+
* @note empty string and -1 for changeset and index if it has never been transformed
|
|
449
|
+
* @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".
|
|
450
|
+
* @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
|
|
481
451
|
*/
|
|
482
452
|
get synchronizationVersion() {
|
|
483
453
|
if (this._cachedSynchronizationVersion === undefined) {
|
|
@@ -488,10 +458,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
488
458
|
const version = this.isReverseSynchronization
|
|
489
459
|
? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
|
|
490
460
|
: provenanceScopeAspect.version;
|
|
491
|
-
if (!version
|
|
461
|
+
if (!version &&
|
|
462
|
+
this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
|
|
492
463
|
return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
|
|
493
464
|
}
|
|
494
|
-
|
|
465
|
+
if (version === undefined) {
|
|
466
|
+
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.
|
|
467
|
+
Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
|
|
468
|
+
}
|
|
469
|
+
const [id, index] = version === "" ? ["", -1] : version.split(";");
|
|
495
470
|
if (Number.isNaN(Number(index)))
|
|
496
471
|
throw new Error("Could not parse version data from scope aspect");
|
|
497
472
|
this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
|
|
@@ -530,7 +505,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
530
505
|
id: this.targetScopeElementId,
|
|
531
506
|
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
532
507
|
},
|
|
533
|
-
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
508
|
+
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
534
509
|
identifier: this.provenanceSourceDb.iModelId,
|
|
535
510
|
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
536
511
|
jsonProperties: undefined,
|
|
@@ -567,29 +542,82 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
567
542
|
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
568
543
|
});
|
|
569
544
|
aspectProps.id = id;
|
|
545
|
+
// Busting a potential cached version
|
|
546
|
+
this.clearCachedSynchronizationVersion();
|
|
570
547
|
}
|
|
571
548
|
}
|
|
572
549
|
else {
|
|
573
550
|
// foundEsaProps is defined.
|
|
574
551
|
aspectProps.id = foundEsaProps.aspectId;
|
|
575
|
-
aspectProps.version =
|
|
576
|
-
foundEsaProps.version ??
|
|
577
|
-
(this._options.branchRelationshipDataBehavior === "unsafe-migrate"
|
|
578
|
-
? ""
|
|
579
|
-
: undefined);
|
|
552
|
+
aspectProps.version = foundEsaProps.version;
|
|
580
553
|
aspectProps.jsonProperties = foundEsaProps.jsonProperties
|
|
581
554
|
? JSON.parse(foundEsaProps.jsonProperties)
|
|
582
|
-
:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
+
}
|
|
589
567
|
}
|
|
590
568
|
this._targetScopeProvenanceProps =
|
|
591
569
|
aspectProps;
|
|
592
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;
|
|
620
|
+
}
|
|
593
621
|
/**
|
|
594
622
|
* Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
|
|
595
623
|
* and call a function for each one.
|
|
@@ -685,7 +713,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
685
713
|
targetScopeElementId: this.targetScopeElementId,
|
|
686
714
|
isReverseSynchronization: this.isReverseSynchronization,
|
|
687
715
|
fn,
|
|
688
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ??
|
|
716
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
|
|
689
717
|
});
|
|
690
718
|
}
|
|
691
719
|
/**
|
|
@@ -807,7 +835,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
807
835
|
}
|
|
808
836
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
809
837
|
* @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
|
|
810
|
-
* @note Not relevant for
|
|
838
|
+
* @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
|
|
811
839
|
*/
|
|
812
840
|
shouldDetectDeletes() {
|
|
813
841
|
nodeAssert(this._syncType !== undefined);
|
|
@@ -817,9 +845,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
817
845
|
* Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements
|
|
818
846
|
* in the source iModel.
|
|
819
847
|
* @deprecated in 1.x. Do not use this. // FIXME<MIKE>: how to better explain this?
|
|
820
|
-
* This method is only called during [[
|
|
848
|
+
* This method is only called during [[process]] when [[IModelTransformOptions.argsForProcessChanges]] is undefined and the option
|
|
821
849
|
* [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
|
|
822
|
-
* necessary when
|
|
850
|
+
* necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] defined, since changeset information is sufficient.
|
|
823
851
|
* @note you do not need to call this directly unless processing a subset of an iModel.
|
|
824
852
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
825
853
|
*/
|
|
@@ -849,12 +877,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
849
877
|
}
|
|
850
878
|
});
|
|
851
879
|
}
|
|
852
|
-
/**
|
|
853
|
-
* @deprecated in 3.x, this no longer has any effect except emitting a warning
|
|
854
|
-
*/
|
|
855
|
-
skipElement(_sourceElement) {
|
|
856
|
-
core_bentley_1.Logger.logWarning(loggerCategory, "Tried to defer/skip an element, which is no longer necessary");
|
|
857
|
-
}
|
|
858
880
|
/** Transform the specified sourceElement into ElementProps for the target iModel.
|
|
859
881
|
* @param sourceElement The Element from the source iModel to transform.
|
|
860
882
|
* @returns ElementProps for the target iModel.
|
|
@@ -888,10 +910,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
888
910
|
}
|
|
889
911
|
/** Returns true if a change within sourceElement is detected.
|
|
890
912
|
* @param sourceElement The Element from the source iModel
|
|
891
|
-
* @param targetElementId The Element from the target iModel to compare against.
|
|
892
913
|
* @note A subclass can override this method to provide custom change detection behavior.
|
|
893
914
|
*/
|
|
894
|
-
hasElementChanged(sourceElement
|
|
915
|
+
hasElementChanged(sourceElement) {
|
|
895
916
|
if (this._sourceChangeDataState === "no-changes")
|
|
896
917
|
return false;
|
|
897
918
|
if (this._sourceChangeDataState === "unconnected")
|
|
@@ -900,85 +921,74 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
900
921
|
nodeAssert(this._hasElementChangedCache !== undefined, "has element changed cache should be initialized by now");
|
|
901
922
|
return this._hasElementChangedCache.has(sourceElement.id);
|
|
902
923
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
(0, core_bentley_1.assert)(false, `unreachable; entity was '${entity.constructor.name}' not an Element, Relationship, or ElementAspect`);
|
|
914
|
-
}
|
|
915
|
-
/** callback to perform when a partial element says it's ready to be completed
|
|
916
|
-
* transforms the source element with all references now valid, then updates the partial element with the results
|
|
917
|
-
*/
|
|
918
|
-
makePartialEntityCompleter(sourceEntity) {
|
|
919
|
-
return () => {
|
|
920
|
-
const targetId = this.context.findTargetEntityId(core_backend_1.EntityReferences.from(sourceEntity));
|
|
921
|
-
if (!core_backend_1.EntityReferences.isValid(targetId))
|
|
922
|
-
throw Error(`${sourceEntity.id} has not been inserted into the target yet, the completer is invalid. This is a bug.`);
|
|
923
|
-
const onEntityTransform = IModelTransformer.transformCallbackFor(this, sourceEntity);
|
|
924
|
-
const updateEntity = EntityUnifier_1.EntityUnifier.updaterFor(this.targetDb, sourceEntity);
|
|
925
|
-
const targetProps = onEntityTransform.call(this, sourceEntity);
|
|
926
|
-
if (sourceEntity instanceof core_backend_1.Relationship) {
|
|
927
|
-
targetProps.sourceId =
|
|
928
|
-
this.context.findTargetElementId(sourceEntity.sourceId);
|
|
929
|
-
targetProps.targetId =
|
|
930
|
-
this.context.findTargetElementId(sourceEntity.targetId);
|
|
924
|
+
completePartiallyCommittedElements() {
|
|
925
|
+
for (const sourceElementId of this._partiallyCommittedElementIds) {
|
|
926
|
+
const sourceElement = this.sourceDb.elements.getElement({
|
|
927
|
+
id: sourceElementId,
|
|
928
|
+
wantGeometry: this.exporter.wantGeometry,
|
|
929
|
+
wantBRepData: this.exporter.wantGeometry,
|
|
930
|
+
});
|
|
931
|
+
const targetId = this.context.findTargetElementId(sourceElementId);
|
|
932
|
+
if (core_bentley_1.Id64.isInvalid(targetId)) {
|
|
933
|
+
throw new Error(`source-target element mapping not found for element "${sourceElementId}" when completing partially committed elements. This is a bug.`);
|
|
931
934
|
}
|
|
932
|
-
|
|
933
|
-
this.
|
|
934
|
-
}
|
|
935
|
+
const targetProps = this.onTransformElement(sourceElement);
|
|
936
|
+
this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
|
|
937
|
+
}
|
|
935
938
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
this._skippedEntities.has(referenceId);
|
|
948
|
-
if (alreadyProcessed)
|
|
949
|
-
continue;
|
|
950
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `Deferring resolution of reference '${referenceId}' of element '${entity.id}'`);
|
|
951
|
-
const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
|
|
952
|
-
entityReference: referenceId,
|
|
939
|
+
completePartiallyCommittedAspects() {
|
|
940
|
+
for (const sourceAspectId of this._partiallyCommittedAspectIds) {
|
|
941
|
+
const sourceAspect = this.sourceDb.elements.getAspect(sourceAspectId);
|
|
942
|
+
const targetAspectId = this.context.findTargetAspectId(sourceAspectId);
|
|
943
|
+
if (core_bentley_1.Id64.isInvalid(targetAspectId)) {
|
|
944
|
+
throw new Error(`source-target aspect mapping not found for aspect "${sourceAspectId}" when completing partially committed aspects. This is a bug.`);
|
|
945
|
+
}
|
|
946
|
+
const targetAspectProps = this.onTransformElementAspect(sourceAspect);
|
|
947
|
+
this.targetDb.elements.updateAspect({
|
|
948
|
+
...targetAspectProps,
|
|
949
|
+
id: targetAspectId,
|
|
953
950
|
});
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
doAllReferencesExistInTarget(entity) {
|
|
954
|
+
let allReferencesExist = true;
|
|
955
|
+
for (const referenceId of entity.getReferenceIds()) {
|
|
956
|
+
const referencedEntityId = core_backend_1.EntityReferences.toId64(referenceId);
|
|
957
|
+
if (referencedEntityId === core_common_1.IModel.repositoryModelId ||
|
|
958
|
+
referencedEntityId === core_common_1.IModel.dictionaryId ||
|
|
959
|
+
referencedEntityId === "0xe") {
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
if (allReferencesExist &&
|
|
963
|
+
!core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
|
|
964
|
+
// if we care about references existing then we cannot return early and must check all other references.
|
|
965
|
+
if (this._options.danglingReferencesBehavior === "ignore") {
|
|
966
|
+
return false;
|
|
967
967
|
}
|
|
968
|
+
allReferencesExist = false;
|
|
968
969
|
}
|
|
969
|
-
if (
|
|
970
|
-
|
|
971
|
-
if (!this._partiallyCommittedEntities.has(entity))
|
|
972
|
-
this._partiallyCommittedEntities.set(entity, thisPartialElem);
|
|
970
|
+
if (this._options.danglingReferencesBehavior === "reject") {
|
|
971
|
+
this.assertReferenceExistsInSource(referenceId, entity);
|
|
973
972
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
973
|
+
}
|
|
974
|
+
return allReferencesExist;
|
|
975
|
+
}
|
|
976
|
+
assertReferenceExistsInSource(referenceId, entity) {
|
|
977
|
+
const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
|
|
978
|
+
entityReference: referenceId,
|
|
979
|
+
});
|
|
980
|
+
if (!referencedExistsInSource) {
|
|
981
|
+
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, [
|
|
982
|
+
`Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
|
|
983
|
+
"This must have been caused by an upstream application that changed the iModel.",
|
|
984
|
+
"You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this,",
|
|
985
|
+
`and the referenceId found on "${entity.id}" will not be carried over to corresponding target element.`,
|
|
986
|
+
].join("\n"));
|
|
977
987
|
}
|
|
978
988
|
}
|
|
979
989
|
/** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
|
|
980
990
|
* @param sourceElementId Identifies the Element from the source iModel to import.
|
|
981
|
-
* @note This method is called from [[
|
|
991
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
982
992
|
*/
|
|
983
993
|
async processElement(sourceElementId) {
|
|
984
994
|
await this.initialize();
|
|
@@ -989,7 +999,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
989
999
|
}
|
|
990
1000
|
/** Import child elements into the target IModelDb
|
|
991
1001
|
* @param sourceElementId Import the child elements of this element in the source IModelDb.
|
|
992
|
-
* @note This method is called from [[
|
|
1002
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
993
1003
|
*/
|
|
994
1004
|
async processChildElements(sourceElementId) {
|
|
995
1005
|
await this.initialize();
|
|
@@ -1001,24 +1011,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1001
1011
|
shouldExportElement(_sourceElement) {
|
|
1002
1012
|
return true;
|
|
1003
1013
|
}
|
|
1004
|
-
onSkipElement(sourceElementId) {
|
|
1005
|
-
if (this.context.findTargetElementId(sourceElementId) !== core_bentley_1.Id64.invalid) {
|
|
1006
|
-
// element already has provenance
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `Element '${sourceElementId}' won't be exported. Marking its references as resolved`);
|
|
1010
|
-
const elementKey = `e${sourceElementId}`;
|
|
1011
|
-
this._skippedEntities.add(elementKey);
|
|
1012
|
-
// Mark any existing pending references to the skipped element as resolved.
|
|
1013
|
-
for (const referencer of this._pendingReferences.getReferencersByEntityKey(elementKey)) {
|
|
1014
|
-
const key = PendingReferenceMap_1.PendingReference.from(referencer, elementKey);
|
|
1015
|
-
const pendingRef = this._pendingReferences.get(key);
|
|
1016
|
-
if (!pendingRef)
|
|
1017
|
-
continue;
|
|
1018
|
-
pendingRef.resolveReference(elementKey);
|
|
1019
|
-
this._pendingReferences.delete(key);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
1014
|
/**
|
|
1023
1015
|
* If they haven't been already, import all of the required references
|
|
1024
1016
|
* @internal do not call, override or implement this, it will be removed
|
|
@@ -1085,11 +1077,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1085
1077
|
onExportElement(sourceElement) {
|
|
1086
1078
|
let targetElementId;
|
|
1087
1079
|
let targetElementProps;
|
|
1088
|
-
if (this._options.
|
|
1089
|
-
targetElementId = sourceElement.id;
|
|
1090
|
-
targetElementProps = this.onTransformElement(sourceElement);
|
|
1091
|
-
}
|
|
1092
|
-
else if (this._options.wasSourceIModelCopiedToTarget) {
|
|
1080
|
+
if (this._options.wasSourceIModelCopiedToTarget) {
|
|
1093
1081
|
targetElementId = sourceElement.id;
|
|
1094
1082
|
targetElementProps =
|
|
1095
1083
|
this.targetDb.elements.getElementProps(targetElementId);
|
|
@@ -1126,20 +1114,42 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1126
1114
|
}
|
|
1127
1115
|
}
|
|
1128
1116
|
}
|
|
1129
|
-
if (
|
|
1130
|
-
!this.hasElementChanged(sourceElement, targetElementId))
|
|
1117
|
+
if (!this.hasElementChanged(sourceElement))
|
|
1131
1118
|
return;
|
|
1132
|
-
this.
|
|
1119
|
+
if (!this.doAllReferencesExistInTarget(sourceElement)) {
|
|
1120
|
+
this._partiallyCommittedElementIds.add(sourceElement.id);
|
|
1121
|
+
}
|
|
1133
1122
|
// targetElementId will be valid (indicating update) or undefined (indicating insert)
|
|
1134
1123
|
targetElementProps.id = core_bentley_1.Id64.isValid(targetElementId)
|
|
1135
1124
|
? targetElementId
|
|
1136
1125
|
: undefined;
|
|
1126
|
+
if (this._options.preserveElementIdsForFiltering) {
|
|
1127
|
+
const isValid = core_bentley_1.Id64.isValid(targetElementId);
|
|
1128
|
+
if (isValid && targetElementId !== sourceElement.id) {
|
|
1129
|
+
// Element found with different id
|
|
1130
|
+
throw new Error(`Element id(${sourceElement.id}) cannot be preserved. Found a different mapping(${targetElementId}) from source element`);
|
|
1131
|
+
}
|
|
1132
|
+
else if (isValid && targetElementId === sourceElement.id) {
|
|
1133
|
+
// targetElementId is valid (indicating update)
|
|
1134
|
+
this.importer.markElementToUpdateDuringPreserveIds(sourceElement.id);
|
|
1135
|
+
}
|
|
1136
|
+
else if (!isValid) {
|
|
1137
|
+
const sourceInTargetElemProps = this.targetDb.elements.tryGetElementProps(sourceElement.id);
|
|
1138
|
+
// if we don't find mapping for source element in target(invalid) but another element with source id exists in target
|
|
1139
|
+
if (sourceInTargetElemProps) {
|
|
1140
|
+
// Element id is already taken by another element
|
|
1141
|
+
throw new Error(`Element id(${sourceElement.id}) cannot be preserved. An unrelated element in the target already uses id: ${sourceElement.id}`);
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
// Element id in target is available to be remapped
|
|
1145
|
+
targetElementProps.id = sourceElement.id;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1137
1149
|
if (!this._options.wasSourceIModelCopiedToTarget) {
|
|
1138
1150
|
this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
|
|
1139
1151
|
}
|
|
1140
1152
|
this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
|
|
1141
|
-
// now that we've mapped this elem we can fix unmapped references to it
|
|
1142
|
-
this.resolvePendingReferences(sourceElement);
|
|
1143
1153
|
// the transformer does not currently 'split' or 'join' any elements, therefore, it does not
|
|
1144
1154
|
// insert external source aspects because federation guids are sufficient for this.
|
|
1145
1155
|
// Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
|
|
@@ -1167,16 +1177,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1167
1177
|
this.markLastProvenance(provenance, { isRelationship: false });
|
|
1168
1178
|
}
|
|
1169
1179
|
}
|
|
1170
|
-
resolvePendingReferences(entity) {
|
|
1171
|
-
for (const referencer of this._pendingReferences.getReferencers(entity)) {
|
|
1172
|
-
const key = PendingReferenceMap_1.PendingReference.from(referencer, entity);
|
|
1173
|
-
const pendingRef = this._pendingReferences.get(key);
|
|
1174
|
-
if (!pendingRef)
|
|
1175
|
-
continue;
|
|
1176
|
-
pendingRef.resolveReference(core_backend_1.EntityReferences.from(entity));
|
|
1177
|
-
this._pendingReferences.delete(key);
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
1180
|
/** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
|
|
1181
1181
|
* This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
|
|
1182
1182
|
*/
|
|
@@ -1196,12 +1196,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1196
1196
|
const targetModeledElementId = this.context.findTargetElementId(sourceModel.id);
|
|
1197
1197
|
// there can only be one repositoryModel per database, so ignore the repo model on remapped subjects
|
|
1198
1198
|
const isRemappedRootSubject = sourceModel.id === core_common_1.IModel.repositoryModelId &&
|
|
1199
|
-
targetModeledElementId
|
|
1199
|
+
targetModeledElementId !== sourceModel.id;
|
|
1200
1200
|
if (isRemappedRootSubject)
|
|
1201
1201
|
return;
|
|
1202
1202
|
const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
|
|
1203
1203
|
this.importer.importModel(targetModelProps);
|
|
1204
|
-
this.resolvePendingReferences(sourceModel);
|
|
1205
1204
|
}
|
|
1206
1205
|
/** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
|
|
1207
1206
|
onDeleteModel(sourceModelId) {
|
|
@@ -1211,13 +1210,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1211
1210
|
const targetModelId = this.context.findTargetElementId(sourceModelId);
|
|
1212
1211
|
if (!core_bentley_1.Id64.isValidId64(targetModelId))
|
|
1213
1212
|
return;
|
|
1213
|
+
let sql;
|
|
1214
|
+
if (this.hasDefinitionContainerDeletionFeature) {
|
|
1215
|
+
sql = `
|
|
1216
|
+
SELECT 1
|
|
1217
|
+
FROM bis.DefinitionPartition
|
|
1218
|
+
WHERE ECInstanceId=:targetModelId
|
|
1219
|
+
UNION
|
|
1220
|
+
SELECT 1
|
|
1221
|
+
FROM bis.DefinitionContainer
|
|
1222
|
+
WHERE ECInstanceId=:targetModelId
|
|
1223
|
+
`;
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
sql = `
|
|
1227
|
+
SELECT 1
|
|
1228
|
+
FROM bis.DefinitionPartition
|
|
1229
|
+
WHERE ECInstanceId=:targetModelId
|
|
1230
|
+
`;
|
|
1231
|
+
}
|
|
1214
1232
|
if (this.exporter.sourceDbChanges?.element.deleteIds.has(sourceModelId)) {
|
|
1215
|
-
const isDefinitionPartition = this.targetDb.withPreparedStatement(
|
|
1216
|
-
|
|
1217
|
-
FROM bis.DefinitionPartition
|
|
1218
|
-
WHERE ECInstanceId=?
|
|
1219
|
-
`, (stmt) => {
|
|
1220
|
-
stmt.bindId(1, targetModelId);
|
|
1233
|
+
const isDefinitionPartition = this.targetDb.withPreparedStatement(sql, (stmt) => {
|
|
1234
|
+
stmt.bindId("targetModelId", targetModelId);
|
|
1221
1235
|
const val = stmt.step();
|
|
1222
1236
|
switch (val) {
|
|
1223
1237
|
case core_bentley_1.DbResult.BE_SQLITE_ROW:
|
|
@@ -1225,7 +1239,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1225
1239
|
case core_bentley_1.DbResult.BE_SQLITE_DONE:
|
|
1226
1240
|
return false;
|
|
1227
1241
|
default:
|
|
1228
|
-
(0, core_bentley_1.assert)(false, `unexpected db result: '${stmt}'`);
|
|
1242
|
+
(0, core_bentley_1.assert)(false, `unexpected db result: '${JSON.stringify(stmt)}'`);
|
|
1229
1243
|
}
|
|
1230
1244
|
});
|
|
1231
1245
|
if (isDefinitionPartition) {
|
|
@@ -1260,7 +1274,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1260
1274
|
}
|
|
1261
1275
|
/** Cause the model container, contents, and sub-models to be exported from the source iModel and imported into the target iModel.
|
|
1262
1276
|
* @param sourceModeledElementId Import this [Model]($backend) from the source IModelDb.
|
|
1263
|
-
* @note This method is called from [[
|
|
1277
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1264
1278
|
*/
|
|
1265
1279
|
async processModel(sourceModeledElementId) {
|
|
1266
1280
|
await this.initialize();
|
|
@@ -1270,7 +1284,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1270
1284
|
* @param sourceModelId Import the contents of this model from the source IModelDb.
|
|
1271
1285
|
* @param targetModelId Import into this model in the target IModelDb. The target model must exist prior to this call.
|
|
1272
1286
|
* @param elementClassFullName Optional classFullName of an element subclass to limit import query against the source model.
|
|
1273
|
-
* @note This method is called from [[
|
|
1287
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1274
1288
|
*/
|
|
1275
1289
|
async processModelContents(sourceModelId, targetModelId, elementClassFullName = core_backend_1.Element.classFullName) {
|
|
1276
1290
|
await this.initialize();
|
|
@@ -1327,46 +1341,50 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1327
1341
|
targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
|
|
1328
1342
|
return targetModelProps;
|
|
1329
1343
|
}
|
|
1330
|
-
/**
|
|
1331
|
-
*
|
|
1332
|
-
*/
|
|
1333
|
-
async processDeferredElements(_numRetries = 3) { }
|
|
1334
|
-
/** called at the end ([[finalizeTransformation]]) of a transformation,
|
|
1344
|
+
/**
|
|
1345
|
+
* Called at the end of a transformation,
|
|
1335
1346
|
* updates the target scope element to say that transformation up through the
|
|
1336
1347
|
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1337
1348
|
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
1338
1349
|
*
|
|
1339
|
-
* You generally should not call this function yourself and use [[
|
|
1350
|
+
* You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
|
|
1340
1351
|
* It is public for unsupported use cases of custom synchronization transforms.
|
|
1341
|
-
* @note
|
|
1342
|
-
*
|
|
1352
|
+
* @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
|
|
1353
|
+
* unless the `initializeReverseSyncVersion` option is set to `true`
|
|
1354
|
+
*
|
|
1355
|
+
* The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
|
|
1356
|
+
* When set to `true`, it saves the reverse sync version as the current changeset of the targetDb. This is typically used for the first transformation between a master and branch iModel.
|
|
1357
|
+
* Setting `initializeReverseSyncVersion` to `true` has the effect of making it so any changesets in the branch iModel at the time of the first transformation will be ignored during any future reverse synchronizations from the branch to the master iModel.
|
|
1358
|
+
*
|
|
1359
|
+
* Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
|
|
1360
|
+
* Setting initializeReverseSyncVersion to true during a forward transformation could overwrite this correct reverseSyncVersion and should only be done during the first transformation between a master and branch iModel.
|
|
1343
1361
|
*/
|
|
1344
|
-
updateSynchronizationVersion({
|
|
1345
|
-
const
|
|
1346
|
-
this._sourceChangeDataState !== "has-changes"
|
|
1347
|
-
|
|
1348
|
-
if (notForcedAndHasNoChangesAndIsntProvenanceInit)
|
|
1362
|
+
updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
|
|
1363
|
+
const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
|
|
1364
|
+
this._sourceChangeDataState !== "has-changes";
|
|
1365
|
+
if (shouldSkipSyncVersionUpdate)
|
|
1349
1366
|
return;
|
|
1350
1367
|
nodeAssert(this._targetScopeProvenanceProps);
|
|
1351
1368
|
const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
|
|
1352
1369
|
const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
|
|
1353
|
-
if (this.
|
|
1354
|
-
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1355
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1356
|
-
targetVersion;
|
|
1357
|
-
}
|
|
1358
|
-
else if (this.isReverseSynchronization) {
|
|
1370
|
+
if (this.isReverseSynchronization) {
|
|
1359
1371
|
const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
|
|
1360
1372
|
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
|
|
1361
1373
|
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1362
1374
|
sourceVersion;
|
|
1363
1375
|
}
|
|
1364
|
-
else
|
|
1376
|
+
else {
|
|
1365
1377
|
core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
|
|
1366
1378
|
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1379
|
+
// save reverse sync version
|
|
1380
|
+
if (initializeReverseSyncVersion) {
|
|
1381
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
|
|
1382
|
+
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1383
|
+
targetVersion;
|
|
1384
|
+
}
|
|
1367
1385
|
}
|
|
1368
|
-
if (this.
|
|
1369
|
-
(this._startingChangesetIndices &&
|
|
1386
|
+
if (this._options.argsForProcessChanges ||
|
|
1387
|
+
(this._startingChangesetIndices && initializeReverseSyncVersion)) {
|
|
1370
1388
|
nodeAssert(this.targetDb.changeset.index !== undefined &&
|
|
1371
1389
|
this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
|
|
1372
1390
|
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
@@ -1374,16 +1392,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1374
1392
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1375
1393
|
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1376
1394
|
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1395
|
+
// Determine which keys to clear and update based on the synchronization direction
|
|
1396
|
+
let syncChangesetsToClearKey;
|
|
1397
|
+
let syncChangesetsToUpdateKey;
|
|
1398
|
+
if (this.isReverseSynchronization) {
|
|
1399
|
+
syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
|
|
1400
|
+
syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
|
|
1404
|
+
syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
|
|
1405
|
+
}
|
|
1406
|
+
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1407
|
+
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1408
|
+
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1409
|
+
// stuff to it which would break future synchronizations
|
|
1387
1410
|
for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1388
1411
|
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1389
1412
|
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
@@ -1403,25 +1426,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1403
1426
|
...this._targetScopeProvenanceProps,
|
|
1404
1427
|
jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
|
|
1405
1428
|
});
|
|
1429
|
+
this.clearCachedSynchronizationVersion();
|
|
1406
1430
|
}
|
|
1407
1431
|
// FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
|
|
1408
|
-
|
|
1432
|
+
finalizeTransformation() {
|
|
1409
1433
|
this.importer.finalize();
|
|
1410
|
-
this.updateSynchronizationVersion(
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
"The following elements were never fully resolved:",
|
|
1414
|
-
[...this._partiallyCommittedEntities.keys()].join(","),
|
|
1415
|
-
"This indicates that either some references were excluded from the transformation",
|
|
1416
|
-
"or the source has dangling references.",
|
|
1417
|
-
].join("\n");
|
|
1418
|
-
if (this._options.danglingReferencesBehavior === "reject")
|
|
1419
|
-
throw new Error(message);
|
|
1420
|
-
core_bentley_1.Logger.logWarning(loggerCategory, message);
|
|
1421
|
-
for (const partiallyCommittedElem of this._partiallyCommittedEntities.values()) {
|
|
1422
|
-
partiallyCommittedElem.forceComplete();
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1434
|
+
this.updateSynchronizationVersion({
|
|
1435
|
+
initializeReverseSyncVersion: this._isProvenanceInitTransform,
|
|
1436
|
+
});
|
|
1425
1437
|
// TODO: ignore if we remove change cache usage
|
|
1426
1438
|
if (!this._options.noDetachChangeCache) {
|
|
1427
1439
|
if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
|
|
@@ -1434,35 +1446,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1434
1446
|
this.targetDb.codeValueBehavior = "trim-unicode-whitespace";
|
|
1435
1447
|
}
|
|
1436
1448
|
/* eslint-enable @itwin/no-internal */
|
|
1437
|
-
const defaultSaveTargetChanges = () => this.targetDb.saveChanges();
|
|
1438
|
-
await (options?.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
1439
|
-
if (this.isReverseSynchronization)
|
|
1440
|
-
this.sourceDb.saveChanges();
|
|
1441
|
-
const description = `${this._isProvenanceInitTransform
|
|
1442
|
-
? options?.provenanceInitTransformChangesetDescription ??
|
|
1443
|
-
`initialized branch provenance with master iModel: ${this.sourceDb.iModelId}`
|
|
1444
|
-
: this.isForwardSynchronization
|
|
1445
|
-
? options?.forwardSyncBranchChangesetDescription ??
|
|
1446
|
-
`Forward sync of iModel: ${this.sourceDb.iModelId}`
|
|
1447
|
-
: options?.reverseSyncMasterChangesetDescription ??
|
|
1448
|
-
`Reverse sync of iModel: ${this.sourceDb.iModelId}`}`;
|
|
1449
|
-
if (this.targetDb.isBriefcaseDb()) {
|
|
1450
|
-
// This relies on authorizationClient on iModelHost being defined, otherwise this will fail
|
|
1451
|
-
await this.targetDb.pushChanges({
|
|
1452
|
-
description,
|
|
1453
|
-
});
|
|
1454
|
-
}
|
|
1455
|
-
if (this.isReverseSynchronization && this.sourceDb.isBriefcaseDb()) {
|
|
1456
|
-
// This relies on authorizationClient on iModelHost being defined, otherwise this will fail
|
|
1457
|
-
await this.sourceDb.pushChanges({
|
|
1458
|
-
description: options?.reverseSyncBranchChangesetDescription ??
|
|
1459
|
-
`Update provenance in response to a reverse sync to iModel: ${this.targetDb.iModelId}`,
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
1449
|
}
|
|
1463
1450
|
/** Imports all relationships that subclass from the specified base class.
|
|
1464
1451
|
* @param baseRelClassFullName The specified base relationship class.
|
|
1465
|
-
* @note This method is called from [[
|
|
1452
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1466
1453
|
*/
|
|
1467
1454
|
async processRelationships(baseRelClassFullName) {
|
|
1468
1455
|
await this.initialize();
|
|
@@ -1535,8 +1522,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1535
1522
|
}
|
|
1536
1523
|
/** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
|
|
1537
1524
|
* @deprecated in 1.x. Don't use this anymore
|
|
1538
|
-
* @see
|
|
1539
|
-
* @note This method is called from [[
|
|
1525
|
+
* @see [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided.
|
|
1526
|
+
* @note This method is called from [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are undefined, so it only needs to be called directly when processing a subset of an iModel.
|
|
1540
1527
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
1541
1528
|
*/
|
|
1542
1529
|
async detectRelationshipDeletes() {
|
|
@@ -1603,22 +1590,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1603
1590
|
* This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
|
|
1604
1591
|
*/
|
|
1605
1592
|
onExportElementUniqueAspect(sourceAspect) {
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1593
|
+
const targetAspectProps = this.onTransformElementAspect(sourceAspect);
|
|
1594
|
+
if (!this.doAllReferencesExistInTarget(sourceAspect)) {
|
|
1595
|
+
this._partiallyCommittedAspectIds.add(sourceAspect.id);
|
|
1596
|
+
}
|
|
1609
1597
|
const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
|
|
1610
1598
|
this.context.remapElementAspect(sourceAspect.id, targetId);
|
|
1611
|
-
this.resolvePendingReferences(sourceAspect);
|
|
1612
1599
|
}
|
|
1613
1600
|
/** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
|
|
1614
1601
|
* This override calls [[onTransformElementAspect]] for each ElementMultiAspect and then [IModelImporter.importElementMultiAspects]($transformer) to update the target iModel.
|
|
1615
1602
|
* @note ElementMultiAspects are handled as a group to make it easier to differentiate between insert, update, and delete.
|
|
1616
1603
|
*/
|
|
1617
1604
|
onExportElementMultiAspects(sourceAspects) {
|
|
1618
|
-
const targetElementId = this.context.findTargetElementId(sourceAspects[0].element.id);
|
|
1619
1605
|
// Transform source ElementMultiAspects into target ElementAspectProps
|
|
1620
|
-
const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA
|
|
1621
|
-
sourceAspects.forEach((a) =>
|
|
1606
|
+
const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA));
|
|
1607
|
+
sourceAspects.forEach((a) => {
|
|
1608
|
+
if (!this.doAllReferencesExistInTarget(a)) {
|
|
1609
|
+
this._partiallyCommittedAspectIds.add(a.id);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1622
1612
|
// const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
|
|
1623
1613
|
const targetIds = this.importer.importElementMultiAspects(targetAspectPropsArray, (a) => {
|
|
1624
1614
|
const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
|
|
@@ -1628,16 +1618,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1628
1618
|
});
|
|
1629
1619
|
for (let i = 0; i < targetIds.length; ++i) {
|
|
1630
1620
|
this.context.remapElementAspect(sourceAspects[i].id, targetIds[i]);
|
|
1631
|
-
this.resolvePendingReferences(sourceAspects[i]);
|
|
1632
1621
|
}
|
|
1633
1622
|
}
|
|
1634
1623
|
/** Transform the specified sourceElementAspect into ElementAspectProps for the target iModel.
|
|
1635
1624
|
* @param sourceElementAspect The ElementAspect from the source iModel to be transformed.
|
|
1636
|
-
* @param _targetElementId The ElementId of the target Element that will own the ElementAspects after transformation.
|
|
1637
1625
|
* @returns ElementAspectProps for the target iModel.
|
|
1638
1626
|
* @note A subclass can override this method to provide custom transform behavior.
|
|
1639
1627
|
*/
|
|
1640
|
-
onTransformElementAspect(sourceElementAspect
|
|
1628
|
+
onTransformElementAspect(sourceElementAspect) {
|
|
1641
1629
|
const targetElementAspectProps = this.context.cloneElementAspect(sourceElementAspect);
|
|
1642
1630
|
return targetElementAspectProps;
|
|
1643
1631
|
}
|
|
@@ -1663,7 +1651,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1663
1651
|
let schemaFileName = schema.name + ext;
|
|
1664
1652
|
// many file systems have a max file-name/path-segment size of 255, so we workaround that on all systems
|
|
1665
1653
|
const systemMaxPathSegmentSize = 255;
|
|
1666
|
-
|
|
1654
|
+
// windows usually has a limit for the total path length of 260
|
|
1655
|
+
const windowsMaxPathLimit = 260;
|
|
1656
|
+
if (schemaFileName.length > systemMaxPathSegmentSize ||
|
|
1657
|
+
path.join(this._schemaExportDir, schemaFileName).length >=
|
|
1658
|
+
windowsMaxPathLimit) {
|
|
1667
1659
|
// this name should be well under 255 bytes
|
|
1668
1660
|
// ( 100 + (Number.MAX_SAFE_INTEGER.toString().length = 16) + (ext.length = 13) ) = 129 which is less than 255
|
|
1669
1661
|
// You'd have to be past 2**53-1 (Number.MAX_SAFE_INTEGER) long named schemas in order to hit decimal formatting,
|
|
@@ -1673,6 +1665,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1673
1665
|
nodeAssert(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
|
|
1674
1666
|
this._longNamedSchemasMap.set(schema.name, schemaFileName);
|
|
1675
1667
|
}
|
|
1668
|
+
/* eslint-disable-next-line deprecation/deprecation */
|
|
1676
1669
|
this.sourceDb.nativeDb.exportSchema(schema.name, this._schemaExportDir, schemaFileName);
|
|
1677
1670
|
return { schemaPath: path.join(this._schemaExportDir, schemaFileName) };
|
|
1678
1671
|
}
|
|
@@ -1713,7 +1706,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1713
1706
|
}
|
|
1714
1707
|
}
|
|
1715
1708
|
/** Cause all fonts to be exported from the source iModel and imported into the target iModel.
|
|
1716
|
-
* @note This method is called from [[
|
|
1709
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1717
1710
|
*/
|
|
1718
1711
|
async processFonts() {
|
|
1719
1712
|
// we do not need to initialize for this since no entities are exported
|
|
@@ -1725,14 +1718,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1725
1718
|
this.context.importFont(font.id);
|
|
1726
1719
|
}
|
|
1727
1720
|
/** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
|
|
1728
|
-
* @note This method is called from [[
|
|
1721
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1729
1722
|
*/
|
|
1730
1723
|
async processCodeSpecs() {
|
|
1731
1724
|
await this.initialize();
|
|
1732
1725
|
return this.exporter.exportCodeSpecs();
|
|
1733
1726
|
}
|
|
1734
1727
|
/** Cause a single CodeSpec to be exported from the source iModel and imported into the target iModel.
|
|
1735
|
-
* @note This method is called from [[
|
|
1728
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1736
1729
|
*/
|
|
1737
1730
|
async processCodeSpec(codeSpecName) {
|
|
1738
1731
|
await this.initialize();
|
|
@@ -1756,21 +1749,23 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1756
1749
|
this.context.remapElement(sourceSubjectId, targetSubjectId);
|
|
1757
1750
|
await this.processChildElements(sourceSubjectId);
|
|
1758
1751
|
await this.processSubjectSubModels(sourceSubjectId);
|
|
1759
|
-
|
|
1752
|
+
this.completePartiallyCommittedElements();
|
|
1753
|
+
this.completePartiallyCommittedAspects();
|
|
1760
1754
|
}
|
|
1761
1755
|
/**
|
|
1762
1756
|
* Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
|
|
1763
|
-
* are intending to process changes
|
|
1757
|
+
* are intending to process changes. Callers may wish to explicitly call initialize if they need to execute code after initialize but before [[process]] is called.
|
|
1764
1758
|
* @note Called by all `process*` functions implicitly.
|
|
1765
1759
|
* Overriders must call `super.initialize()` first
|
|
1766
1760
|
*/
|
|
1767
|
-
async initialize(
|
|
1761
|
+
async initialize() {
|
|
1768
1762
|
if (this._initialized)
|
|
1769
1763
|
return;
|
|
1770
|
-
|
|
1764
|
+
this.initScopeProvenance();
|
|
1765
|
+
await this._tryInitChangesetData(this._options.argsForProcessChanges);
|
|
1771
1766
|
await this.context.initialize();
|
|
1772
1767
|
// need exporter initialized to do remapdeletedsourceentities.
|
|
1773
|
-
await this.exporter.initialize(this.getExportInitOpts(
|
|
1768
|
+
await this.exporter.initialize(this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
|
|
1774
1769
|
// Exporter must be initialized prior to processing changesets in order to properly handle entity recreations (an entity delete followed by an insert of that same entity).
|
|
1775
1770
|
await this.processChangesets();
|
|
1776
1771
|
this._initialized = true;
|
|
@@ -1797,6 +1792,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1797
1792
|
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)")) {
|
|
1798
1793
|
relationshipECClassIds.add(row.ECInstanceId);
|
|
1799
1794
|
}
|
|
1795
|
+
const elementECClassIds = new Set();
|
|
1796
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.Element)")) {
|
|
1797
|
+
elementECClassIds.add(row.ECInstanceId);
|
|
1798
|
+
}
|
|
1800
1799
|
// For later use when processing deletes.
|
|
1801
1800
|
const alreadyImportedElementInserts = new Set();
|
|
1802
1801
|
const alreadyImportedModelInserts = new Set();
|
|
@@ -1832,10 +1831,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1832
1831
|
const changeType = change.$meta?.op;
|
|
1833
1832
|
if (changeType === "Deleted" &&
|
|
1834
1833
|
change?.$meta?.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
|
|
1835
|
-
change.Scope.Id === this.targetScopeElementId
|
|
1834
|
+
change.Scope.Id === this.targetScopeElementId &&
|
|
1835
|
+
change.Kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
|
|
1836
1836
|
elemIdToScopeEsa.set(change.Element.Id, change);
|
|
1837
1837
|
}
|
|
1838
|
-
else if (changeType === "Inserted" || changeType === "Updated")
|
|
1838
|
+
else if ((changeType === "Inserted" || changeType === "Updated") &&
|
|
1839
|
+
change.ECClassId !== undefined &&
|
|
1840
|
+
elementECClassIds.has(change.ECClassId))
|
|
1839
1841
|
hasElementChangedCache.add(change.ECInstanceId);
|
|
1840
1842
|
}
|
|
1841
1843
|
// Loop to process deletes.
|
|
@@ -1869,7 +1871,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1869
1871
|
async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1870
1872
|
// we need a connected iModel with changes to remap elements with deletions
|
|
1871
1873
|
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1872
|
-
const noChanges = this.
|
|
1874
|
+
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
1873
1875
|
if (notConnectedModel || noChanges)
|
|
1874
1876
|
return;
|
|
1875
1877
|
/**
|
|
@@ -1966,17 +1968,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1966
1968
|
this._sourceChangeDataState = "unconnected";
|
|
1967
1969
|
return;
|
|
1968
1970
|
}
|
|
1969
|
-
const noChanges = this.
|
|
1971
|
+
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
1970
1972
|
if (noChanges) {
|
|
1971
1973
|
this._sourceChangeDataState = "no-changes";
|
|
1972
1974
|
this._csFileProps = [];
|
|
1973
1975
|
return;
|
|
1974
1976
|
}
|
|
1977
|
+
const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
|
|
1975
1978
|
// NOTE: that we do NOT download the changesummary for the last transformed version, we want
|
|
1976
1979
|
// to ignore those already processed changes
|
|
1977
|
-
const startChangesetIndexOrId =
|
|
1978
|
-
|
|
1979
|
-
this.
|
|
1980
|
+
const startChangesetIndexOrId = startChangeset?.index ??
|
|
1981
|
+
startChangeset?.id ??
|
|
1982
|
+
this.synchronizationVersion.index + 1;
|
|
1980
1983
|
const endChangesetId = this.sourceDb.changeset.id;
|
|
1981
1984
|
const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
|
|
1982
1985
|
? indexOrId
|
|
@@ -1985,20 +1988,20 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1985
1988
|
iModelId: this.sourceDb.iModelId,
|
|
1986
1989
|
// eslint-disable-next-line deprecation/deprecation
|
|
1987
1990
|
changeset: { id: indexOrId },
|
|
1988
|
-
accessToken: args.accessToken,
|
|
1989
1991
|
})
|
|
1990
1992
|
.then((changeset) => changeset.index)));
|
|
1991
|
-
const missingChangesets = startChangesetIndex > this.
|
|
1992
|
-
if (!this._options.
|
|
1993
|
-
|
|
1994
|
-
this.
|
|
1993
|
+
const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
|
|
1994
|
+
if (!this._options.argsForProcessChanges
|
|
1995
|
+
?.ignoreMissingChangesetsInSynchronizations &&
|
|
1996
|
+
startChangesetIndex !== this.synchronizationVersion.index + 1 &&
|
|
1997
|
+
this.synchronizationVersion.index !== -1) {
|
|
1995
1998
|
throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
|
|
1996
1999
|
" startChangesetId should be" +
|
|
1997
2000
|
" exactly the first changeset *after* the previous synchronization to not miss data." +
|
|
1998
2001
|
` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
|
|
1999
|
-
` but the previous synchronization for this targetScopeElement was '${this.
|
|
2000
|
-
` which is changeset #${this.
|
|
2001
|
-
` #${this.
|
|
2002
|
+
` but the previous synchronization for this targetScopeElement was '${this.synchronizationVersion.id}'` +
|
|
2003
|
+
` which is changeset #${this.synchronizationVersion.index}. The transformer expected` +
|
|
2004
|
+
` #${this.synchronizationVersion.index + 1}.`);
|
|
2002
2005
|
}
|
|
2003
2006
|
nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps should be set by now");
|
|
2004
2007
|
const changesetsToSkip = this.isReverseSynchronization
|
|
@@ -2020,15 +2023,44 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2020
2023
|
csFileProps.push(...fileProps);
|
|
2021
2024
|
}
|
|
2022
2025
|
this._csFileProps = csFileProps;
|
|
2023
|
-
|
|
2026
|
+
/** Theres a possibility that our csFileProps length is still 0 here, since we skip cs indices found in the pendingSync and pendingReverseSync indices arrays. */
|
|
2027
|
+
this._sourceChangeDataState =
|
|
2028
|
+
this._csFileProps.length === 0 ? "no-changes" : "has-changes";
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* The behavior of process is influenced by [[IModelTransformOptions.argsForProcessChanges]] being defined or not defined during construction passed of the IModelTransformer.
|
|
2032
|
+
* @section When argsForProcessChanges are defined:
|
|
2033
|
+
*
|
|
2034
|
+
* Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2035
|
+
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2036
|
+
*
|
|
2037
|
+
* Notes:
|
|
2038
|
+
* - the transformer assumes that you saveChanges after processing changes. You should not modify the iModel after processChanges until saveChanges,
|
|
2039
|
+
* failure to do so may result in corrupted
|
|
2040
|
+
* data loss in future branch operations
|
|
2041
|
+
* - if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2042
|
+
* will automatically be determined and used
|
|
2043
|
+
* - 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.
|
|
2044
|
+
*
|
|
2045
|
+
* @section When argsForProcessChanges are undefined:
|
|
2046
|
+
*
|
|
2047
|
+
* Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2048
|
+
*
|
|
2049
|
+
* Notes:
|
|
2050
|
+
* - [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2051
|
+
*
|
|
2052
|
+
*/
|
|
2053
|
+
async process() {
|
|
2054
|
+
await this.initialize();
|
|
2055
|
+
this.logSettings();
|
|
2056
|
+
return this._options.argsForProcessChanges !== undefined
|
|
2057
|
+
? this.processChanges(this._options.argsForProcessChanges)
|
|
2058
|
+
: this.processAll();
|
|
2024
2059
|
}
|
|
2025
2060
|
/** Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2026
2061
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2027
2062
|
*/
|
|
2028
|
-
async processAll(
|
|
2029
|
-
this.logSettings();
|
|
2030
|
-
this.initScopeProvenance();
|
|
2031
|
-
await this.initialize();
|
|
2063
|
+
async processAll() {
|
|
2032
2064
|
await this.exporter.exportCodeSpecs();
|
|
2033
2065
|
await this.exporter.exportFonts();
|
|
2034
2066
|
if (this._options.skipPropagateChangesToRootElements) {
|
|
@@ -2040,18 +2072,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2040
2072
|
else {
|
|
2041
2073
|
await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
|
|
2042
2074
|
}
|
|
2075
|
+
this.completePartiallyCommittedElements();
|
|
2043
2076
|
await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
|
|
2077
|
+
this.completePartiallyCommittedAspects();
|
|
2044
2078
|
await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
2045
|
-
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
2046
2079
|
if (this._options.forceExternalSourceAspectProvenance &&
|
|
2047
2080
|
this.shouldDetectDeletes()) {
|
|
2081
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2048
2082
|
await this.detectElementDeletes();
|
|
2083
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2049
2084
|
await this.detectRelationshipDeletes();
|
|
2050
2085
|
}
|
|
2051
2086
|
if (this._options.optimizeGeometry)
|
|
2052
2087
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2053
2088
|
this.importer.computeProjectExtents();
|
|
2054
|
-
|
|
2089
|
+
this.finalizeTransformation();
|
|
2055
2090
|
}
|
|
2056
2091
|
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
2057
2092
|
this._lastProvenanceEntityInfo =
|
|
@@ -2068,42 +2103,45 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2068
2103
|
}
|
|
2069
2104
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2070
2105
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2071
|
-
* @note the transformer
|
|
2106
|
+
* @note the transformer assumes that you saveChanges after processing changes. You should not
|
|
2107
|
+
* modify the iModel after processChanges until saveChanges, failure to do so may result in corrupted
|
|
2108
|
+
* data loss in future branch operations
|
|
2072
2109
|
* @note if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2073
2110
|
* will automatically be determined and used
|
|
2074
2111
|
* @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.
|
|
2075
2112
|
*/
|
|
2076
2113
|
async processChanges(options) {
|
|
2077
|
-
this._isSynchronization = true;
|
|
2078
|
-
this.initScopeProvenance();
|
|
2079
|
-
this.logSettings();
|
|
2080
|
-
await this.initialize(options);
|
|
2081
2114
|
// must wait for initialization of synchronization provenance data
|
|
2082
2115
|
await this.exporter.exportChanges(this.getExportInitOpts(options));
|
|
2083
|
-
|
|
2116
|
+
this.completePartiallyCommittedElements();
|
|
2117
|
+
this.completePartiallyCommittedAspects();
|
|
2084
2118
|
if (this._options.optimizeGeometry)
|
|
2085
2119
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2086
2120
|
this.importer.computeProjectExtents();
|
|
2087
|
-
|
|
2121
|
+
this.finalizeTransformation();
|
|
2122
|
+
const defaultSaveTargetChanges = () => {
|
|
2123
|
+
this.targetDb.saveChanges();
|
|
2124
|
+
};
|
|
2125
|
+
await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
2088
2126
|
}
|
|
2089
2127
|
/** Changeset data must be initialized in order to build correct changeOptions.
|
|
2090
2128
|
* Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
|
|
2091
2129
|
*/
|
|
2092
2130
|
getExportInitOpts(opts) {
|
|
2093
|
-
if (!this.
|
|
2131
|
+
if (!this._options.argsForProcessChanges)
|
|
2094
2132
|
return {};
|
|
2133
|
+
const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
|
|
2095
2134
|
return {
|
|
2096
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements
|
|
2097
|
-
accessToken: opts.accessToken,
|
|
2135
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
2098
2136
|
...(this._csFileProps
|
|
2099
2137
|
? { csFileProps: this._csFileProps }
|
|
2100
2138
|
: this._changesetRanges
|
|
2101
2139
|
? { changesetRanges: this._changesetRanges }
|
|
2102
|
-
:
|
|
2103
|
-
? { startChangeset
|
|
2140
|
+
: startChangeset
|
|
2141
|
+
? { startChangeset }
|
|
2104
2142
|
: {
|
|
2105
2143
|
startChangeset: {
|
|
2106
|
-
index: this.
|
|
2144
|
+
index: this.synchronizationVersion.index + 1,
|
|
2107
2145
|
},
|
|
2108
2146
|
}),
|
|
2109
2147
|
};
|
|
@@ -2182,8 +2220,7 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2182
2220
|
}
|
|
2183
2221
|
/** Cloning from a template requires this override of onTransformElement. */
|
|
2184
2222
|
onTransformElement(sourceElement) {
|
|
2185
|
-
|
|
2186
|
-
const referenceIds = sourceElement.getReferenceConcreteIds();
|
|
2223
|
+
const referenceIds = sourceElement.getReferenceIds();
|
|
2187
2224
|
referenceIds.forEach((referenceId) => {
|
|
2188
2225
|
// TODO: consider going through all definition elements at once and remapping them to themselves
|
|
2189
2226
|
if (!core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
|