@itwin/imodel-transformer 1.0.0-dev.1 → 1.0.0-dev.11
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 +7 -45
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +5 -53
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +61 -112
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +290 -402
- 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,10 +274,9 @@ 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",
|
|
279
|
+
skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
|
|
280
280
|
};
|
|
281
281
|
this._isProvenanceInitTransform = this._options
|
|
282
282
|
.wasSourceIModelCopiedToTarget
|
|
@@ -361,8 +361,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
361
361
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._includeSourceProvenance=${this._options.includeSourceProvenance}`);
|
|
362
362
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._cloneUsingBinaryGeometry=${this._options.cloneUsingBinaryGeometry}`);
|
|
363
363
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._wasSourceIModelCopiedToTarget=${this._options.wasSourceIModelCopiedToTarget}`);
|
|
364
|
-
core_bentley_1.Logger.logInfo(loggerCategory,
|
|
365
|
-
|
|
364
|
+
core_bentley_1.Logger.logInfo(loggerCategory,
|
|
365
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
366
|
+
`this._isReverseSynchronization=${this._options.isReverseSynchronization}`);
|
|
367
|
+
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
366
368
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
367
369
|
}
|
|
368
370
|
/** Return the IModelDb where IModelTransformer will store its provenance.
|
|
@@ -455,29 +457,30 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
455
457
|
forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
|
|
456
458
|
});
|
|
457
459
|
}
|
|
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).
|
|
460
|
+
/**
|
|
461
|
+
* As of itwinjs 4.6.0, definitionContainers are now deleted as if they were DefinitionPartitions as opposed to Definitions.
|
|
462
|
+
* This variable being true will be used to special case the deletion of DefinitionContainers the same way DefinitionPartitions are deleted.
|
|
463
463
|
*/
|
|
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");
|
|
464
|
+
get hasDefinitionContainerDeletionFeature() {
|
|
465
|
+
if (this._hasDefinitionContainerDeletionFeature === undefined) {
|
|
466
|
+
this._hasDefinitionContainerDeletionFeature = Semver.satisfies(coreBackendPkgJson.version, "^4.6.0");
|
|
474
467
|
}
|
|
475
|
-
return this.
|
|
468
|
+
return this._hasDefinitionContainerDeletionFeature;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* We cache the synchronization version to avoid querying the target scoping ESA multiple times.
|
|
472
|
+
* If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
|
|
473
|
+
* Sets this._cachedSynchronizationVersion to undefined.
|
|
474
|
+
*/
|
|
475
|
+
clearCachedSynchronizationVersion() {
|
|
476
|
+
this._cachedSynchronizationVersion = undefined;
|
|
476
477
|
}
|
|
477
478
|
/** the changeset in the scoping element's source version found for this transformation
|
|
478
|
-
* @note
|
|
479
|
+
* @note the version depends on whether this is a reverse synchronization or not, as
|
|
479
480
|
* it is stored separately for both synchronization directions.
|
|
480
|
-
* @note
|
|
481
|
+
* @note empty string and -1 for changeset and index if it has never been transformed
|
|
482
|
+
* @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".
|
|
483
|
+
* @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
|
|
481
484
|
*/
|
|
482
485
|
get synchronizationVersion() {
|
|
483
486
|
if (this._cachedSynchronizationVersion === undefined) {
|
|
@@ -488,10 +491,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
488
491
|
const version = this.isReverseSynchronization
|
|
489
492
|
? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
|
|
490
493
|
: provenanceScopeAspect.version;
|
|
491
|
-
if (!version
|
|
494
|
+
if (!version &&
|
|
495
|
+
this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
|
|
492
496
|
return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
|
|
493
497
|
}
|
|
494
|
-
|
|
498
|
+
if (version === undefined) {
|
|
499
|
+
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.
|
|
500
|
+
Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
|
|
501
|
+
}
|
|
502
|
+
const [id, index] = version === "" ? ["", -1] : version.split(";");
|
|
495
503
|
if (Number.isNaN(Number(index)))
|
|
496
504
|
throw new Error("Could not parse version data from scope aspect");
|
|
497
505
|
this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
|
|
@@ -530,7 +538,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
530
538
|
id: this.targetScopeElementId,
|
|
531
539
|
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
532
540
|
},
|
|
533
|
-
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
541
|
+
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
534
542
|
identifier: this.provenanceSourceDb.iModelId,
|
|
535
543
|
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
536
544
|
jsonProperties: undefined,
|
|
@@ -567,29 +575,81 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
567
575
|
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
568
576
|
});
|
|
569
577
|
aspectProps.id = id;
|
|
578
|
+
// Busting a potential cached version
|
|
579
|
+
this.clearCachedSynchronizationVersion();
|
|
570
580
|
}
|
|
571
581
|
}
|
|
572
582
|
else {
|
|
573
583
|
// foundEsaProps is defined.
|
|
574
584
|
aspectProps.id = foundEsaProps.aspectId;
|
|
575
|
-
aspectProps.version =
|
|
576
|
-
foundEsaProps.version ??
|
|
577
|
-
(this._options.branchRelationshipDataBehavior === "unsafe-migrate"
|
|
578
|
-
? ""
|
|
579
|
-
: undefined);
|
|
585
|
+
aspectProps.version = foundEsaProps.version;
|
|
580
586
|
aspectProps.jsonProperties = foundEsaProps.jsonProperties
|
|
581
587
|
? JSON.parse(foundEsaProps.jsonProperties)
|
|
582
|
-
:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
588
|
+
: undefined;
|
|
589
|
+
// Clone oldProps incase they're changed for logging purposes
|
|
590
|
+
const oldProps = JSON.parse(JSON.stringify(aspectProps));
|
|
591
|
+
if (this.handleUnsafeMigrate(aspectProps)) {
|
|
592
|
+
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 });
|
|
593
|
+
this.provenanceDb.elements.updateAspect({
|
|
594
|
+
...aspectProps,
|
|
595
|
+
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
596
|
+
});
|
|
597
|
+
// Busting a potential cached version
|
|
598
|
+
this.clearCachedSynchronizationVersion();
|
|
599
|
+
}
|
|
589
600
|
}
|
|
590
601
|
this._targetScopeProvenanceProps =
|
|
591
602
|
aspectProps;
|
|
592
603
|
}
|
|
604
|
+
/** Returns true if a change was made to the aspectProps. */
|
|
605
|
+
handleUnsafeMigrate(aspectProps) {
|
|
606
|
+
let madeChange = false;
|
|
607
|
+
if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
|
|
608
|
+
return madeChange;
|
|
609
|
+
const fallbackSyncVersionToUse = this._options.unsafeFallbackSyncVersion ?? "";
|
|
610
|
+
const fallbackReverseSyncVersionToUse = this._options.unsafeFallbackReverseSyncVersion ?? "";
|
|
611
|
+
if (aspectProps.version === undefined ||
|
|
612
|
+
(aspectProps.version === "" &&
|
|
613
|
+
aspectProps.version !== fallbackSyncVersionToUse)) {
|
|
614
|
+
aspectProps.version = fallbackSyncVersionToUse;
|
|
615
|
+
madeChange = true;
|
|
616
|
+
}
|
|
617
|
+
if (aspectProps.jsonProperties === undefined) {
|
|
618
|
+
aspectProps.jsonProperties = {
|
|
619
|
+
pendingReverseSyncChangesetIndices: [],
|
|
620
|
+
pendingSyncChangesetIndices: [],
|
|
621
|
+
reverseSyncVersion: fallbackReverseSyncVersionToUse,
|
|
622
|
+
};
|
|
623
|
+
madeChange = true;
|
|
624
|
+
}
|
|
625
|
+
else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
|
|
626
|
+
(aspectProps.jsonProperties.reverseSyncVersion === "" &&
|
|
627
|
+
aspectProps.jsonProperties.reverseSyncVersion !==
|
|
628
|
+
fallbackReverseSyncVersionToUse)) {
|
|
629
|
+
aspectProps.jsonProperties.reverseSyncVersion =
|
|
630
|
+
fallbackReverseSyncVersionToUse;
|
|
631
|
+
madeChange = true;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* This case will only be hit when:
|
|
635
|
+
* - first transformation was performed on pre-fedguid transformer.
|
|
636
|
+
* - a second processAll transformation was performed on the same target-source iModels post-fedguid transformer.
|
|
637
|
+
* - change processing was invoked on for the second 'initial' transformation.
|
|
638
|
+
* NOTE: This case likely does not exist anymore, but we will keep it just to be sure.
|
|
639
|
+
*/
|
|
640
|
+
if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
|
|
641
|
+
undefined) {
|
|
642
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
|
|
643
|
+
aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
|
|
644
|
+
madeChange = true;
|
|
645
|
+
}
|
|
646
|
+
if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
|
|
647
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
|
|
648
|
+
aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
|
|
649
|
+
madeChange = true;
|
|
650
|
+
}
|
|
651
|
+
return madeChange;
|
|
652
|
+
}
|
|
593
653
|
/**
|
|
594
654
|
* Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
|
|
595
655
|
* and call a function for each one.
|
|
@@ -685,9 +745,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
685
745
|
targetScopeElementId: this.targetScopeElementId,
|
|
686
746
|
isReverseSynchronization: this.isReverseSynchronization,
|
|
687
747
|
fn,
|
|
688
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ??
|
|
748
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
|
|
689
749
|
});
|
|
690
750
|
}
|
|
751
|
+
/**
|
|
752
|
+
* Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
|
|
753
|
+
* The identifier on the ESA is the id of the element in the [[IModelTransformer.provenanceSourceDb]]
|
|
754
|
+
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
755
|
+
* @param entityInProvenanceSourceId
|
|
756
|
+
* @returns the elementId that the ESA is stored on, esa.Element.Id
|
|
757
|
+
*/
|
|
691
758
|
_queryProvenanceForElement(entityInProvenanceSourceId) {
|
|
692
759
|
return this.provenanceDb.withPreparedStatement(`
|
|
693
760
|
SELECT esa.Element.Id
|
|
@@ -705,6 +772,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
705
772
|
return undefined;
|
|
706
773
|
});
|
|
707
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
|
|
777
|
+
* The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
|
|
778
|
+
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
779
|
+
* @param entityInProvenanceSourceId
|
|
780
|
+
* @returns
|
|
781
|
+
*/
|
|
708
782
|
_queryProvenanceForRelationship(entityInProvenanceSourceId, sourceRelInfo) {
|
|
709
783
|
return this.provenanceDb.withPreparedStatement(`
|
|
710
784
|
SELECT
|
|
@@ -850,6 +924,20 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
850
924
|
onTransformElement(sourceElement) {
|
|
851
925
|
core_bentley_1.Logger.logTrace(loggerCategory, `onTransformElement(${sourceElement.id}) "${sourceElement.getDisplayLabel()}"`);
|
|
852
926
|
const targetElementProps = this.context.cloneElement(sourceElement, { binaryGeometry: this._options.cloneUsingBinaryGeometry });
|
|
927
|
+
// Special case: source element is the root subject
|
|
928
|
+
if (sourceElement.id === core_common_1.IModel.rootSubjectId) {
|
|
929
|
+
const targetElementId = this.context.findTargetElementId(sourceElement.id);
|
|
930
|
+
// When remapping rootSubject from source to non root subject in target, the code.scope gets remapped incorrectly.
|
|
931
|
+
// This is because the rootSubject has no parent and its code.scope is unique in that it is the id of itself.
|
|
932
|
+
// For all other subjects which do have parents the code.scope and its parent should be in agreement.
|
|
933
|
+
if (targetElementId !== core_bentley_1.Id64.invalid &&
|
|
934
|
+
targetElementId !== core_common_1.IModel.rootSubjectId) {
|
|
935
|
+
const targetElement = this.targetDb.elements.getElement(targetElementId);
|
|
936
|
+
targetElementProps.parent =
|
|
937
|
+
targetElement.parent ?? targetElementProps.parent;
|
|
938
|
+
targetElementProps.code.scope = targetElement.code.scope;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
853
941
|
if (sourceElement instanceof core_backend_1.Subject) {
|
|
854
942
|
if (targetElementProps.jsonProperties?.Subject?.Job) {
|
|
855
943
|
// don't propagate source channels into target (legacy bridge case)
|
|
@@ -860,10 +948,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
860
948
|
}
|
|
861
949
|
/** Returns true if a change within sourceElement is detected.
|
|
862
950
|
* @param sourceElement The Element from the source iModel
|
|
863
|
-
* @param targetElementId The Element from the target iModel to compare against.
|
|
864
951
|
* @note A subclass can override this method to provide custom change detection behavior.
|
|
865
952
|
*/
|
|
866
|
-
hasElementChanged(sourceElement
|
|
953
|
+
hasElementChanged(sourceElement) {
|
|
867
954
|
if (this._sourceChangeDataState === "no-changes")
|
|
868
955
|
return false;
|
|
869
956
|
if (this._sourceChangeDataState === "unconnected")
|
|
@@ -1098,8 +1185,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1098
1185
|
}
|
|
1099
1186
|
}
|
|
1100
1187
|
}
|
|
1101
|
-
if (
|
|
1102
|
-
!this.hasElementChanged(sourceElement, targetElementId))
|
|
1188
|
+
if (!this.hasElementChanged(sourceElement))
|
|
1103
1189
|
return;
|
|
1104
1190
|
this.collectUnmappedReferences(sourceElement);
|
|
1105
1191
|
// targetElementId will be valid (indicating update) or undefined (indicating insert)
|
|
@@ -1168,7 +1254,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1168
1254
|
const targetModeledElementId = this.context.findTargetElementId(sourceModel.id);
|
|
1169
1255
|
// there can only be one repositoryModel per database, so ignore the repo model on remapped subjects
|
|
1170
1256
|
const isRemappedRootSubject = sourceModel.id === core_common_1.IModel.repositoryModelId &&
|
|
1171
|
-
targetModeledElementId
|
|
1257
|
+
targetModeledElementId !== sourceModel.id;
|
|
1172
1258
|
if (isRemappedRootSubject)
|
|
1173
1259
|
return;
|
|
1174
1260
|
const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
|
|
@@ -1183,13 +1269,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1183
1269
|
const targetModelId = this.context.findTargetElementId(sourceModelId);
|
|
1184
1270
|
if (!core_bentley_1.Id64.isValidId64(targetModelId))
|
|
1185
1271
|
return;
|
|
1272
|
+
let sql;
|
|
1273
|
+
if (this.hasDefinitionContainerDeletionFeature) {
|
|
1274
|
+
sql = `
|
|
1275
|
+
SELECT 1
|
|
1276
|
+
FROM bis.DefinitionPartition
|
|
1277
|
+
WHERE ECInstanceId=:targetModelId
|
|
1278
|
+
UNION
|
|
1279
|
+
SELECT 1
|
|
1280
|
+
FROM bis.DefinitionContainer
|
|
1281
|
+
WHERE ECInstanceId=:targetModelId
|
|
1282
|
+
`;
|
|
1283
|
+
}
|
|
1284
|
+
else {
|
|
1285
|
+
sql = `
|
|
1286
|
+
SELECT 1
|
|
1287
|
+
FROM bis.DefinitionPartition
|
|
1288
|
+
WHERE ECInstanceId=:targetModelId
|
|
1289
|
+
`;
|
|
1290
|
+
}
|
|
1186
1291
|
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);
|
|
1292
|
+
const isDefinitionPartition = this.targetDb.withPreparedStatement(sql, (stmt) => {
|
|
1293
|
+
stmt.bindId("targetModelId", targetModelId);
|
|
1193
1294
|
const val = stmt.step();
|
|
1194
1295
|
switch (val) {
|
|
1195
1296
|
case core_bentley_1.DbResult.BE_SQLITE_ROW:
|
|
@@ -1197,7 +1298,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1197
1298
|
case core_bentley_1.DbResult.BE_SQLITE_DONE:
|
|
1198
1299
|
return false;
|
|
1199
1300
|
default:
|
|
1200
|
-
(0, core_bentley_1.assert)(false, `unexpected db result: '${stmt}'`);
|
|
1301
|
+
(0, core_bentley_1.assert)(false, `unexpected db result: '${JSON.stringify(stmt)}'`);
|
|
1201
1302
|
}
|
|
1202
1303
|
});
|
|
1203
1304
|
if (isDefinitionPartition) {
|
|
@@ -1303,7 +1404,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1303
1404
|
* @deprecated in 3.x. This method is no longer necessary since the transformer no longer needs to defer elements
|
|
1304
1405
|
*/
|
|
1305
1406
|
async processDeferredElements(_numRetries = 3) { }
|
|
1306
|
-
/** called at the end
|
|
1407
|
+
/** called at the end of a transformation,
|
|
1307
1408
|
* updates the target scope element to say that transformation up through the
|
|
1308
1409
|
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1309
1410
|
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
@@ -1344,21 +1445,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1344
1445
|
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
1345
1446
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1346
1447
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1347
|
-
const
|
|
1448
|
+
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1449
|
+
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1450
|
+
const [syncChangesetsToClearKey, syncChangesetsToUpdateKey] = this
|
|
1348
1451
|
.isReverseSynchronization
|
|
1349
1452
|
? [
|
|
1350
|
-
|
|
1351
|
-
|
|
1453
|
+
pendingReverseSyncChangesetIndicesKey,
|
|
1454
|
+
pendingSyncChangesetIndicesKey,
|
|
1352
1455
|
]
|
|
1353
1456
|
: [
|
|
1354
|
-
|
|
1355
|
-
|
|
1457
|
+
pendingSyncChangesetIndicesKey,
|
|
1458
|
+
pendingReverseSyncChangesetIndicesKey,
|
|
1356
1459
|
];
|
|
1460
|
+
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1461
|
+
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1462
|
+
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1463
|
+
// stuff to it which would break future synchronizations
|
|
1357
1464
|
for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1358
|
-
|
|
1359
|
-
//
|
|
1360
|
-
|
|
1361
|
-
|
|
1465
|
+
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1466
|
+
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
1467
|
+
jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
|
|
1468
|
+
return csIndex > this._startingChangesetIndices.source;
|
|
1469
|
+
});
|
|
1362
1470
|
// if reverse sync then we may have received provenance changes which should be marked as sync changes
|
|
1363
1471
|
if (this.isReverseSynchronization) {
|
|
1364
1472
|
nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
|
|
@@ -1372,9 +1480,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1372
1480
|
...this._targetScopeProvenanceProps,
|
|
1373
1481
|
jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
|
|
1374
1482
|
});
|
|
1483
|
+
this.clearCachedSynchronizationVersion();
|
|
1375
1484
|
}
|
|
1376
1485
|
// FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
|
|
1377
|
-
|
|
1486
|
+
finalizeTransformation() {
|
|
1378
1487
|
this.importer.finalize();
|
|
1379
1488
|
this.updateSynchronizationVersion();
|
|
1380
1489
|
if (this._partiallyCommittedEntities.size > 0) {
|
|
@@ -1403,31 +1512,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1403
1512
|
this.targetDb.codeValueBehavior = "trim-unicode-whitespace";
|
|
1404
1513
|
}
|
|
1405
1514
|
/* 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
1515
|
}
|
|
1432
1516
|
/** Imports all relationships that subclass from the specified base class.
|
|
1433
1517
|
* @param baseRelClassFullName The specified base relationship class.
|
|
@@ -1479,15 +1563,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1479
1563
|
core_bentley_1.Logger.logWarning(loggerCategory, "tried to delete a relationship that wasn't in change data");
|
|
1480
1564
|
return;
|
|
1481
1565
|
}
|
|
1482
|
-
const
|
|
1483
|
-
{
|
|
1566
|
+
const id = deletedRelData.relId ??
|
|
1567
|
+
this.targetDb.relationships.tryGetInstance(deletedRelData.classFullName, {
|
|
1484
1568
|
sourceId: deletedRelData.sourceIdInTarget,
|
|
1485
1569
|
targetId: deletedRelData.targetIdInTarget,
|
|
1486
|
-
};
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1570
|
+
})?.id;
|
|
1571
|
+
if (id) {
|
|
1572
|
+
this.importer.deleteRelationship({
|
|
1573
|
+
id,
|
|
1574
|
+
classFullName: deletedRelData.classFullName,
|
|
1575
|
+
});
|
|
1491
1576
|
}
|
|
1492
1577
|
if (deletedRelData.provenanceAspectId) {
|
|
1493
1578
|
try {
|
|
@@ -1531,8 +1616,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1531
1616
|
const json = JSON.parse(statement.getValue(2).getString());
|
|
1532
1617
|
const targetRelInstanceId = json.targetRelInstanceId ?? json.provenanceRelInstanceId;
|
|
1533
1618
|
if (targetRelInstanceId) {
|
|
1534
|
-
|
|
1535
|
-
|
|
1619
|
+
this.importer.deleteRelationship({
|
|
1620
|
+
id: targetRelInstanceId,
|
|
1621
|
+
classFullName: core_backend_1.ElementRefersToElements.classFullName,
|
|
1622
|
+
});
|
|
1536
1623
|
}
|
|
1537
1624
|
aspectDeleteIds.push(statement.getValue(0).getId());
|
|
1538
1625
|
}
|
|
@@ -1629,7 +1716,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1629
1716
|
let schemaFileName = schema.name + ext;
|
|
1630
1717
|
// many file systems have a max file-name/path-segment size of 255, so we workaround that on all systems
|
|
1631
1718
|
const systemMaxPathSegmentSize = 255;
|
|
1632
|
-
|
|
1719
|
+
// windows usually has a limit for the total path length of 260
|
|
1720
|
+
const windowsMaxPathLimit = 260;
|
|
1721
|
+
if (schemaFileName.length > systemMaxPathSegmentSize ||
|
|
1722
|
+
path.join(this._schemaExportDir, schemaFileName).length >=
|
|
1723
|
+
windowsMaxPathLimit) {
|
|
1633
1724
|
// this name should be well under 255 bytes
|
|
1634
1725
|
// ( 100 + (Number.MAX_SAFE_INTEGER.toString().length = 16) + (ext.length = 13) ) = 129 which is less than 255
|
|
1635
1726
|
// You'd have to be past 2**53-1 (Number.MAX_SAFE_INTEGER) long named schemas in order to hit decimal formatting,
|
|
@@ -1763,6 +1854,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1763
1854
|
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)")) {
|
|
1764
1855
|
relationshipECClassIds.add(row.ECInstanceId);
|
|
1765
1856
|
}
|
|
1857
|
+
const elementECClassIds = new Set();
|
|
1858
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.Element)")) {
|
|
1859
|
+
elementECClassIds.add(row.ECInstanceId);
|
|
1860
|
+
}
|
|
1766
1861
|
// For later use when processing deletes.
|
|
1767
1862
|
const alreadyImportedElementInserts = new Set();
|
|
1768
1863
|
const alreadyImportedModelInserts = new Set();
|
|
@@ -1801,7 +1896,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1801
1896
|
change.Scope.Id === this.targetScopeElementId) {
|
|
1802
1897
|
elemIdToScopeEsa.set(change.Element.Id, change);
|
|
1803
1898
|
}
|
|
1804
|
-
else if (changeType === "Inserted" || changeType === "Updated")
|
|
1899
|
+
else if ((changeType === "Inserted" || changeType === "Updated") &&
|
|
1900
|
+
change.ECClassId !== undefined &&
|
|
1901
|
+
elementECClassIds.has(change.ECClassId))
|
|
1805
1902
|
hasElementChangedCache.add(change.ECInstanceId);
|
|
1806
1903
|
}
|
|
1807
1904
|
// Loop to process deletes.
|
|
@@ -1815,7 +1912,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1815
1912
|
if (changeType !== "Deleted" ||
|
|
1816
1913
|
relationshipECClassIdsToSkip.has(ecClassId))
|
|
1817
1914
|
continue;
|
|
1818
|
-
this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1915
|
+
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1819
1916
|
}
|
|
1820
1917
|
csReader.close();
|
|
1821
1918
|
}
|
|
@@ -1832,116 +1929,98 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1832
1929
|
* @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
|
|
1833
1930
|
* @returns void
|
|
1834
1931
|
*/
|
|
1835
|
-
processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1932
|
+
async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1836
1933
|
// we need a connected iModel with changes to remap elements with deletions
|
|
1837
1934
|
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1838
|
-
const noChanges = this.
|
|
1935
|
+
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
1839
1936
|
if (notConnectedModel || noChanges)
|
|
1840
1937
|
return;
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
const
|
|
1846
|
-
|
|
1847
|
-
const sourceElemFedGuid = change.FederationGuid;
|
|
1938
|
+
/**
|
|
1939
|
+
* if our ChangedECInstance is in the provenanceDb, then we can use the ids we find in the ChangedECInstance to query for ESAs.
|
|
1940
|
+
* This is because the ESAs are stored on an element Id thats present in the provenanceDb.
|
|
1941
|
+
*/
|
|
1942
|
+
const changeDataInProvenanceDb = this.sourceDb === this.provenanceDb;
|
|
1943
|
+
const getTargetIdFromSourceId = async (id) => {
|
|
1848
1944
|
let identifierValue;
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1945
|
+
let element;
|
|
1946
|
+
if (isRelationship) {
|
|
1947
|
+
element = this.sourceDb.elements.tryGetElement(id);
|
|
1948
|
+
}
|
|
1949
|
+
const fedGuid = isRelationship
|
|
1950
|
+
? element?.federationGuid
|
|
1951
|
+
: change.FederationGuid;
|
|
1952
|
+
if (changeDataInProvenanceDb) {
|
|
1953
|
+
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
1954
|
+
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([
|
|
1955
|
+
this.targetScopeElementId,
|
|
1956
|
+
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1957
|
+
id,
|
|
1958
|
+
]))) {
|
|
1959
|
+
identifierValue = row.Identifier;
|
|
1856
1960
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1961
|
+
identifierValue =
|
|
1962
|
+
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.Identifier;
|
|
1859
1963
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1964
|
+
// Check for targetId by an esa first
|
|
1965
|
+
if (changeDataInProvenanceDb && identifierValue) {
|
|
1966
|
+
const targetId = identifierValue;
|
|
1967
|
+
return targetId;
|
|
1864
1968
|
}
|
|
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);
|
|
1969
|
+
// Check for targetId using sourceId's fedguid if we didn't find an esa.
|
|
1970
|
+
if (fedGuid) {
|
|
1971
|
+
const targetId = this._queryElemIdByFedGuid(this.targetDb, fedGuid);
|
|
1972
|
+
return targetId;
|
|
1885
1973
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1974
|
+
return undefined;
|
|
1975
|
+
};
|
|
1976
|
+
const changedInstanceId = change.ECInstanceId;
|
|
1977
|
+
if (isRelationship) {
|
|
1890
1978
|
const sourceIdOfRelationshipInSource = change.SourceECInstanceId;
|
|
1891
1979
|
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, {
|
|
1980
|
+
const classFullName = change.$meta?.classFullName;
|
|
1981
|
+
const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
|
|
1982
|
+
const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
|
|
1983
|
+
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
1984
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1925
1985
|
classFullName: classFullName ?? "",
|
|
1926
|
-
sourceIdInTarget,
|
|
1927
|
-
targetIdInTarget,
|
|
1986
|
+
sourceIdInTarget: sourceIdOfRelationshipInTarget,
|
|
1987
|
+
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
1928
1988
|
});
|
|
1929
1989
|
}
|
|
1930
|
-
else {
|
|
1931
|
-
|
|
1932
|
-
const relProvenance = this._queryProvenanceForRelationship(instId, {
|
|
1990
|
+
else if (this.sourceDb === this.provenanceSourceDb) {
|
|
1991
|
+
const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
|
|
1933
1992
|
classFullName: classFullName ?? "",
|
|
1934
1993
|
sourceId: sourceIdOfRelationshipInSource,
|
|
1935
1994
|
targetId: targetIdOfRelationshipInSource,
|
|
1936
1995
|
});
|
|
1937
1996
|
if (relProvenance && relProvenance.relationshipId)
|
|
1938
|
-
this._deletedSourceRelationshipData.set(
|
|
1997
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1939
1998
|
classFullName: classFullName ?? "",
|
|
1940
1999
|
relId: relProvenance.relationshipId,
|
|
1941
2000
|
provenanceAspectId: relProvenance.aspectId,
|
|
1942
2001
|
});
|
|
1943
2002
|
}
|
|
1944
2003
|
}
|
|
2004
|
+
else {
|
|
2005
|
+
let targetId = await getTargetIdFromSourceId(changedInstanceId);
|
|
2006
|
+
if (targetId === undefined && this.sourceDb === this.provenanceSourceDb) {
|
|
2007
|
+
targetId = this._queryProvenanceForElement(changedInstanceId);
|
|
2008
|
+
}
|
|
2009
|
+
// since we are processing one changeset at a time, we can see local source deletes
|
|
2010
|
+
// of entities that were never synced and can be safely ignored
|
|
2011
|
+
const deletionNotInTarget = !targetId;
|
|
2012
|
+
if (deletionNotInTarget)
|
|
2013
|
+
return;
|
|
2014
|
+
this.context.remapElement(changedInstanceId, targetId);
|
|
2015
|
+
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
2016
|
+
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
2017
|
+
if (alreadyImportedElementInserts.has(targetId)) {
|
|
2018
|
+
this.exporter.sourceDbChanges?.element.deleteIds.delete(changedInstanceId);
|
|
2019
|
+
}
|
|
2020
|
+
if (alreadyImportedModelInserts.has(targetId)) {
|
|
2021
|
+
this.exporter.sourceDbChanges?.model.deleteIds.delete(changedInstanceId);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1945
2024
|
}
|
|
1946
2025
|
async _tryInitChangesetData(args) {
|
|
1947
2026
|
if (!args ||
|
|
@@ -1950,7 +2029,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1950
2029
|
this._sourceChangeDataState = "unconnected";
|
|
1951
2030
|
return;
|
|
1952
2031
|
}
|
|
1953
|
-
const noChanges = this.
|
|
2032
|
+
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
1954
2033
|
if (noChanges) {
|
|
1955
2034
|
this._sourceChangeDataState = "no-changes";
|
|
1956
2035
|
this._csFileProps = [];
|
|
@@ -1960,7 +2039,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1960
2039
|
// to ignore those already processed changes
|
|
1961
2040
|
const startChangesetIndexOrId = args.startChangeset?.index ??
|
|
1962
2041
|
args.startChangeset?.id ??
|
|
1963
|
-
this.
|
|
2042
|
+
this.synchronizationVersion.index + 1;
|
|
1964
2043
|
const endChangesetId = this.sourceDb.changeset.id;
|
|
1965
2044
|
const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
|
|
1966
2045
|
? indexOrId
|
|
@@ -1972,17 +2051,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1972
2051
|
accessToken: args.accessToken,
|
|
1973
2052
|
})
|
|
1974
2053
|
.then((changeset) => changeset.index)));
|
|
1975
|
-
const missingChangesets = startChangesetIndex > this.
|
|
2054
|
+
const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
|
|
1976
2055
|
if (!this._options.ignoreMissingChangesetsInSynchronizations &&
|
|
1977
|
-
startChangesetIndex !== this.
|
|
1978
|
-
this.
|
|
2056
|
+
startChangesetIndex !== this.synchronizationVersion.index + 1 &&
|
|
2057
|
+
this.synchronizationVersion.index !== -1) {
|
|
1979
2058
|
throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
|
|
1980
2059
|
" startChangesetId should be" +
|
|
1981
2060
|
" exactly the first changeset *after* the previous synchronization to not miss data." +
|
|
1982
2061
|
` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
|
|
1983
|
-
` but the previous synchronization for this targetScopeElement was '${this.
|
|
1984
|
-
` which is changeset #${this.
|
|
1985
|
-
` #${this.
|
|
2062
|
+
` but the previous synchronization for this targetScopeElement was '${this.synchronizationVersion.id}'` +
|
|
2063
|
+
` which is changeset #${this.synchronizationVersion.index}. The transformer expected` +
|
|
2064
|
+
` #${this.synchronizationVersion.index + 1}.`);
|
|
1986
2065
|
}
|
|
1987
2066
|
nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps should be set by now");
|
|
1988
2067
|
const changesetsToSkip = this.isReverseSynchronization
|
|
@@ -2004,19 +2083,20 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2004
2083
|
csFileProps.push(...fileProps);
|
|
2005
2084
|
}
|
|
2006
2085
|
this._csFileProps = csFileProps;
|
|
2007
|
-
|
|
2086
|
+
/** Theres a possibility that our csFileProps length is still 0 here, since we skip cs indices found in the pendingSync and pendingReverseSync indices arrays. */
|
|
2087
|
+
this._sourceChangeDataState =
|
|
2088
|
+
this._csFileProps.length === 0 ? "no-changes" : "has-changes";
|
|
2008
2089
|
}
|
|
2009
2090
|
/** Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2010
2091
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2011
2092
|
*/
|
|
2012
|
-
async processAll(
|
|
2093
|
+
async processAll() {
|
|
2013
2094
|
this.logSettings();
|
|
2014
2095
|
this.initScopeProvenance();
|
|
2015
2096
|
await this.initialize();
|
|
2016
2097
|
await this.exporter.exportCodeSpecs();
|
|
2017
2098
|
await this.exporter.exportFonts();
|
|
2018
2099
|
if (this._options.skipPropagateChangesToRootElements) {
|
|
2019
|
-
// FIXME<NICK>: This option in exportAll was a maybe.
|
|
2020
2100
|
// The RepositoryModel and root Subject of the target iModel should not be transformed.
|
|
2021
2101
|
await this.exporter.exportChildElements(core_common_1.IModel.rootSubjectId); // start below the root Subject
|
|
2022
2102
|
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 +2110,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2030
2110
|
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
2031
2111
|
if (this._options.forceExternalSourceAspectProvenance &&
|
|
2032
2112
|
this.shouldDetectDeletes()) {
|
|
2113
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2033
2114
|
await this.detectElementDeletes();
|
|
2115
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2034
2116
|
await this.detectRelationshipDeletes();
|
|
2035
2117
|
}
|
|
2036
2118
|
if (this._options.optimizeGeometry)
|
|
2037
2119
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2038
2120
|
this.importer.computeProjectExtents();
|
|
2039
|
-
|
|
2121
|
+
this.finalizeTransformation();
|
|
2040
2122
|
}
|
|
2041
2123
|
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
2042
2124
|
this._lastProvenanceEntityInfo =
|
|
@@ -2051,205 +2133,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2051
2133
|
: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
2052
2134
|
};
|
|
2053
2135
|
}
|
|
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
2136
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2251
2137
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2252
|
-
* @note the transformer
|
|
2138
|
+
* @note the transformer assumes that you saveChanges after processing changes. You should not
|
|
2139
|
+
* modify the iModel after processChanges until saveChanges, failure to do so may result in corrupted
|
|
2140
|
+
* data loss in future branch operations
|
|
2253
2141
|
* @note if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2254
2142
|
* will automatically be determined and used
|
|
2255
2143
|
* @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 +2153,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2265
2153
|
if (this._options.optimizeGeometry)
|
|
2266
2154
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2267
2155
|
this.importer.computeProjectExtents();
|
|
2268
|
-
|
|
2156
|
+
this.finalizeTransformation();
|
|
2157
|
+
const defaultSaveTargetChanges = () => {
|
|
2158
|
+
this.targetDb.saveChanges();
|
|
2159
|
+
};
|
|
2160
|
+
await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
2269
2161
|
}
|
|
2270
2162
|
/** Changeset data must be initialized in order to build correct changeOptions.
|
|
2271
2163
|
* Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
|
|
@@ -2274,7 +2166,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2274
2166
|
if (!this._isSynchronization)
|
|
2275
2167
|
return {};
|
|
2276
2168
|
return {
|
|
2277
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements
|
|
2169
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
2278
2170
|
accessToken: opts.accessToken,
|
|
2279
2171
|
...(this._csFileProps
|
|
2280
2172
|
? { csFileProps: this._csFileProps }
|
|
@@ -2284,7 +2176,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2284
2176
|
? { startChangeset: opts.startChangeset }
|
|
2285
2177
|
: {
|
|
2286
2178
|
startChangeset: {
|
|
2287
|
-
index: this.
|
|
2179
|
+
index: this.synchronizationVersion.index + 1,
|
|
2288
2180
|
},
|
|
2289
2181
|
}),
|
|
2290
2182
|
};
|
|
@@ -2303,10 +2195,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2303
2195
|
}
|
|
2304
2196
|
exports.IModelTransformer = IModelTransformer;
|
|
2305
2197
|
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
2198
|
/** IModelTransformer that clones the contents of a template model.
|
|
2311
2199
|
* @beta
|
|
2312
2200
|
*/
|