@itwin/imodel-transformer 1.0.1-dev.0 → 1.1.0
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 +49 -1
- package/README.md +17 -0
- package/lib/cjs/Algo.d.ts +7 -0
- package/lib/cjs/Algo.d.ts.map +1 -1
- package/lib/cjs/Algo.js +7 -0
- package/lib/cjs/Algo.js.map +1 -1
- package/lib/cjs/BigMap.d.ts +6 -1
- package/lib/cjs/BigMap.d.ts.map +1 -1
- package/lib/cjs/BigMap.js +28 -2
- package/lib/cjs/BigMap.js.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +2 -0
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
- package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
- package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
- package/lib/cjs/EntityUnifier.d.ts.map +1 -1
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts +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 +67 -62
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +173 -83
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +31 -47
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +62 -72
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +146 -210
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +515 -657
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/{transformer.d.ts → imodel-transformer.d.ts} +1 -1
- package/lib/cjs/imodel-transformer.d.ts.map +1 -0
- package/lib/cjs/{transformer.js → imodel-transformer.js} +13 -3
- package/lib/cjs/imodel-transformer.js.map +1 -0
- package/package.json +22 -20
- package/lib/cjs/PendingReferenceMap.d.ts +0 -37
- package/lib/cjs/PendingReferenceMap.d.ts.map +0 -1
- package/lib/cjs/PendingReferenceMap.js +0 -92
- package/lib/cjs/PendingReferenceMap.js.map +0 -1
- package/lib/cjs/transformer.d.ts.map +0 -1
- package/lib/cjs/transformer.js.map +0 -1
|
@@ -55,7 +55,9 @@ class IModelExportHandler {
|
|
|
55
55
|
shouldExportElement(_element) {
|
|
56
56
|
return true;
|
|
57
57
|
}
|
|
58
|
-
/** Called when element is skipped instead of exported.
|
|
58
|
+
/** Called when element is skipped instead of exported.
|
|
59
|
+
* @note When an element is skipped, exporter will not export any of its child elements. Because of this, [[onSkipElement]] will not be invoked for any children of a "skipped" element.
|
|
60
|
+
*/
|
|
59
61
|
onSkipElement(_elementId) { }
|
|
60
62
|
/** Called when an element should be exported.
|
|
61
63
|
* @param element The element to export
|
|
@@ -130,7 +132,7 @@ exports.IModelExportHandler = IModelExportHandler;
|
|
|
130
132
|
class IModelExporter {
|
|
131
133
|
/**
|
|
132
134
|
* Retrieve the cached entity change information.
|
|
133
|
-
* @note This will only be initialized after [IModelExporter.exportChanges] is invoked.
|
|
135
|
+
* @note This will only be initialized after [IModelExporter.exportChanges] is invoked or [IModelExporter.initialize] is called.
|
|
134
136
|
*/
|
|
135
137
|
get sourceDbChanges() {
|
|
136
138
|
return this._sourceDbChanges;
|
|
@@ -252,19 +254,24 @@ class IModelExporter {
|
|
|
252
254
|
await this.exportModel(core_common_1.IModel.repositoryModelId);
|
|
253
255
|
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
254
256
|
}
|
|
255
|
-
|
|
257
|
+
/** Export changes from the source iModel.
|
|
258
|
+
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
259
|
+
* @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired
|
|
260
|
+
* range and open the source iModel as of the end (inclusive) of the desired range.
|
|
261
|
+
* @note the changedInstanceIds are just for this call to exportChanges, so you must continue to pass it in
|
|
262
|
+
* for consecutive calls
|
|
263
|
+
*/
|
|
264
|
+
async exportChanges(args) {
|
|
256
265
|
if (!this.sourceDb.isBriefcaseDb())
|
|
257
266
|
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Must be a briefcase to export changes");
|
|
258
267
|
if ("" === this.sourceDb.changeset.id) {
|
|
259
268
|
await this.exportAll(); // no changesets, so revert to exportAll
|
|
260
269
|
return;
|
|
261
270
|
}
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
: {
|
|
265
|
-
|
|
266
|
-
startChangeset: { id: startChangesetId },
|
|
267
|
-
};
|
|
271
|
+
const startChangeset = args && "startChangeset" in args ? args.startChangeset : undefined;
|
|
272
|
+
const initOpts = {
|
|
273
|
+
startChangeset: { id: startChangeset?.id },
|
|
274
|
+
};
|
|
268
275
|
await this.initialize(initOpts);
|
|
269
276
|
// _sourceDbChanges are initialized in this.initialize
|
|
270
277
|
nodeAssert(this._sourceDbChanges !== undefined, "sourceDbChanges must be initialized.");
|
|
@@ -592,6 +599,12 @@ class IModelExporter {
|
|
|
592
599
|
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportElement(${elementId})`);
|
|
593
600
|
return;
|
|
594
601
|
}
|
|
602
|
+
// Return early if the elementId is already in the excludedElementIds, that way we don't need to load the element from the db.
|
|
603
|
+
if (this._excludedElementIds.has(elementId)) {
|
|
604
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementId} by Id`);
|
|
605
|
+
this.handler.onSkipElement(elementId);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
595
608
|
// are we processing changes?
|
|
596
609
|
const isUpdate = this._sourceDbChanges?.element.insertIds.has(elementId)
|
|
597
610
|
? false
|
|
@@ -702,70 +715,10 @@ class IModelExporter {
|
|
|
702
715
|
return this.handler.onProgress();
|
|
703
716
|
}
|
|
704
717
|
}
|
|
705
|
-
/**
|
|
706
|
-
* You may override this to store arbitrary json state in a exporter state dump, useful for some resumptions
|
|
707
|
-
* @see [[IModelTransformer.saveStateToFile]]
|
|
708
|
-
*/
|
|
709
|
-
getAdditionalStateJson() {
|
|
710
|
-
return {};
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* You may override this to load arbitrary json state in a transformer state dump, useful for some resumptions
|
|
714
|
-
* @see [[IModelTransformer.loadStateFromFile]]
|
|
715
|
-
*/
|
|
716
|
-
loadAdditionalStateJson(_additionalState) { }
|
|
717
|
-
/**
|
|
718
|
-
* Reload our state from a JSON object
|
|
719
|
-
* Intended for [[IModelTransformer.resumeTransformation]]
|
|
720
|
-
* @internal
|
|
721
|
-
* You can load custom json from the exporter save state for custom exporters by overriding [[IModelExporter.loadAdditionalStateJson]]
|
|
722
|
-
*/
|
|
723
|
-
loadStateFromJson(state) {
|
|
724
|
-
if (state.exporterClass !== this.constructor.name)
|
|
725
|
-
throw Error("resuming from a differently named exporter class, it is not necessarily valid to resume with a different exporter class");
|
|
726
|
-
this.wantGeometry = state.wantGeometry;
|
|
727
|
-
this.wantTemplateModels = state.wantTemplateModels;
|
|
728
|
-
this.wantSystemSchemas = state.wantSystemSchemas;
|
|
729
|
-
this.visitElements = state.visitElements;
|
|
730
|
-
this.visitRelationships = state.visitRelationships;
|
|
731
|
-
this._excludedCodeSpecNames = new Set(state.excludedCodeSpecNames);
|
|
732
|
-
(this._excludedElementIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementIds)),
|
|
733
|
-
(this._excludedElementCategoryIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementCategoryIds)),
|
|
734
|
-
(this._excludedElementClasses = new Set(state.excludedElementClassNames.map((c) => this.sourceDb.getJsClass(c))));
|
|
735
|
-
this._exportElementAspectsStrategy.loadExcludedElementAspectClasses(state.excludedElementAspectClassFullNames);
|
|
736
|
-
this._excludedRelationshipClasses = new Set(state.excludedRelationshipClassNames.map((c) => this.sourceDb.getJsClass(c)));
|
|
737
|
-
this.loadAdditionalStateJson(state.additionalState);
|
|
738
|
-
}
|
|
739
|
-
/**
|
|
740
|
-
* Serialize state to a JSON object
|
|
741
|
-
* Intended for [[IModelTransformer.resumeTransformation]]
|
|
742
|
-
* @internal
|
|
743
|
-
* You can add custom json to the exporter save state for custom exporters by overriding [[IModelExporter.getAdditionalStateJson]]
|
|
744
|
-
*/
|
|
745
|
-
saveStateToJson() {
|
|
746
|
-
return {
|
|
747
|
-
exporterClass: this.constructor.name,
|
|
748
|
-
wantGeometry: this.wantGeometry,
|
|
749
|
-
wantTemplateModels: this.wantTemplateModels,
|
|
750
|
-
wantSystemSchemas: this.wantSystemSchemas,
|
|
751
|
-
visitElements: this.visitElements,
|
|
752
|
-
visitRelationships: this.visitRelationships,
|
|
753
|
-
excludedCodeSpecNames: [...this._excludedCodeSpecNames],
|
|
754
|
-
excludedElementIds: core_bentley_1.CompressedId64Set.compressSet(this._excludedElementIds),
|
|
755
|
-
excludedElementCategoryIds: core_bentley_1.CompressedId64Set.compressSet(this._excludedElementCategoryIds),
|
|
756
|
-
excludedElementClassNames: Array.from(this._excludedElementClasses, (cls) => cls.classFullName),
|
|
757
|
-
excludedElementAspectClassFullNames: [
|
|
758
|
-
...this._exportElementAspectsStrategy
|
|
759
|
-
.excludedElementAspectClassFullNames,
|
|
760
|
-
],
|
|
761
|
-
excludedRelationshipClassNames: Array.from(this._excludedRelationshipClasses, (cls) => cls.classFullName),
|
|
762
|
-
additionalState: this.getAdditionalStateJson(),
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
718
|
}
|
|
766
719
|
exports.IModelExporter = IModelExporter;
|
|
767
720
|
/** Class for holding change information.
|
|
768
|
-
* @
|
|
721
|
+
* @public
|
|
769
722
|
*/
|
|
770
723
|
class ChangedInstanceOps {
|
|
771
724
|
constructor() {
|
|
@@ -784,11 +737,20 @@ class ChangedInstanceOps {
|
|
|
784
737
|
val.delete.forEach((id) => this.deleteIds.add(id));
|
|
785
738
|
}
|
|
786
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* Checks if empty.
|
|
742
|
+
* @returns true if there no ids in the ChangedInstanceOps object.
|
|
743
|
+
*/
|
|
744
|
+
get isEmpty() {
|
|
745
|
+
return (0 === this.insertIds.size &&
|
|
746
|
+
0 === this.updateIds.size &&
|
|
747
|
+
0 === this.deleteIds.size);
|
|
748
|
+
}
|
|
787
749
|
}
|
|
788
750
|
exports.ChangedInstanceOps = ChangedInstanceOps;
|
|
789
751
|
/**
|
|
790
752
|
* Class for discovering modified elements between 2 versions of an iModel.
|
|
791
|
-
* @
|
|
753
|
+
* @public
|
|
792
754
|
*/
|
|
793
755
|
class ChangedInstanceIds {
|
|
794
756
|
constructor(db) {
|
|
@@ -806,6 +768,7 @@ class ChangedInstanceIds {
|
|
|
806
768
|
this._elementSubclassIds = new Set();
|
|
807
769
|
this._aspectSubclassIds = new Set();
|
|
808
770
|
this._relationshipSubclassIds = new Set();
|
|
771
|
+
this._relationshipSubclassIdsToSkip = new Set();
|
|
809
772
|
const addECClassIdsToSet = async (setToModify, baseClass) => {
|
|
810
773
|
for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})`)) {
|
|
811
774
|
setToModify.add(row.ECInstanceId);
|
|
@@ -818,6 +781,7 @@ class ChangedInstanceIds {
|
|
|
818
781
|
addECClassIdsToSet(this._aspectSubclassIds, "BisCore.ElementUniqueAspect"),
|
|
819
782
|
addECClassIdsToSet(this._aspectSubclassIds, "BisCore.ElementMultiAspect"),
|
|
820
783
|
addECClassIdsToSet(this._relationshipSubclassIds, "BisCore.ElementRefersToElements"),
|
|
784
|
+
addECClassIdsToSet(this._relationshipSubclassIdsToSkip, "BisCore.ElementDrivesElement"),
|
|
821
785
|
];
|
|
822
786
|
await Promise.all(promises);
|
|
823
787
|
}
|
|
@@ -826,7 +790,8 @@ class ChangedInstanceIds {
|
|
|
826
790
|
this._modelSubclassIds &&
|
|
827
791
|
this._elementSubclassIds &&
|
|
828
792
|
this._aspectSubclassIds &&
|
|
829
|
-
this._relationshipSubclassIds
|
|
793
|
+
this._relationshipSubclassIds &&
|
|
794
|
+
this._relationshipSubclassIdsToSkip);
|
|
830
795
|
}
|
|
831
796
|
isRelationship(ecClassId) {
|
|
832
797
|
return this._relationshipSubclassIds?.has(ecClassId);
|
|
@@ -843,6 +808,17 @@ class ChangedInstanceIds {
|
|
|
843
808
|
isElement(ecClassId) {
|
|
844
809
|
return this._elementSubclassIds?.has(ecClassId);
|
|
845
810
|
}
|
|
811
|
+
/** Checks if there are any changes.
|
|
812
|
+
* @returns true if there are any changes in the ChangedInstanceIds object.
|
|
813
|
+
*/
|
|
814
|
+
get hasChanges() {
|
|
815
|
+
return (!this.codeSpec.isEmpty ||
|
|
816
|
+
!this.model.isEmpty ||
|
|
817
|
+
!this.element.isEmpty ||
|
|
818
|
+
!this.aspect.isEmpty ||
|
|
819
|
+
!this.relationship.isEmpty ||
|
|
820
|
+
!this.font.isEmpty);
|
|
821
|
+
}
|
|
846
822
|
/**
|
|
847
823
|
* Adds the provided [[ChangedECInstance]] to the appropriate set of changes by class type (codeSpec, model, element, aspect, or relationship) maintained by this instance of ChangedInstanceIds.
|
|
848
824
|
* If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
|
|
@@ -858,6 +834,8 @@ class ChangedInstanceIds {
|
|
|
858
834
|
const changeType = change.$meta?.op;
|
|
859
835
|
if (changeType === undefined)
|
|
860
836
|
throw new Error(`ChangeType was undefined for id: ${change.ECInstanceId}.`);
|
|
837
|
+
if (this._relationshipSubclassIdsToSkip?.has(ecClassId))
|
|
838
|
+
return;
|
|
861
839
|
if (this.isRelationship(ecClassId))
|
|
862
840
|
this.handleChange(this.relationship, changeType, change.ECInstanceId);
|
|
863
841
|
else if (this.isCodeSpec(ecClassId))
|
|
@@ -869,6 +847,128 @@ class ChangedInstanceIds {
|
|
|
869
847
|
else if (this.isElement(ecClassId))
|
|
870
848
|
this.handleChange(this.element, changeType, change.ECInstanceId);
|
|
871
849
|
}
|
|
850
|
+
/**
|
|
851
|
+
* This method should only be called inside [[IModelTransformer.addCustomChanges]].
|
|
852
|
+
* It adds the provided change to the element changes maintained by this instance of ChangedInstanceIds.
|
|
853
|
+
* If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
|
|
854
|
+
* from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
|
|
855
|
+
* @note Custom element 'Insert' and 'Update' will mark element's parent model hierarchy and their modeled elements as 'Updated' in [[ChangedInstanceIds.model]] and [[ChangedInstanceIds.element]]. Parent models have to be marked as 'Updated' to make sure that added change is not skipped by transformer. Transformer starts processing elements from RepositoryModel and then visits all child models. Modeled elements hierarchy is marked as updated to trigger their inserts in case a new model (or its parent) needs to be inserted.
|
|
856
|
+
* @note Custom element 'Insert' will also mark element aspects and all element relationships as inserted.
|
|
857
|
+
* @note It is the responsibility of the caller to ensure that the provided id is, in fact an element.
|
|
858
|
+
* @note In most cases, this method does not need to be called. Its only for consumers to mimic changes as if they were found in a changeset, which should only be useful in certain cases such as the changing of filter criteria for a preexisting master branch relationship.
|
|
859
|
+
* @note In data processing with filter criteria scenarios it is important to consistently filter out models and their modeled elements that were previously removed from target via [[addCustomModelChange]] or [[shouldExportElement]] apis.
|
|
860
|
+
* @beta
|
|
861
|
+
*/
|
|
862
|
+
async addCustomElementChange(changeType, ids) {
|
|
863
|
+
if (core_bentley_1.Id64.sizeOf(ids) === 0) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
for (const id of core_bentley_1.Id64.iterable(ids)) {
|
|
867
|
+
this.handleChange(this.element, changeType, id);
|
|
868
|
+
}
|
|
869
|
+
if (changeType === "Deleted") {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const idsSet = core_bentley_1.Id64.toIdSet(ids);
|
|
873
|
+
// Parent models have to be marked as 'Updated' to make sure that added change is not skipped by transformer. Transformer starts processing elements from RepositoryModel and then visits all child models.
|
|
874
|
+
// Transformer handles update as insert if element is not found in target, for this reason modeled elements will be also marked as updated to trigger their inserts in case a new model (or its parent) needs to be inserted. Otherwise error would be thrown about missing modeled element while inserting new model.
|
|
875
|
+
const parentModelIds = await this.markParentModelsAsUpdated(idsSet);
|
|
876
|
+
// Aspects and relationships of inserted data needs to be marked as inserted otherwise those would not be exported
|
|
877
|
+
if (changeType === "Inserted") {
|
|
878
|
+
// Adding parents as well as we are not sure if those were inserted or updated
|
|
879
|
+
parentModelIds.forEach((parentId) => {
|
|
880
|
+
idsSet.add(parentId);
|
|
881
|
+
});
|
|
882
|
+
await this.markElementAspectsAsInserted(idsSet);
|
|
883
|
+
// Marking only ElementRefersToElements.classFullName as only those are exported in exportRelationships()
|
|
884
|
+
await this.markElementRelationshipsAsInserted(core_backend_1.ElementRefersToElements.classFullName, idsSet);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* This method should only be called inside [IModelTransformer.addCustomChanges].
|
|
889
|
+
* Adds the provided change to the model changes maintained by this instance of ChangedInstanceIds.
|
|
890
|
+
* If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
|
|
891
|
+
* from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
|
|
892
|
+
* Will add same change to the model's modeledElement by calling [[ChangedInstanceIds.addCustomElementChange]] which will register more needed changes. This is to ensure the changes from the model and its modeledElement get exported together.
|
|
893
|
+
* @note It is the responsibility of the caller to ensure that the provided id is, in fact a model.
|
|
894
|
+
* @note In most cases, this method does not need to be called. Its only for consumers to mimic changes as if they were found in a changeset, which should only be useful in certain cases such as the changing of filter criteria for a preexisting master branch relationship.
|
|
895
|
+
* @note In data processing with filter criteria scenarios it is important to consistently filter out models and their modeled elements that were previously removed from target via [[addCustomModelChange]] or [[shouldExportElement]] apis.
|
|
896
|
+
* @beta
|
|
897
|
+
*/
|
|
898
|
+
async addCustomModelChange(changeType, ids) {
|
|
899
|
+
// Also add the model's modeledElement to the element changes. The modeledElement and model go hand in hand and have the same id.
|
|
900
|
+
await this.addCustomElementChange(changeType, ids);
|
|
901
|
+
for (const id of core_bentley_1.Id64.iterable(ids)) {
|
|
902
|
+
this.handleChange(this.model, changeType, id);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* This method should only be called inside [IModelTransformer.addCustomChanges].
|
|
907
|
+
* Adds the provided change to the aspect changes maintained by this instance of ChangedInstanceIds
|
|
908
|
+
* If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
|
|
909
|
+
* from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
|
|
910
|
+
* @note It is the responsibility of the caller to ensure that the provided id is, in fact an aspect.
|
|
911
|
+
* @note In most cases, this method does not need to be called. Its only for consumers to mimic changes as if they were found in a changeset, which should only be useful in certain cases such as the changing of filter criteria for a preexisting master branch relationship.
|
|
912
|
+
* @beta
|
|
913
|
+
*/
|
|
914
|
+
addCustomAspectChange(changeType, ids) {
|
|
915
|
+
for (const id of core_bentley_1.Id64.iterable(ids)) {
|
|
916
|
+
this.handleChange(this.aspect, changeType, id);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* There is an optimization in [IModelExporter.exportModelContents] which doesn't try to export elements within a model unless the model itself is marked as `Updated` or 'Inserted' in sourceDbChanges. This method is used in [[addCustomElementChange]] and [[addCustomModelChange]] to add the parent model hierarchy to the 'updatedIds' so that the custom element changes are exported.
|
|
921
|
+
* Transformer will insert 'Updated' model to target if it does not exist there already. To handle such case, modeled elements of parent models are also marked as updated. This is done, because model can not be inserted without it's modeled element.
|
|
922
|
+
*/
|
|
923
|
+
async markParentModelsAsUpdated(elementIds) {
|
|
924
|
+
const params = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
925
|
+
const ecQuery = `
|
|
926
|
+
WITH RECURSIVE hierarchy (parentId) AS (
|
|
927
|
+
SELECT Model.Id FROM bis.Element WHERE InVirtualSet(:elementIds, ECInstanceId)
|
|
928
|
+
UNION
|
|
929
|
+
SELECT ParentModel.id
|
|
930
|
+
FROM bis.Model e
|
|
931
|
+
INNER JOIN hierarchy h ON h.parentId = e.ECInstanceId
|
|
932
|
+
)
|
|
933
|
+
SELECT parentId FROM hierarchy where parentId is not null
|
|
934
|
+
`;
|
|
935
|
+
const parentModelIds = new Set();
|
|
936
|
+
for await (const row of this._db.createQueryReader(ecQuery, params)) {
|
|
937
|
+
// Transformer handles update as insert when element does not exist in target.
|
|
938
|
+
// Which means that in scenario where child and parent model are filtered out from target,
|
|
939
|
+
// and child element is inserted trough custom change, its parent model will be marked as updated.
|
|
940
|
+
// Transformer then will:
|
|
941
|
+
// 1. Handle parent update as insert (since it does not exist in target).
|
|
942
|
+
// 2. Will insert child element (otherwise this insert would be ignored due to missing parent).
|
|
943
|
+
this.handleChange(this.model, "Updated", row.parentId);
|
|
944
|
+
this.handleChange(this.element, "Updated", row.parentId);
|
|
945
|
+
parentModelIds.add(row.parentId);
|
|
946
|
+
}
|
|
947
|
+
return parentModelIds;
|
|
948
|
+
}
|
|
949
|
+
async markElementRelationshipsAsInserted(relationshipClassName, elementIds) {
|
|
950
|
+
const ecQuery = `SELECT ECInstanceId FROM ${relationshipClassName}
|
|
951
|
+
WHERE InVirtualSet(:elementIds, TargetECInstanceId)
|
|
952
|
+
OR InVirtualSet(:elementIds, SourceECInstanceId)`;
|
|
953
|
+
const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
954
|
+
const queryReader = this._db.createQueryReader(ecQuery, queryBinder);
|
|
955
|
+
for await (const row of queryReader) {
|
|
956
|
+
this.handleChange(this.relationship, "Inserted", row.ECInstanceId);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
async markElementAspectsAsInserted(elementIds) {
|
|
960
|
+
for (const aspectClassName of [
|
|
961
|
+
core_backend_1.ElementUniqueAspect.classFullName,
|
|
962
|
+
core_backend_1.ElementMultiAspect.classFullName,
|
|
963
|
+
]) {
|
|
964
|
+
const ecQuery = `Select ECInstanceId from ${aspectClassName} where InVirtualSet(:elementIds, Element.Id)`;
|
|
965
|
+
const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
966
|
+
const queryReader = this._db.createQueryReader(ecQuery, queryBinder);
|
|
967
|
+
for await (const row of queryReader) {
|
|
968
|
+
this.addCustomAspectChange("Inserted", row.toArray()[0]);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
872
972
|
handleChange(changedInstanceOps, changeType, id) {
|
|
873
973
|
// if changeType is a delete and we already have the id in the inserts then we can remove the id from the inserts.
|
|
874
974
|
// if changeType is a delete and we already have the id in the updates then we can remove the id from the updates AND add it to the deletes.
|
|
@@ -893,12 +993,12 @@ class ChangedInstanceIds {
|
|
|
893
993
|
}
|
|
894
994
|
/**
|
|
895
995
|
* Initializes a new ChangedInstanceIds object with information taken from a range of changesets.
|
|
996
|
+
* @public
|
|
896
997
|
*/
|
|
897
998
|
static async initialize(opts) {
|
|
898
999
|
if ("changedInstanceIds" in opts)
|
|
899
1000
|
return opts.changedInstanceIds;
|
|
900
1001
|
const iModelId = opts.iModel.iModelId;
|
|
901
|
-
const accessToken = opts.accessToken;
|
|
902
1002
|
const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
|
|
903
1003
|
const changesetRanges = startChangeset !== undefined
|
|
904
1004
|
? [
|
|
@@ -909,13 +1009,11 @@ class ChangedInstanceIds {
|
|
|
909
1009
|
changeset: {
|
|
910
1010
|
id: startChangeset.id ?? opts.iModel.changeset.id,
|
|
911
1011
|
},
|
|
912
|
-
accessToken,
|
|
913
1012
|
})).index,
|
|
914
1013
|
opts.iModel.changeset.index ??
|
|
915
1014
|
(await core_backend_1.IModelHost.hubAccess.queryChangeset({
|
|
916
1015
|
iModelId,
|
|
917
1016
|
changeset: { id: opts.iModel.changeset.id },
|
|
918
|
-
accessToken,
|
|
919
1017
|
})).index,
|
|
920
1018
|
],
|
|
921
1019
|
]
|
|
@@ -924,7 +1022,6 @@ class ChangedInstanceIds {
|
|
|
924
1022
|
: undefined;
|
|
925
1023
|
const csFileProps = changesetRanges !== undefined
|
|
926
1024
|
? (await Promise.all(changesetRanges.map(async ([first, end]) => core_backend_1.IModelHost.hubAccess.downloadChangesets({
|
|
927
|
-
accessToken,
|
|
928
1025
|
iModelId,
|
|
929
1026
|
range: { first, end },
|
|
930
1027
|
targetDir: core_backend_1.BriefcaseManager.getChangeSetsPath(iModelId),
|
|
@@ -935,10 +1032,6 @@ class ChangedInstanceIds {
|
|
|
935
1032
|
if (csFileProps === undefined)
|
|
936
1033
|
return undefined;
|
|
937
1034
|
const changedInstanceIds = new ChangedInstanceIds(opts.iModel);
|
|
938
|
-
const relationshipECClassIdsToSkip = new Set();
|
|
939
|
-
for await (const row of opts.iModel.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)")) {
|
|
940
|
-
relationshipECClassIdsToSkip.add(row.ECInstanceId);
|
|
941
|
-
}
|
|
942
1035
|
for (const csFile of csFileProps) {
|
|
943
1036
|
const csReader = core_backend_1.SqliteChangesetReader.openFile({
|
|
944
1037
|
fileName: csFile.pathname,
|
|
@@ -952,9 +1045,6 @@ class ChangedInstanceIds {
|
|
|
952
1045
|
}
|
|
953
1046
|
const changes = [...ecChangeUnifier.instances];
|
|
954
1047
|
for (const change of changes) {
|
|
955
|
-
if (change.ECClassId !== undefined &&
|
|
956
|
-
relationshipECClassIdsToSkip.has(change.ECClassId))
|
|
957
|
-
continue;
|
|
958
1048
|
await changedInstanceIds.addChange(change);
|
|
959
1049
|
}
|
|
960
1050
|
csReader.close();
|