@itwin/imodel-transformer 1.0.0-dev.1 → 1.0.0-dev.10
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.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 +1 -4
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +16 -31
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +5 -52
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +6 -60
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +5 -44
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +4 -52
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +59 -111
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +287 -400
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/PendingReferenceMap.js.map +1 -1
- package/lib/cjs/transformer.js +2 -1
- package/lib/cjs/transformer.js.map +1 -1
- package/package.json +17 -16
|
@@ -13,6 +13,7 @@ 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");
|
|
@@ -80,7 +81,7 @@ function mapId64(idContainer, func) {
|
|
|
80
81
|
}
|
|
81
82
|
else {
|
|
82
83
|
throw Error([
|
|
83
|
-
`Id64 container '${idContainer}' is unsupported.`,
|
|
84
|
+
`Id64 container '${JSON.stringify(idContainer)}' is unsupported.`,
|
|
84
85
|
"Currently only singular Id64 strings or prop-like objects containing an 'id' property are supported.",
|
|
85
86
|
].join("\n"));
|
|
86
87
|
}
|
|
@@ -149,7 +150,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
149
150
|
id: targetScopeElementId,
|
|
150
151
|
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
151
152
|
},
|
|
152
|
-
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
153
|
+
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
153
154
|
identifier: sourceDb.iModelId,
|
|
154
155
|
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
155
156
|
jsonProperties: undefined,
|
|
@@ -273,9 +274,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
273
274
|
cloneUsingBinaryGeometry: options?.cloneUsingBinaryGeometry ?? true,
|
|
274
275
|
targetScopeElementId: options?.targetScopeElementId ?? core_common_1.IModel.rootSubjectId,
|
|
275
276
|
// eslint-disable-next-line deprecation/deprecation
|
|
276
|
-
danglingReferencesBehavior: options?.danglingReferencesBehavior ??
|
|
277
|
-
options?.danglingPredecessorsBehavior ??
|
|
278
|
-
"reject",
|
|
277
|
+
danglingReferencesBehavior: options?.danglingReferencesBehavior ?? "reject",
|
|
279
278
|
branchRelationshipDataBehavior: options?.branchRelationshipDataBehavior ?? "reject",
|
|
280
279
|
};
|
|
281
280
|
this._isProvenanceInitTransform = this._options
|
|
@@ -361,8 +360,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
361
360
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._includeSourceProvenance=${this._options.includeSourceProvenance}`);
|
|
362
361
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._cloneUsingBinaryGeometry=${this._options.cloneUsingBinaryGeometry}`);
|
|
363
362
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._wasSourceIModelCopiedToTarget=${this._options.wasSourceIModelCopiedToTarget}`);
|
|
364
|
-
core_bentley_1.Logger.logInfo(loggerCategory,
|
|
365
|
-
|
|
363
|
+
core_bentley_1.Logger.logInfo(loggerCategory,
|
|
364
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
365
|
+
`this._isReverseSynchronization=${this._options.isReverseSynchronization}`);
|
|
366
|
+
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
366
367
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
367
368
|
}
|
|
368
369
|
/** Return the IModelDb where IModelTransformer will store its provenance.
|
|
@@ -455,29 +456,30 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
455
456
|
forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
|
|
456
457
|
});
|
|
457
458
|
}
|
|
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).
|
|
459
|
+
/**
|
|
460
|
+
* As of itwinjs 4.6.0, definitionContainers are now deleted as if they were DefinitionPartitions as opposed to Definitions.
|
|
461
|
+
* This variable being true will be used to special case the deletion of DefinitionContainers the same way DefinitionPartitions are deleted.
|
|
463
462
|
*/
|
|
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");
|
|
463
|
+
get hasDefinitionContainerDeletionFeature() {
|
|
464
|
+
if (this._hasDefinitionContainerDeletionFeature === undefined) {
|
|
465
|
+
this._hasDefinitionContainerDeletionFeature = Semver.satisfies(coreBackendPkgJson.version, "^4.6.0");
|
|
474
466
|
}
|
|
475
|
-
return this.
|
|
467
|
+
return this._hasDefinitionContainerDeletionFeature;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* We cache the synchronization version to avoid querying the target scoping ESA multiple times.
|
|
471
|
+
* If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
|
|
472
|
+
* Sets this._cachedSynchronizationVersion to undefined.
|
|
473
|
+
*/
|
|
474
|
+
clearCachedSynchronizationVersion() {
|
|
475
|
+
this._cachedSynchronizationVersion = undefined;
|
|
476
476
|
}
|
|
477
477
|
/** the changeset in the scoping element's source version found for this transformation
|
|
478
|
-
* @note
|
|
478
|
+
* @note the version depends on whether this is a reverse synchronization or not, as
|
|
479
479
|
* it is stored separately for both synchronization directions.
|
|
480
|
-
* @note
|
|
480
|
+
* @note empty string and -1 for changeset and index if it has never been transformed
|
|
481
|
+
* @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".
|
|
482
|
+
* @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
|
|
481
483
|
*/
|
|
482
484
|
get synchronizationVersion() {
|
|
483
485
|
if (this._cachedSynchronizationVersion === undefined) {
|
|
@@ -488,10 +490,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
488
490
|
const version = this.isReverseSynchronization
|
|
489
491
|
? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
|
|
490
492
|
: provenanceScopeAspect.version;
|
|
491
|
-
if (!version
|
|
493
|
+
if (!version &&
|
|
494
|
+
this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
|
|
492
495
|
return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
|
|
493
496
|
}
|
|
494
|
-
|
|
497
|
+
if (version === undefined) {
|
|
498
|
+
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.
|
|
499
|
+
Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
|
|
500
|
+
}
|
|
501
|
+
const [id, index] = version === "" ? ["", -1] : version.split(";");
|
|
495
502
|
if (Number.isNaN(Number(index)))
|
|
496
503
|
throw new Error("Could not parse version data from scope aspect");
|
|
497
504
|
this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
|
|
@@ -530,7 +537,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
530
537
|
id: this.targetScopeElementId,
|
|
531
538
|
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
532
539
|
},
|
|
533
|
-
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
540
|
+
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
534
541
|
identifier: this.provenanceSourceDb.iModelId,
|
|
535
542
|
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
536
543
|
jsonProperties: undefined,
|
|
@@ -567,29 +574,81 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
567
574
|
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
568
575
|
});
|
|
569
576
|
aspectProps.id = id;
|
|
577
|
+
// Busting a potential cached version
|
|
578
|
+
this.clearCachedSynchronizationVersion();
|
|
570
579
|
}
|
|
571
580
|
}
|
|
572
581
|
else {
|
|
573
582
|
// foundEsaProps is defined.
|
|
574
583
|
aspectProps.id = foundEsaProps.aspectId;
|
|
575
|
-
aspectProps.version =
|
|
576
|
-
foundEsaProps.version ??
|
|
577
|
-
(this._options.branchRelationshipDataBehavior === "unsafe-migrate"
|
|
578
|
-
? ""
|
|
579
|
-
: undefined);
|
|
584
|
+
aspectProps.version = foundEsaProps.version;
|
|
580
585
|
aspectProps.jsonProperties = foundEsaProps.jsonProperties
|
|
581
586
|
? JSON.parse(foundEsaProps.jsonProperties)
|
|
582
|
-
:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
587
|
+
: undefined;
|
|
588
|
+
// Clone oldProps incase they're changed for logging purposes
|
|
589
|
+
const oldProps = JSON.parse(JSON.stringify(aspectProps));
|
|
590
|
+
if (this.handleUnsafeMigrate(aspectProps)) {
|
|
591
|
+
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 });
|
|
592
|
+
this.provenanceDb.elements.updateAspect({
|
|
593
|
+
...aspectProps,
|
|
594
|
+
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
595
|
+
});
|
|
596
|
+
// Busting a potential cached version
|
|
597
|
+
this.clearCachedSynchronizationVersion();
|
|
598
|
+
}
|
|
589
599
|
}
|
|
590
600
|
this._targetScopeProvenanceProps =
|
|
591
601
|
aspectProps;
|
|
592
602
|
}
|
|
603
|
+
/** Returns true if a change was made to the aspectProps. */
|
|
604
|
+
handleUnsafeMigrate(aspectProps) {
|
|
605
|
+
let madeChange = false;
|
|
606
|
+
if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
|
|
607
|
+
return madeChange;
|
|
608
|
+
const fallbackSyncVersionToUse = this._options.unsafeFallbackSyncVersion ?? "";
|
|
609
|
+
const fallbackReverseSyncVersionToUse = this._options.unsafeFallbackReverseSyncVersion ?? "";
|
|
610
|
+
if (aspectProps.version === undefined ||
|
|
611
|
+
(aspectProps.version === "" &&
|
|
612
|
+
aspectProps.version !== fallbackSyncVersionToUse)) {
|
|
613
|
+
aspectProps.version = fallbackSyncVersionToUse;
|
|
614
|
+
madeChange = true;
|
|
615
|
+
}
|
|
616
|
+
if (aspectProps.jsonProperties === undefined) {
|
|
617
|
+
aspectProps.jsonProperties = {
|
|
618
|
+
pendingReverseSyncChangesetIndices: [],
|
|
619
|
+
pendingSyncChangesetIndices: [],
|
|
620
|
+
reverseSyncVersion: fallbackReverseSyncVersionToUse,
|
|
621
|
+
};
|
|
622
|
+
madeChange = true;
|
|
623
|
+
}
|
|
624
|
+
else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
|
|
625
|
+
(aspectProps.jsonProperties.reverseSyncVersion === "" &&
|
|
626
|
+
aspectProps.jsonProperties.reverseSyncVersion !==
|
|
627
|
+
fallbackReverseSyncVersionToUse)) {
|
|
628
|
+
aspectProps.jsonProperties.reverseSyncVersion =
|
|
629
|
+
fallbackReverseSyncVersionToUse;
|
|
630
|
+
madeChange = true;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* This case will only be hit when:
|
|
634
|
+
* - first transformation was performed on pre-fedguid transformer.
|
|
635
|
+
* - a second processAll transformation was performed on the same target-source iModels post-fedguid transformer.
|
|
636
|
+
* - change processing was invoked on for the second 'initial' transformation.
|
|
637
|
+
* NOTE: This case likely does not exist anymore, but we will keep it just to be sure.
|
|
638
|
+
*/
|
|
639
|
+
if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
|
|
640
|
+
undefined) {
|
|
641
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
|
|
642
|
+
aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
|
|
643
|
+
madeChange = true;
|
|
644
|
+
}
|
|
645
|
+
if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
|
|
646
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
|
|
647
|
+
aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
|
|
648
|
+
madeChange = true;
|
|
649
|
+
}
|
|
650
|
+
return madeChange;
|
|
651
|
+
}
|
|
593
652
|
/**
|
|
594
653
|
* Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
|
|
595
654
|
* and call a function for each one.
|
|
@@ -688,6 +747,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
688
747
|
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? false,
|
|
689
748
|
});
|
|
690
749
|
}
|
|
750
|
+
/**
|
|
751
|
+
* Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
|
|
752
|
+
* The identifier on the ESA is the id of the element in the [[IModelTransformer.provenanceSourceDb]]
|
|
753
|
+
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
754
|
+
* @param entityInProvenanceSourceId
|
|
755
|
+
* @returns the elementId that the ESA is stored on, esa.Element.Id
|
|
756
|
+
*/
|
|
691
757
|
_queryProvenanceForElement(entityInProvenanceSourceId) {
|
|
692
758
|
return this.provenanceDb.withPreparedStatement(`
|
|
693
759
|
SELECT esa.Element.Id
|
|
@@ -705,6 +771,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
705
771
|
return undefined;
|
|
706
772
|
});
|
|
707
773
|
}
|
|
774
|
+
/**
|
|
775
|
+
* Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
|
|
776
|
+
* The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
|
|
777
|
+
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
778
|
+
* @param entityInProvenanceSourceId
|
|
779
|
+
* @returns
|
|
780
|
+
*/
|
|
708
781
|
_queryProvenanceForRelationship(entityInProvenanceSourceId, sourceRelInfo) {
|
|
709
782
|
return this.provenanceDb.withPreparedStatement(`
|
|
710
783
|
SELECT
|
|
@@ -850,6 +923,20 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
850
923
|
onTransformElement(sourceElement) {
|
|
851
924
|
core_bentley_1.Logger.logTrace(loggerCategory, `onTransformElement(${sourceElement.id}) "${sourceElement.getDisplayLabel()}"`);
|
|
852
925
|
const targetElementProps = this.context.cloneElement(sourceElement, { binaryGeometry: this._options.cloneUsingBinaryGeometry });
|
|
926
|
+
// Special case: source element is the root subject
|
|
927
|
+
if (sourceElement.id === core_common_1.IModel.rootSubjectId) {
|
|
928
|
+
const targetElementId = this.context.findTargetElementId(sourceElement.id);
|
|
929
|
+
// When remapping rootSubject from source to non root subject in target, the code.scope gets remapped incorrectly.
|
|
930
|
+
// This is because the rootSubject has no parent and its code.scope is unique in that it is the id of itself.
|
|
931
|
+
// For all other subjects which do have parents the code.scope and its parent should be in agreement.
|
|
932
|
+
if (targetElementId !== core_bentley_1.Id64.invalid &&
|
|
933
|
+
targetElementId !== core_common_1.IModel.rootSubjectId) {
|
|
934
|
+
const targetElement = this.targetDb.elements.getElement(targetElementId);
|
|
935
|
+
targetElementProps.parent =
|
|
936
|
+
targetElement.parent ?? targetElementProps.parent;
|
|
937
|
+
targetElementProps.code.scope = targetElement.code.scope;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
853
940
|
if (sourceElement instanceof core_backend_1.Subject) {
|
|
854
941
|
if (targetElementProps.jsonProperties?.Subject?.Job) {
|
|
855
942
|
// don't propagate source channels into target (legacy bridge case)
|
|
@@ -860,10 +947,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
860
947
|
}
|
|
861
948
|
/** Returns true if a change within sourceElement is detected.
|
|
862
949
|
* @param sourceElement The Element from the source iModel
|
|
863
|
-
* @param targetElementId The Element from the target iModel to compare against.
|
|
864
950
|
* @note A subclass can override this method to provide custom change detection behavior.
|
|
865
951
|
*/
|
|
866
|
-
hasElementChanged(sourceElement
|
|
952
|
+
hasElementChanged(sourceElement) {
|
|
867
953
|
if (this._sourceChangeDataState === "no-changes")
|
|
868
954
|
return false;
|
|
869
955
|
if (this._sourceChangeDataState === "unconnected")
|
|
@@ -1098,8 +1184,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1098
1184
|
}
|
|
1099
1185
|
}
|
|
1100
1186
|
}
|
|
1101
|
-
if (
|
|
1102
|
-
!this.hasElementChanged(sourceElement, targetElementId))
|
|
1187
|
+
if (!this.hasElementChanged(sourceElement))
|
|
1103
1188
|
return;
|
|
1104
1189
|
this.collectUnmappedReferences(sourceElement);
|
|
1105
1190
|
// targetElementId will be valid (indicating update) or undefined (indicating insert)
|
|
@@ -1168,7 +1253,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1168
1253
|
const targetModeledElementId = this.context.findTargetElementId(sourceModel.id);
|
|
1169
1254
|
// there can only be one repositoryModel per database, so ignore the repo model on remapped subjects
|
|
1170
1255
|
const isRemappedRootSubject = sourceModel.id === core_common_1.IModel.repositoryModelId &&
|
|
1171
|
-
targetModeledElementId
|
|
1256
|
+
targetModeledElementId !== sourceModel.id;
|
|
1172
1257
|
if (isRemappedRootSubject)
|
|
1173
1258
|
return;
|
|
1174
1259
|
const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
|
|
@@ -1183,13 +1268,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1183
1268
|
const targetModelId = this.context.findTargetElementId(sourceModelId);
|
|
1184
1269
|
if (!core_bentley_1.Id64.isValidId64(targetModelId))
|
|
1185
1270
|
return;
|
|
1271
|
+
let sql;
|
|
1272
|
+
if (this.hasDefinitionContainerDeletionFeature) {
|
|
1273
|
+
sql = `
|
|
1274
|
+
SELECT 1
|
|
1275
|
+
FROM bis.DefinitionPartition
|
|
1276
|
+
WHERE ECInstanceId=:targetModelId
|
|
1277
|
+
UNION
|
|
1278
|
+
SELECT 1
|
|
1279
|
+
FROM bis.DefinitionContainer
|
|
1280
|
+
WHERE ECInstanceId=:targetModelId
|
|
1281
|
+
`;
|
|
1282
|
+
}
|
|
1283
|
+
else {
|
|
1284
|
+
sql = `
|
|
1285
|
+
SELECT 1
|
|
1286
|
+
FROM bis.DefinitionPartition
|
|
1287
|
+
WHERE ECInstanceId=:targetModelId
|
|
1288
|
+
`;
|
|
1289
|
+
}
|
|
1186
1290
|
if (this.exporter.sourceDbChanges?.element.deleteIds.has(sourceModelId)) {
|
|
1187
|
-
const isDefinitionPartition = this.targetDb.withPreparedStatement(
|
|
1188
|
-
|
|
1189
|
-
FROM bis.DefinitionPartition
|
|
1190
|
-
WHERE ECInstanceId=?
|
|
1191
|
-
`, (stmt) => {
|
|
1192
|
-
stmt.bindId(1, targetModelId);
|
|
1291
|
+
const isDefinitionPartition = this.targetDb.withPreparedStatement(sql, (stmt) => {
|
|
1292
|
+
stmt.bindId("targetModelId", targetModelId);
|
|
1193
1293
|
const val = stmt.step();
|
|
1194
1294
|
switch (val) {
|
|
1195
1295
|
case core_bentley_1.DbResult.BE_SQLITE_ROW:
|
|
@@ -1197,7 +1297,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1197
1297
|
case core_bentley_1.DbResult.BE_SQLITE_DONE:
|
|
1198
1298
|
return false;
|
|
1199
1299
|
default:
|
|
1200
|
-
(0, core_bentley_1.assert)(false, `unexpected db result: '${stmt}'`);
|
|
1300
|
+
(0, core_bentley_1.assert)(false, `unexpected db result: '${JSON.stringify(stmt)}'`);
|
|
1201
1301
|
}
|
|
1202
1302
|
});
|
|
1203
1303
|
if (isDefinitionPartition) {
|
|
@@ -1303,7 +1403,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1303
1403
|
* @deprecated in 3.x. This method is no longer necessary since the transformer no longer needs to defer elements
|
|
1304
1404
|
*/
|
|
1305
1405
|
async processDeferredElements(_numRetries = 3) { }
|
|
1306
|
-
/** called at the end
|
|
1406
|
+
/** called at the end of a transformation,
|
|
1307
1407
|
* updates the target scope element to say that transformation up through the
|
|
1308
1408
|
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1309
1409
|
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
@@ -1344,21 +1444,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1344
1444
|
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
1345
1445
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1346
1446
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1347
|
-
const
|
|
1447
|
+
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1448
|
+
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1449
|
+
const [syncChangesetsToClearKey, syncChangesetsToUpdateKey] = this
|
|
1348
1450
|
.isReverseSynchronization
|
|
1349
1451
|
? [
|
|
1350
|
-
|
|
1351
|
-
|
|
1452
|
+
pendingReverseSyncChangesetIndicesKey,
|
|
1453
|
+
pendingSyncChangesetIndicesKey,
|
|
1352
1454
|
]
|
|
1353
1455
|
: [
|
|
1354
|
-
|
|
1355
|
-
|
|
1456
|
+
pendingSyncChangesetIndicesKey,
|
|
1457
|
+
pendingReverseSyncChangesetIndicesKey,
|
|
1356
1458
|
];
|
|
1459
|
+
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1460
|
+
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1461
|
+
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1462
|
+
// stuff to it which would break future synchronizations
|
|
1357
1463
|
for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1358
|
-
|
|
1359
|
-
//
|
|
1360
|
-
|
|
1361
|
-
|
|
1464
|
+
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1465
|
+
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
1466
|
+
jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
|
|
1467
|
+
return csIndex > this._startingChangesetIndices.source;
|
|
1468
|
+
});
|
|
1362
1469
|
// if reverse sync then we may have received provenance changes which should be marked as sync changes
|
|
1363
1470
|
if (this.isReverseSynchronization) {
|
|
1364
1471
|
nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
|
|
@@ -1372,9 +1479,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1372
1479
|
...this._targetScopeProvenanceProps,
|
|
1373
1480
|
jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
|
|
1374
1481
|
});
|
|
1482
|
+
this.clearCachedSynchronizationVersion();
|
|
1375
1483
|
}
|
|
1376
1484
|
// FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
|
|
1377
|
-
|
|
1485
|
+
finalizeTransformation() {
|
|
1378
1486
|
this.importer.finalize();
|
|
1379
1487
|
this.updateSynchronizationVersion();
|
|
1380
1488
|
if (this._partiallyCommittedEntities.size > 0) {
|
|
@@ -1403,31 +1511,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1403
1511
|
this.targetDb.codeValueBehavior = "trim-unicode-whitespace";
|
|
1404
1512
|
}
|
|
1405
1513
|
/* eslint-enable @itwin/no-internal */
|
|
1406
|
-
const defaultSaveTargetChanges = () => this.targetDb.saveChanges();
|
|
1407
|
-
await (options?.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
1408
|
-
if (this.isReverseSynchronization)
|
|
1409
|
-
this.sourceDb.saveChanges();
|
|
1410
|
-
const description = `${this._isProvenanceInitTransform
|
|
1411
|
-
? options?.provenanceInitTransformChangesetDescription ??
|
|
1412
|
-
`initialized branch provenance with master iModel: ${this.sourceDb.iModelId}`
|
|
1413
|
-
: this.isForwardSynchronization
|
|
1414
|
-
? options?.forwardSyncBranchChangesetDescription ??
|
|
1415
|
-
`Forward sync of iModel: ${this.sourceDb.iModelId}`
|
|
1416
|
-
: options?.reverseSyncMasterChangesetDescription ??
|
|
1417
|
-
`Reverse sync of iModel: ${this.sourceDb.iModelId}`}`;
|
|
1418
|
-
if (this.targetDb.isBriefcaseDb()) {
|
|
1419
|
-
// This relies on authorizationClient on iModelHost being defined, otherwise this will fail
|
|
1420
|
-
await this.targetDb.pushChanges({
|
|
1421
|
-
description,
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
if (this.isReverseSynchronization && this.sourceDb.isBriefcaseDb()) {
|
|
1425
|
-
// This relies on authorizationClient on iModelHost being defined, otherwise this will fail
|
|
1426
|
-
await this.sourceDb.pushChanges({
|
|
1427
|
-
description: options?.reverseSyncBranchChangesetDescription ??
|
|
1428
|
-
`Update provenance in response to a reverse sync to iModel: ${this.targetDb.iModelId}`,
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
1514
|
}
|
|
1432
1515
|
/** Imports all relationships that subclass from the specified base class.
|
|
1433
1516
|
* @param baseRelClassFullName The specified base relationship class.
|
|
@@ -1479,15 +1562,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1479
1562
|
core_bentley_1.Logger.logWarning(loggerCategory, "tried to delete a relationship that wasn't in change data");
|
|
1480
1563
|
return;
|
|
1481
1564
|
}
|
|
1482
|
-
const
|
|
1483
|
-
{
|
|
1565
|
+
const id = deletedRelData.relId ??
|
|
1566
|
+
this.targetDb.relationships.tryGetInstance(deletedRelData.classFullName, {
|
|
1484
1567
|
sourceId: deletedRelData.sourceIdInTarget,
|
|
1485
1568
|
targetId: deletedRelData.targetIdInTarget,
|
|
1486
|
-
};
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1569
|
+
})?.id;
|
|
1570
|
+
if (id) {
|
|
1571
|
+
this.importer.deleteRelationship({
|
|
1572
|
+
id,
|
|
1573
|
+
classFullName: deletedRelData.classFullName,
|
|
1574
|
+
});
|
|
1491
1575
|
}
|
|
1492
1576
|
if (deletedRelData.provenanceAspectId) {
|
|
1493
1577
|
try {
|
|
@@ -1531,8 +1615,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1531
1615
|
const json = JSON.parse(statement.getValue(2).getString());
|
|
1532
1616
|
const targetRelInstanceId = json.targetRelInstanceId ?? json.provenanceRelInstanceId;
|
|
1533
1617
|
if (targetRelInstanceId) {
|
|
1534
|
-
|
|
1535
|
-
|
|
1618
|
+
this.importer.deleteRelationship({
|
|
1619
|
+
id: targetRelInstanceId,
|
|
1620
|
+
classFullName: core_backend_1.ElementRefersToElements.classFullName,
|
|
1621
|
+
});
|
|
1536
1622
|
}
|
|
1537
1623
|
aspectDeleteIds.push(statement.getValue(0).getId());
|
|
1538
1624
|
}
|
|
@@ -1629,7 +1715,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1629
1715
|
let schemaFileName = schema.name + ext;
|
|
1630
1716
|
// many file systems have a max file-name/path-segment size of 255, so we workaround that on all systems
|
|
1631
1717
|
const systemMaxPathSegmentSize = 255;
|
|
1632
|
-
|
|
1718
|
+
// windows usually has a limit for the total path length of 260
|
|
1719
|
+
const windowsMaxPathLimit = 260;
|
|
1720
|
+
if (schemaFileName.length > systemMaxPathSegmentSize ||
|
|
1721
|
+
path.join(this._schemaExportDir, schemaFileName).length >=
|
|
1722
|
+
windowsMaxPathLimit) {
|
|
1633
1723
|
// this name should be well under 255 bytes
|
|
1634
1724
|
// ( 100 + (Number.MAX_SAFE_INTEGER.toString().length = 16) + (ext.length = 13) ) = 129 which is less than 255
|
|
1635
1725
|
// You'd have to be past 2**53-1 (Number.MAX_SAFE_INTEGER) long named schemas in order to hit decimal formatting,
|
|
@@ -1763,6 +1853,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1763
1853
|
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)")) {
|
|
1764
1854
|
relationshipECClassIds.add(row.ECInstanceId);
|
|
1765
1855
|
}
|
|
1856
|
+
const elementECClassIds = new Set();
|
|
1857
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.Element)")) {
|
|
1858
|
+
elementECClassIds.add(row.ECInstanceId);
|
|
1859
|
+
}
|
|
1766
1860
|
// For later use when processing deletes.
|
|
1767
1861
|
const alreadyImportedElementInserts = new Set();
|
|
1768
1862
|
const alreadyImportedModelInserts = new Set();
|
|
@@ -1801,7 +1895,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1801
1895
|
change.Scope.Id === this.targetScopeElementId) {
|
|
1802
1896
|
elemIdToScopeEsa.set(change.Element.Id, change);
|
|
1803
1897
|
}
|
|
1804
|
-
else if (changeType === "Inserted" || changeType === "Updated")
|
|
1898
|
+
else if ((changeType === "Inserted" || changeType === "Updated") &&
|
|
1899
|
+
change.ECClassId !== undefined &&
|
|
1900
|
+
elementECClassIds.has(change.ECClassId))
|
|
1805
1901
|
hasElementChangedCache.add(change.ECInstanceId);
|
|
1806
1902
|
}
|
|
1807
1903
|
// Loop to process deletes.
|
|
@@ -1815,7 +1911,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1815
1911
|
if (changeType !== "Deleted" ||
|
|
1816
1912
|
relationshipECClassIdsToSkip.has(ecClassId))
|
|
1817
1913
|
continue;
|
|
1818
|
-
this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1914
|
+
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1819
1915
|
}
|
|
1820
1916
|
csReader.close();
|
|
1821
1917
|
}
|
|
@@ -1832,116 +1928,98 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1832
1928
|
* @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
|
|
1833
1929
|
* @returns void
|
|
1834
1930
|
*/
|
|
1835
|
-
processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1931
|
+
async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1836
1932
|
// we need a connected iModel with changes to remap elements with deletions
|
|
1837
1933
|
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1838
|
-
const noChanges = this.
|
|
1934
|
+
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
1839
1935
|
if (notConnectedModel || noChanges)
|
|
1840
1936
|
return;
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
const
|
|
1846
|
-
|
|
1847
|
-
const sourceElemFedGuid = change.FederationGuid;
|
|
1937
|
+
/**
|
|
1938
|
+
* if our ChangedECInstance is in the provenanceDb, then we can use the ids we find in the ChangedECInstance to query for ESAs.
|
|
1939
|
+
* This is because the ESAs are stored on an element Id thats present in the provenanceDb.
|
|
1940
|
+
*/
|
|
1941
|
+
const changeDataInProvenanceDb = this.sourceDb === this.provenanceDb;
|
|
1942
|
+
const getTargetIdFromSourceId = async (id) => {
|
|
1848
1943
|
let identifierValue;
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1944
|
+
let element;
|
|
1945
|
+
if (isRelationship) {
|
|
1946
|
+
element = this.sourceDb.elements.tryGetElement(id);
|
|
1947
|
+
}
|
|
1948
|
+
const fedGuid = isRelationship
|
|
1949
|
+
? element?.federationGuid
|
|
1950
|
+
: change.FederationGuid;
|
|
1951
|
+
if (changeDataInProvenanceDb) {
|
|
1952
|
+
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
1953
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT esa.Identifier FROM bis.ExternalSourceAspect esa WHERE Scope.Id=:scopeId AND Kind=:kind AND Element.Id=:relatedElementId LIMIT 1", core_common_1.QueryBinder.from([
|
|
1954
|
+
this.targetScopeElementId,
|
|
1955
|
+
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1956
|
+
id,
|
|
1957
|
+
]))) {
|
|
1958
|
+
identifierValue = row.Identifier;
|
|
1856
1959
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1960
|
+
identifierValue =
|
|
1961
|
+
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.Identifier;
|
|
1859
1962
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1963
|
+
// Check for targetId by an esa first
|
|
1964
|
+
if (changeDataInProvenanceDb && identifierValue) {
|
|
1965
|
+
const targetId = identifierValue;
|
|
1966
|
+
return targetId;
|
|
1864
1967
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
this._queryElemIdByFedGuid(this.targetDb, sourceElemFedGuid)) ||
|
|
1870
|
-
// FIXME<MIKE>: describe why it's safe to assume nothing has been deleted in provenanceDb
|
|
1871
|
-
this._queryProvenanceForElement(instId);
|
|
1872
|
-
// since we are processing one changeset at a time, we can see local source deletes
|
|
1873
|
-
// of entities that were never synced and can be safely ignored
|
|
1874
|
-
const deletionNotInTarget = !targetId;
|
|
1875
|
-
if (deletionNotInTarget)
|
|
1876
|
-
return;
|
|
1877
|
-
this.context.remapElement(instId, targetId);
|
|
1878
|
-
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
1879
|
-
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
1880
|
-
if (alreadyImportedElementInserts.has(targetId)) {
|
|
1881
|
-
this.exporter.sourceDbChanges?.element.deleteIds.delete(instId);
|
|
1882
|
-
}
|
|
1883
|
-
if (alreadyImportedModelInserts.has(targetId)) {
|
|
1884
|
-
this.exporter.sourceDbChanges?.model.deleteIds.delete(instId);
|
|
1968
|
+
// Check for targetId using sourceId's fedguid if we didn't find an esa.
|
|
1969
|
+
if (fedGuid) {
|
|
1970
|
+
const targetId = this._queryElemIdByFedGuid(this.targetDb, fedGuid);
|
|
1971
|
+
return targetId;
|
|
1885
1972
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1973
|
+
return undefined;
|
|
1974
|
+
};
|
|
1975
|
+
const changedInstanceId = change.ECInstanceId;
|
|
1976
|
+
if (isRelationship) {
|
|
1890
1977
|
const sourceIdOfRelationshipInSource = change.SourceECInstanceId;
|
|
1891
1978
|
const targetIdOfRelationshipInSource = change.TargetECInstanceId;
|
|
1892
|
-
const
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
try {
|
|
1898
|
-
element = this.sourceDb.elements.getElement(id);
|
|
1899
|
-
}
|
|
1900
|
-
catch (err) {
|
|
1901
|
-
return undefined;
|
|
1902
|
-
}
|
|
1903
|
-
const fedGuid = element.federationGuid;
|
|
1904
|
-
let identifierValue;
|
|
1905
|
-
if (queryCanAccessProvenance) {
|
|
1906
|
-
const aspects = this.sourceDb.elements.getAspects(id, core_backend_1.ExternalSourceAspect.classFullName);
|
|
1907
|
-
for (const aspect of aspects) {
|
|
1908
|
-
if (aspect.element.id === id &&
|
|
1909
|
-
aspect.scope.id === this.targetScopeElementId)
|
|
1910
|
-
identifierValue = aspect.identifier;
|
|
1911
|
-
}
|
|
1912
|
-
if (identifierValue === undefined) {
|
|
1913
|
-
if (mapOfDeletedElemIdToScopeEsas.get(id) !== undefined)
|
|
1914
|
-
identifierValue =
|
|
1915
|
-
mapOfDeletedElemIdToScopeEsas.get(id).Identifier;
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
return ((queryCanAccessProvenance && identifierValue) ||
|
|
1919
|
-
// maybe batching these queries would perform better but we should
|
|
1920
|
-
// try to attach the second db and query both together anyway
|
|
1921
|
-
(fedGuid && this._queryElemIdByFedGuid(this.targetDb, fedGuid)));
|
|
1922
|
-
});
|
|
1923
|
-
if (sourceIdInTarget && targetIdInTarget) {
|
|
1924
|
-
this._deletedSourceRelationshipData.set(instId, {
|
|
1979
|
+
const classFullName = change.$meta?.classFullName;
|
|
1980
|
+
const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
|
|
1981
|
+
const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
|
|
1982
|
+
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
1983
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1925
1984
|
classFullName: classFullName ?? "",
|
|
1926
|
-
sourceIdInTarget,
|
|
1927
|
-
targetIdInTarget,
|
|
1985
|
+
sourceIdInTarget: sourceIdOfRelationshipInTarget,
|
|
1986
|
+
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
1928
1987
|
});
|
|
1929
1988
|
}
|
|
1930
|
-
else {
|
|
1931
|
-
|
|
1932
|
-
const relProvenance = this._queryProvenanceForRelationship(instId, {
|
|
1989
|
+
else if (this.sourceDb === this.provenanceSourceDb) {
|
|
1990
|
+
const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
|
|
1933
1991
|
classFullName: classFullName ?? "",
|
|
1934
1992
|
sourceId: sourceIdOfRelationshipInSource,
|
|
1935
1993
|
targetId: targetIdOfRelationshipInSource,
|
|
1936
1994
|
});
|
|
1937
1995
|
if (relProvenance && relProvenance.relationshipId)
|
|
1938
|
-
this._deletedSourceRelationshipData.set(
|
|
1996
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1939
1997
|
classFullName: classFullName ?? "",
|
|
1940
1998
|
relId: relProvenance.relationshipId,
|
|
1941
1999
|
provenanceAspectId: relProvenance.aspectId,
|
|
1942
2000
|
});
|
|
1943
2001
|
}
|
|
1944
2002
|
}
|
|
2003
|
+
else {
|
|
2004
|
+
let targetId = await getTargetIdFromSourceId(changedInstanceId);
|
|
2005
|
+
if (targetId === undefined && this.sourceDb === this.provenanceSourceDb) {
|
|
2006
|
+
targetId = this._queryProvenanceForElement(changedInstanceId);
|
|
2007
|
+
}
|
|
2008
|
+
// since we are processing one changeset at a time, we can see local source deletes
|
|
2009
|
+
// of entities that were never synced and can be safely ignored
|
|
2010
|
+
const deletionNotInTarget = !targetId;
|
|
2011
|
+
if (deletionNotInTarget)
|
|
2012
|
+
return;
|
|
2013
|
+
this.context.remapElement(changedInstanceId, targetId);
|
|
2014
|
+
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
2015
|
+
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
2016
|
+
if (alreadyImportedElementInserts.has(targetId)) {
|
|
2017
|
+
this.exporter.sourceDbChanges?.element.deleteIds.delete(changedInstanceId);
|
|
2018
|
+
}
|
|
2019
|
+
if (alreadyImportedModelInserts.has(targetId)) {
|
|
2020
|
+
this.exporter.sourceDbChanges?.model.deleteIds.delete(changedInstanceId);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
1945
2023
|
}
|
|
1946
2024
|
async _tryInitChangesetData(args) {
|
|
1947
2025
|
if (!args ||
|
|
@@ -1950,7 +2028,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1950
2028
|
this._sourceChangeDataState = "unconnected";
|
|
1951
2029
|
return;
|
|
1952
2030
|
}
|
|
1953
|
-
const noChanges = this.
|
|
2031
|
+
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
1954
2032
|
if (noChanges) {
|
|
1955
2033
|
this._sourceChangeDataState = "no-changes";
|
|
1956
2034
|
this._csFileProps = [];
|
|
@@ -1960,7 +2038,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1960
2038
|
// to ignore those already processed changes
|
|
1961
2039
|
const startChangesetIndexOrId = args.startChangeset?.index ??
|
|
1962
2040
|
args.startChangeset?.id ??
|
|
1963
|
-
this.
|
|
2041
|
+
this.synchronizationVersion.index + 1;
|
|
1964
2042
|
const endChangesetId = this.sourceDb.changeset.id;
|
|
1965
2043
|
const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
|
|
1966
2044
|
? indexOrId
|
|
@@ -1972,17 +2050,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1972
2050
|
accessToken: args.accessToken,
|
|
1973
2051
|
})
|
|
1974
2052
|
.then((changeset) => changeset.index)));
|
|
1975
|
-
const missingChangesets = startChangesetIndex > this.
|
|
2053
|
+
const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
|
|
1976
2054
|
if (!this._options.ignoreMissingChangesetsInSynchronizations &&
|
|
1977
|
-
startChangesetIndex !== this.
|
|
1978
|
-
this.
|
|
2055
|
+
startChangesetIndex !== this.synchronizationVersion.index + 1 &&
|
|
2056
|
+
this.synchronizationVersion.index !== -1) {
|
|
1979
2057
|
throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
|
|
1980
2058
|
" startChangesetId should be" +
|
|
1981
2059
|
" exactly the first changeset *after* the previous synchronization to not miss data." +
|
|
1982
2060
|
` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
|
|
1983
|
-
` but the previous synchronization for this targetScopeElement was '${this.
|
|
1984
|
-
` which is changeset #${this.
|
|
1985
|
-
` #${this.
|
|
2061
|
+
` but the previous synchronization for this targetScopeElement was '${this.synchronizationVersion.id}'` +
|
|
2062
|
+
` which is changeset #${this.synchronizationVersion.index}. The transformer expected` +
|
|
2063
|
+
` #${this.synchronizationVersion.index + 1}.`);
|
|
1986
2064
|
}
|
|
1987
2065
|
nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps should be set by now");
|
|
1988
2066
|
const changesetsToSkip = this.isReverseSynchronization
|
|
@@ -2004,19 +2082,20 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2004
2082
|
csFileProps.push(...fileProps);
|
|
2005
2083
|
}
|
|
2006
2084
|
this._csFileProps = csFileProps;
|
|
2007
|
-
|
|
2085
|
+
/** Theres a possibility that our csFileProps length is still 0 here, since we skip cs indices found in the pendingSync and pendingReverseSync indices arrays. */
|
|
2086
|
+
this._sourceChangeDataState =
|
|
2087
|
+
this._csFileProps.length === 0 ? "no-changes" : "has-changes";
|
|
2008
2088
|
}
|
|
2009
2089
|
/** Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2010
2090
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2011
2091
|
*/
|
|
2012
|
-
async processAll(
|
|
2092
|
+
async processAll() {
|
|
2013
2093
|
this.logSettings();
|
|
2014
2094
|
this.initScopeProvenance();
|
|
2015
2095
|
await this.initialize();
|
|
2016
2096
|
await this.exporter.exportCodeSpecs();
|
|
2017
2097
|
await this.exporter.exportFonts();
|
|
2018
2098
|
if (this._options.skipPropagateChangesToRootElements) {
|
|
2019
|
-
// FIXME<NICK>: This option in exportAll was a maybe.
|
|
2020
2099
|
// The RepositoryModel and root Subject of the target iModel should not be transformed.
|
|
2021
2100
|
await this.exporter.exportChildElements(core_common_1.IModel.rootSubjectId); // start below the root Subject
|
|
2022
2101
|
await this.exporter.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true); // after the Subject hierarchy, process the other elements of the RepositoryModel
|
|
@@ -2030,13 +2109,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2030
2109
|
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
2031
2110
|
if (this._options.forceExternalSourceAspectProvenance &&
|
|
2032
2111
|
this.shouldDetectDeletes()) {
|
|
2112
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2033
2113
|
await this.detectElementDeletes();
|
|
2114
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2034
2115
|
await this.detectRelationshipDeletes();
|
|
2035
2116
|
}
|
|
2036
2117
|
if (this._options.optimizeGeometry)
|
|
2037
2118
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2038
2119
|
this.importer.computeProjectExtents();
|
|
2039
|
-
|
|
2120
|
+
this.finalizeTransformation();
|
|
2040
2121
|
}
|
|
2041
2122
|
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
2042
2123
|
this._lastProvenanceEntityInfo =
|
|
@@ -2051,205 +2132,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2051
2132
|
: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
2052
2133
|
};
|
|
2053
2134
|
}
|
|
2054
|
-
/**
|
|
2055
|
-
* Load the state of the active transformation from an open SQLiteDb
|
|
2056
|
-
* You can override this if you'd like to load from custom tables in the resumable dump state, but you should call
|
|
2057
|
-
* this super implementation
|
|
2058
|
-
* @note the SQLiteDb must be open
|
|
2059
|
-
*/
|
|
2060
|
-
loadStateFromDb(db) {
|
|
2061
|
-
const lastProvenanceEntityInfo = db.withSqliteStatement(`SELECT entityId, aspectId, aspectVersion, aspectKind FROM ${IModelTransformer.lastProvenanceEntityInfoTable}`, (stmt) => {
|
|
2062
|
-
if (core_bentley_1.DbResult.BE_SQLITE_ROW !== stmt.step())
|
|
2063
|
-
throw Error("expected row when getting lastProvenanceEntityId from target state table");
|
|
2064
|
-
const entityId = stmt.getValueString(0);
|
|
2065
|
-
const isGuidOrGuidPair = entityId.includes("-");
|
|
2066
|
-
return isGuidOrGuidPair
|
|
2067
|
-
? entityId
|
|
2068
|
-
: {
|
|
2069
|
-
entityId,
|
|
2070
|
-
aspectId: stmt.getValueString(1),
|
|
2071
|
-
aspectVersion: stmt.getValueString(2),
|
|
2072
|
-
aspectKind: stmt.getValueString(3),
|
|
2073
|
-
};
|
|
2074
|
-
});
|
|
2075
|
-
/*
|
|
2076
|
-
// TODO: maybe save transformer state resumption state based on target changset and require calls
|
|
2077
|
-
// to saveChanges
|
|
2078
|
-
if () {
|
|
2079
|
-
const [sourceFedGuid, targetFedGuid, relClassFullName] = lastProvenanceEntityInfo.split("/");
|
|
2080
|
-
const isRelProvenance = targetFedGuid !== undefined;
|
|
2081
|
-
const instanceId = isRelProvenance
|
|
2082
|
-
? this.targetDb.elements.getElement({federationGuid: sourceFedGuid})
|
|
2083
|
-
: "";
|
|
2084
|
-
//const classId =
|
|
2085
|
-
if (isRelProvenance) {
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
*/
|
|
2089
|
-
const targetHasCorrectLastProvenance = typeof lastProvenanceEntityInfo === "string" ||
|
|
2090
|
-
// ignore provenance check if it's null since we can't bind those ids
|
|
2091
|
-
!core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.entityId) ||
|
|
2092
|
-
!core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.aspectId) ||
|
|
2093
|
-
this.provenanceDb.withPreparedStatement(`
|
|
2094
|
-
SELECT Version FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
2095
|
-
WHERE Scope.Id=:scopeId
|
|
2096
|
-
AND ECInstanceId=:aspectId
|
|
2097
|
-
AND Kind=:kind
|
|
2098
|
-
AND Element.Id=:entityId
|
|
2099
|
-
`, (statement) => {
|
|
2100
|
-
statement.bindId("scopeId", this.targetScopeElementId);
|
|
2101
|
-
statement.bindId("aspectId", lastProvenanceEntityInfo.aspectId);
|
|
2102
|
-
statement.bindString("kind", lastProvenanceEntityInfo.aspectKind);
|
|
2103
|
-
statement.bindId("entityId", lastProvenanceEntityInfo.entityId);
|
|
2104
|
-
const stepResult = statement.step();
|
|
2105
|
-
switch (stepResult) {
|
|
2106
|
-
case core_bentley_1.DbResult.BE_SQLITE_ROW:
|
|
2107
|
-
const version = statement.getValue(0).getString();
|
|
2108
|
-
return version === lastProvenanceEntityInfo.aspectVersion;
|
|
2109
|
-
case core_bentley_1.DbResult.BE_SQLITE_DONE:
|
|
2110
|
-
return false;
|
|
2111
|
-
default:
|
|
2112
|
-
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.SQLiteError, `got sql error ${stepResult}`);
|
|
2113
|
-
}
|
|
2114
|
-
});
|
|
2115
|
-
if (!targetHasCorrectLastProvenance)
|
|
2116
|
-
throw Error([
|
|
2117
|
-
"Target for resuming from does not have the expected provenance ",
|
|
2118
|
-
"from the target that the resume state was made with",
|
|
2119
|
-
].join("\n"));
|
|
2120
|
-
this._lastProvenanceEntityInfo = lastProvenanceEntityInfo;
|
|
2121
|
-
const state = db.withSqliteStatement(`SELECT data FROM ${IModelTransformer.jsStateTable}`, (stmt) => {
|
|
2122
|
-
if (core_bentley_1.DbResult.BE_SQLITE_ROW !== stmt.step())
|
|
2123
|
-
throw Error("expected row when getting data from js state table");
|
|
2124
|
-
return JSON.parse(stmt.getValueString(0));
|
|
2125
|
-
});
|
|
2126
|
-
if (state.transformerClass !== this.constructor.name)
|
|
2127
|
-
throw Error("resuming from a differently named transformer class, it is not necessarily valid to resume with a different transformer class");
|
|
2128
|
-
// force assign to readonly options since we do not know how the transformer subclass takes options to pass to the superclass
|
|
2129
|
-
this._options = state.options;
|
|
2130
|
-
this.context.loadStateFromDb(db);
|
|
2131
|
-
this.importer.loadStateFromJson(state.importerState);
|
|
2132
|
-
this.exporter.loadStateFromJson(state.exporterState);
|
|
2133
|
-
this._elementsWithExplicitlyTrackedProvenance =
|
|
2134
|
-
core_bentley_1.CompressedId64Set.decompressSet(state.explicitlyTrackedElements);
|
|
2135
|
-
this.loadAdditionalStateJson(state.additionalState);
|
|
2136
|
-
}
|
|
2137
|
-
/**
|
|
2138
|
-
* @deprecated in 0.1.x, this is buggy, and it is now equivalently efficient to simply restart the transformation
|
|
2139
|
-
* from the original changeset
|
|
2140
|
-
*
|
|
2141
|
-
* Return a new transformer instance with the same remappings state as saved from a previous [[IModelTransformer.saveStateToFile]] call.
|
|
2142
|
-
* This allows you to "resume" an iModel transformation, you will have to call [[IModelTransformer.processChanges]]/[[IModelTransformer.processAll]]
|
|
2143
|
-
* again but the remapping state will cause already mapped elements to be skipped.
|
|
2144
|
-
* To "resume" an iModel Transformation you need:
|
|
2145
|
-
* - the sourceDb at the same changeset
|
|
2146
|
-
* - the same targetDb in the state in which it was before
|
|
2147
|
-
* @param statePath the path to the serialized state of the transformer, use [[IModelTransformer.saveStateToFile]] to get this from an existing transformer instance
|
|
2148
|
-
* @param constructorArgs remaining arguments that you would normally pass to the Transformer subclass you are using, usually (sourceDb, targetDb)
|
|
2149
|
-
* @note custom transformers with custom state may need to override this method in order to handle loading their own custom state somewhere
|
|
2150
|
-
*/
|
|
2151
|
-
static resumeTransformation(statePath, ...constructorArgs) {
|
|
2152
|
-
const transformer = new this(...constructorArgs);
|
|
2153
|
-
const db = new core_backend_1.SQLiteDb();
|
|
2154
|
-
db.openDb(statePath, core_bentley_1.OpenMode.Readonly);
|
|
2155
|
-
try {
|
|
2156
|
-
transformer.loadStateFromDb(db);
|
|
2157
|
-
}
|
|
2158
|
-
finally {
|
|
2159
|
-
db.closeDb();
|
|
2160
|
-
}
|
|
2161
|
-
return transformer;
|
|
2162
|
-
}
|
|
2163
|
-
/**
|
|
2164
|
-
* You may override this to store arbitrary json state in a transformer state dump, useful for some resumptions
|
|
2165
|
-
* @see [[IModelTransformer.saveStateToFile]]
|
|
2166
|
-
*/
|
|
2167
|
-
getAdditionalStateJson() {
|
|
2168
|
-
return {};
|
|
2169
|
-
}
|
|
2170
|
-
/**
|
|
2171
|
-
* You may override this to load arbitrary json state in a transformer state dump, useful for some resumptions
|
|
2172
|
-
* @see [[IModelTransformer.loadStateFromFile]]
|
|
2173
|
-
*/
|
|
2174
|
-
loadAdditionalStateJson(_additionalState) { }
|
|
2175
|
-
/**
|
|
2176
|
-
* Save the state of the active transformation to an open SQLiteDb
|
|
2177
|
-
* You can override this if you'd like to write custom tables to the resumable dump state, but you should call
|
|
2178
|
-
* this super implementation
|
|
2179
|
-
* @note the SQLiteDb must be open
|
|
2180
|
-
*/
|
|
2181
|
-
saveStateToDb(db) {
|
|
2182
|
-
const jsonState = {
|
|
2183
|
-
transformerClass: this.constructor.name,
|
|
2184
|
-
options: this._options,
|
|
2185
|
-
explicitlyTrackedElements: core_bentley_1.CompressedId64Set.compressSet(this._elementsWithExplicitlyTrackedProvenance),
|
|
2186
|
-
importerState: this.importer.saveStateToJson(),
|
|
2187
|
-
exporterState: this.exporter.saveStateToJson(),
|
|
2188
|
-
additionalState: this.getAdditionalStateJson(),
|
|
2189
|
-
};
|
|
2190
|
-
this.context.saveStateToDb(db);
|
|
2191
|
-
if (core_bentley_1.DbResult.BE_SQLITE_DONE !==
|
|
2192
|
-
db.executeSQL(`CREATE TABLE ${IModelTransformer.jsStateTable} (data TEXT)`))
|
|
2193
|
-
throw Error("Failed to create the js state table in the state database");
|
|
2194
|
-
if (core_bentley_1.DbResult.BE_SQLITE_DONE !==
|
|
2195
|
-
db.executeSQL(`
|
|
2196
|
-
CREATE TABLE ${IModelTransformer.lastProvenanceEntityInfoTable} (
|
|
2197
|
-
-- either the invalid id for null provenance state, federation guid (or pair for rels) of the entity, or a hex element id
|
|
2198
|
-
entityId TEXT,
|
|
2199
|
-
-- the following are only valid if the above entityId is a hex id representation
|
|
2200
|
-
aspectId TEXT,
|
|
2201
|
-
aspectVersion TEXT,
|
|
2202
|
-
aspectKind TEXT
|
|
2203
|
-
)
|
|
2204
|
-
`))
|
|
2205
|
-
throw Error("Failed to create the target state table in the state database");
|
|
2206
|
-
db.saveChanges();
|
|
2207
|
-
db.withSqliteStatement(`INSERT INTO ${IModelTransformer.jsStateTable} (data) VALUES (?)`, (stmt) => {
|
|
2208
|
-
stmt.bindString(1, JSON.stringify(jsonState));
|
|
2209
|
-
if (core_bentley_1.DbResult.BE_SQLITE_DONE !== stmt.step())
|
|
2210
|
-
throw Error("Failed to insert options into the state database");
|
|
2211
|
-
});
|
|
2212
|
-
db.withSqliteStatement(`INSERT INTO ${IModelTransformer.lastProvenanceEntityInfoTable} (entityId, aspectId, aspectVersion, aspectKind) VALUES (?,?,?,?)`, (stmt) => {
|
|
2213
|
-
const lastProvenanceEntityInfo = this
|
|
2214
|
-
._lastProvenanceEntityInfo;
|
|
2215
|
-
stmt.bindString(1, lastProvenanceEntityInfo?.entityId ??
|
|
2216
|
-
this._lastProvenanceEntityInfo);
|
|
2217
|
-
stmt.bindString(2, lastProvenanceEntityInfo?.aspectId ?? "");
|
|
2218
|
-
stmt.bindString(3, lastProvenanceEntityInfo?.aspectVersion ?? "");
|
|
2219
|
-
stmt.bindString(4, lastProvenanceEntityInfo?.aspectKind ?? "");
|
|
2220
|
-
if (core_bentley_1.DbResult.BE_SQLITE_DONE !== stmt.step())
|
|
2221
|
-
throw Error("Failed to insert options into the state database");
|
|
2222
|
-
});
|
|
2223
|
-
db.saveChanges();
|
|
2224
|
-
}
|
|
2225
|
-
/**
|
|
2226
|
-
* @deprecated in 0.1.x, this is buggy, and it is now equivalently efficient to simply restart the transformation
|
|
2227
|
-
* from the original changeset
|
|
2228
|
-
*
|
|
2229
|
-
* Save the state of the active transformation to a file path, if a file at the path already exists, it will be overwritten
|
|
2230
|
-
* This state can be used by [[IModelTransformer.resumeTransformation]] to resume a transformation from this point.
|
|
2231
|
-
* The serialization format is a custom sqlite database.
|
|
2232
|
-
* @note custom transformers with custom state may override [[IModelTransformer.saveStateToDb]] or [[IModelTransformer.getAdditionalStateJson]]
|
|
2233
|
-
* and [[IModelTransformer.loadStateFromDb]] (with a super call) or [[IModelTransformer.loadAdditionalStateJson]]
|
|
2234
|
-
* if they have custom state that needs to be stored with
|
|
2235
|
-
* potentially inside the same sqlite file in separate tables
|
|
2236
|
-
*/
|
|
2237
|
-
saveStateToFile(nativeStatePath) {
|
|
2238
|
-
const db = new core_backend_1.SQLiteDb();
|
|
2239
|
-
if (core_backend_1.IModelJsFs.existsSync(nativeStatePath))
|
|
2240
|
-
core_backend_1.IModelJsFs.unlinkSync(nativeStatePath);
|
|
2241
|
-
db.createDb(nativeStatePath);
|
|
2242
|
-
try {
|
|
2243
|
-
this.saveStateToDb(db);
|
|
2244
|
-
db.saveChanges();
|
|
2245
|
-
}
|
|
2246
|
-
finally {
|
|
2247
|
-
db.closeDb();
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
2135
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2251
2136
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2252
|
-
* @note the transformer
|
|
2137
|
+
* @note the transformer assumes that you saveChanges after processing changes. You should not
|
|
2138
|
+
* modify the iModel after processChanges until saveChanges, failure to do so may result in corrupted
|
|
2139
|
+
* data loss in future branch operations
|
|
2253
2140
|
* @note if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2254
2141
|
* will automatically be determined and used
|
|
2255
2142
|
* @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.
|
|
@@ -2265,7 +2152,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2265
2152
|
if (this._options.optimizeGeometry)
|
|
2266
2153
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2267
2154
|
this.importer.computeProjectExtents();
|
|
2268
|
-
|
|
2155
|
+
this.finalizeTransformation();
|
|
2156
|
+
const defaultSaveTargetChanges = () => {
|
|
2157
|
+
this.targetDb.saveChanges();
|
|
2158
|
+
};
|
|
2159
|
+
await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
2269
2160
|
}
|
|
2270
2161
|
/** Changeset data must be initialized in order to build correct changeOptions.
|
|
2271
2162
|
* Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
|
|
@@ -2284,7 +2175,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2284
2175
|
? { startChangeset: opts.startChangeset }
|
|
2285
2176
|
: {
|
|
2286
2177
|
startChangeset: {
|
|
2287
|
-
index: this.
|
|
2178
|
+
index: this.synchronizationVersion.index + 1,
|
|
2288
2179
|
},
|
|
2289
2180
|
}),
|
|
2290
2181
|
};
|
|
@@ -2303,10 +2194,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2303
2194
|
}
|
|
2304
2195
|
exports.IModelTransformer = IModelTransformer;
|
|
2305
2196
|
IModelTransformer.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.";
|
|
2306
|
-
/** @internal the name of the table where javascript state of the transformer is serialized in transformer state dumps */
|
|
2307
|
-
IModelTransformer.jsStateTable = "TransformerJsState";
|
|
2308
|
-
/** @internal the name of the table where the target state heuristics is serialized in transformer state dumps */
|
|
2309
|
-
IModelTransformer.lastProvenanceEntityInfoTable = "LastProvenanceEntityInfo";
|
|
2310
2197
|
/** IModelTransformer that clones the contents of a template model.
|
|
2311
2198
|
* @beta
|
|
2312
2199
|
*/
|