@itwin/imodel-transformer 1.0.1-customchanges.2 → 1.0.1-customchanges.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/IModelExporter.d.ts +23 -58
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +97 -92
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +10 -34
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +98 -177
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/package.json +2 -2
|
@@ -220,7 +220,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
220
220
|
this._cachedSynchronizationVersion = undefined;
|
|
221
221
|
this._targetClassNameToClassIdCache = new Map();
|
|
222
222
|
// if undefined, it can be initialized by calling [[this.processChangesets]]
|
|
223
|
-
this._hasElementChangedCache = undefined;
|
|
224
223
|
this._deletedSourceRelationshipData = undefined;
|
|
225
224
|
this._yieldManager = new core_bentley_1.YieldManager();
|
|
226
225
|
/** The directory where schemas will be exported, a random temporary directory */
|
|
@@ -913,13 +912,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
913
912
|
* @note A subclass can override this method to provide custom change detection behavior.
|
|
914
913
|
*/
|
|
915
914
|
hasElementChanged(sourceElement) {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
nodeAssert(this._sourceChangeDataState === "has-changes", "change data should be initialized by now");
|
|
921
|
-
nodeAssert(this._hasElementChangedCache !== undefined, "has element changed cache should be initialized by now");
|
|
922
|
-
return this._hasElementChangedCache.has(sourceElement.id);
|
|
915
|
+
const sourceDbChanges = this.exporter.sourceDbChanges;
|
|
916
|
+
return (!sourceDbChanges || // are we processing changes? if not then element is considered as changed
|
|
917
|
+
sourceDbChanges.element.insertIds.has(sourceElement.id) ||
|
|
918
|
+
sourceDbChanges.element.updateIds.has(sourceElement.id));
|
|
923
919
|
}
|
|
924
920
|
completePartiallyCommittedElements() {
|
|
925
921
|
for (const sourceElementId of this._partiallyCommittedElementIds) {
|
|
@@ -1114,8 +1110,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1114
1110
|
}
|
|
1115
1111
|
}
|
|
1116
1112
|
}
|
|
1117
|
-
if (!this.hasElementChanged(sourceElement))
|
|
1113
|
+
if (!this.hasElementChanged(sourceElement)) {
|
|
1114
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `Skipping unchanged element (${sourceElement.id}, ${sourceElement.getDisplayLabel()}).`);
|
|
1118
1115
|
return;
|
|
1116
|
+
}
|
|
1119
1117
|
if (!this.doAllReferencesExistInTarget(sourceElement)) {
|
|
1120
1118
|
this._partiallyCommittedElementIds.add(sourceElement.id);
|
|
1121
1119
|
}
|
|
@@ -1770,39 +1768,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1770
1768
|
await this.processChangesets();
|
|
1771
1769
|
this._initialized = true;
|
|
1772
1770
|
}
|
|
1773
|
-
async handleCustomChanges(hasElementChangedCache, deleteIdsProcessed) {
|
|
1774
|
-
// The hasElementChangedCache gets populated by changes from this._csFileProps.
|
|
1775
|
-
// Because there is a possibility that someone could manually add ids to exporter.sourceDbChanges, we must separately process exporter.sourceDbChanges and add them to our hasElementChangedCache.
|
|
1776
|
-
// Without this change we risk onExportElement returning early because we use hasElementChangedCache to decide if an element has changed or not.
|
|
1777
|
-
this.exporter.sourceDbChanges?.element.updateIds.forEach((id) => hasElementChangedCache.add(id));
|
|
1778
|
-
this.exporter.sourceDbChanges?.element.insertIds.forEach((id) => hasElementChangedCache.add(id));
|
|
1779
|
-
// This loop is to process all custom deleteIds. Unclear if the special logic is still necessary for relationships or not (TODO!!). For all other entities, we assume that the element is still present in the sourceDb because it is not
|
|
1780
|
-
// a real delete and instead a simulated delete to update filtering criteria between source and target. Since the element is still present, we do not need to call processDeletedOp to find the corresponding targetId.
|
|
1781
|
-
// We can instead rely on `forEachTrackedElement` at the top of processChangesets to find the corresponding targetId.
|
|
1782
|
-
// Note this also assumes we don't need to handle entity recreation for these custom deletes. I.e. a caller of API would not be able to add a custom delete for an entity that was recreated.
|
|
1783
|
-
// a delete followed by an insert.
|
|
1784
|
-
// ASSUME: If a changeset has a deleteId then custom change will never reference it. Is this still true if it was re-inserted? (TODO!!)
|
|
1785
|
-
if (this.exporter.sourceDbChanges?.hasCustomRelationshipChanges) {
|
|
1786
|
-
for (const id of this.exporter.sourceDbChanges?.relationship.deleteIds.keys() ??
|
|
1787
|
-
[]) {
|
|
1788
|
-
if (deleteIdsProcessed?.has(id))
|
|
1789
|
-
continue;
|
|
1790
|
-
const customData = this.exporter.sourceDbChanges?.getCustomRelationshipDataFromId(id);
|
|
1791
|
-
if (customData === undefined) {
|
|
1792
|
-
core_bentley_1.Logger.logError(loggerCategory, "Custom data not found for relationship.", { id });
|
|
1793
|
-
continue;
|
|
1794
|
-
}
|
|
1795
|
-
const classFullName = customData.classFullName;
|
|
1796
|
-
const sourceIdOfRelationshipInSource = customData?.sourceIdOfRelationship;
|
|
1797
|
-
const targetIdOfRelationshipInSource = customData?.targetIdOfRelationship;
|
|
1798
|
-
await this.processRelationshipDeleteOp(id, classFullName, sourceIdOfRelationshipInSource, targetIdOfRelationshipInSource);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
1771
|
/**
|
|
1803
|
-
* Reads all the changeset files in the private member of the transformer: _csFileProps
|
|
1804
|
-
*
|
|
1805
|
-
* Populates this._hasElementChangedCache with a set of elementIds that have been updated or inserted into the database.
|
|
1772
|
+
* Reads all the changeset files in the private member of the transformer: _csFileProps
|
|
1773
|
+
* and finds the corresponding target entity for any deleted source entities and remaps the sourceId to the targetId.
|
|
1806
1774
|
* This function returns early if csFileProps is undefined or is of length 0.
|
|
1807
1775
|
* @returns void
|
|
1808
1776
|
*/
|
|
@@ -1810,7 +1778,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1810
1778
|
this.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1811
1779
|
this.context.remapElement(sourceElementId, targetElementId);
|
|
1812
1780
|
});
|
|
1813
|
-
|
|
1781
|
+
if (this.exporter.sourceDbChanges)
|
|
1782
|
+
await this.addCustomChanges(this.exporter.sourceDbChanges);
|
|
1814
1783
|
if (this._csFileProps === undefined || this._csFileProps.length === 0) {
|
|
1815
1784
|
if (this.exporter.sourceDbChanges === undefined ||
|
|
1816
1785
|
!this.exporter.sourceDbChanges.hasChanges)
|
|
@@ -1819,7 +1788,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1819
1788
|
if (this._sourceChangeDataState === "no-changes")
|
|
1820
1789
|
this._sourceChangeDataState = "has-changes";
|
|
1821
1790
|
}
|
|
1822
|
-
const hasElementChangedCache = new Set();
|
|
1823
1791
|
const relationshipECClassIdsToSkip = new Set();
|
|
1824
1792
|
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)")) {
|
|
1825
1793
|
relationshipECClassIdsToSkip.add(row.ECInstanceId);
|
|
@@ -1828,10 +1796,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1828
1796
|
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)")) {
|
|
1829
1797
|
relationshipECClassIds.add(row.ECInstanceId);
|
|
1830
1798
|
}
|
|
1831
|
-
const elementECClassIds = new Set();
|
|
1832
|
-
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.Element)")) {
|
|
1833
|
-
elementECClassIds.add(row.ECInstanceId);
|
|
1834
|
-
}
|
|
1835
1799
|
// For later use when processing deletes.
|
|
1836
1800
|
const alreadyImportedElementInserts = new Set();
|
|
1837
1801
|
const alreadyImportedModelInserts = new Set();
|
|
@@ -1846,9 +1810,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1846
1810
|
alreadyImportedModelInserts.add(targetModelId);
|
|
1847
1811
|
});
|
|
1848
1812
|
this._deletedSourceRelationshipData = new Map();
|
|
1849
|
-
/** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
|
|
1850
|
-
const elemIdToScopeEsa = new Map();
|
|
1851
|
-
const deleteIdsProcessed = new Set();
|
|
1852
1813
|
for (const csFile of this._csFileProps ?? []) {
|
|
1853
1814
|
const csReader = core_backend_1.SqliteChangesetReader.openFile({
|
|
1854
1815
|
fileName: csFile.pathname,
|
|
@@ -1861,6 +1822,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1861
1822
|
ecChangeUnifier.appendFrom(csAdaptor);
|
|
1862
1823
|
}
|
|
1863
1824
|
const changes = [...ecChangeUnifier.instances];
|
|
1825
|
+
/** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
|
|
1826
|
+
const elemIdToScopeEsa = new Map();
|
|
1864
1827
|
for (const change of changes) {
|
|
1865
1828
|
if (change.ECClassId !== undefined &&
|
|
1866
1829
|
relationshipECClassIdsToSkip.has(change.ECClassId))
|
|
@@ -1872,10 +1835,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1872
1835
|
change.Kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
|
|
1873
1836
|
elemIdToScopeEsa.set(change.Element.Id, change);
|
|
1874
1837
|
}
|
|
1875
|
-
else if ((changeType === "Inserted" || changeType === "Updated") &&
|
|
1876
|
-
change.ECClassId !== undefined &&
|
|
1877
|
-
elementECClassIds.has(change.ECClassId))
|
|
1878
|
-
hasElementChangedCache.add(change.ECInstanceId);
|
|
1879
1838
|
}
|
|
1880
1839
|
// Loop to process deletes.
|
|
1881
1840
|
for (const change of changes) {
|
|
@@ -1888,80 +1847,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1888
1847
|
if (changeType !== "Deleted" ||
|
|
1889
1848
|
relationshipECClassIdsToSkip.has(ecClassId))
|
|
1890
1849
|
continue;
|
|
1891
|
-
|
|
1892
|
-
if (change.$meta?.classFullName === undefined) {
|
|
1893
|
-
core_bentley_1.Logger.logError(loggerCategory, "ClassFullName was not found for relationship when reading changes. Relationship delete will not propagate.", { relationshipId: change.ECInstanceId, ecClassId });
|
|
1894
|
-
continue;
|
|
1895
|
-
}
|
|
1896
|
-
if (change.SourceECInstanceId === undefined ||
|
|
1897
|
-
change.TargetECInstanceId === undefined) {
|
|
1898
|
-
core_bentley_1.Logger.logError(loggerCategory, "SourceECInstanceId or TargetECInstanceId was not found for relationship when reading changes. Relationship delete will not propagate.", {
|
|
1899
|
-
relationshipId: change.ECInstanceId,
|
|
1900
|
-
ecClassId,
|
|
1901
|
-
classFullName: change.$meta.classFullName,
|
|
1902
|
-
});
|
|
1903
|
-
continue;
|
|
1904
|
-
}
|
|
1905
|
-
await this.processRelationshipDeleteOp(change.ECInstanceId, change.$meta.classFullName, change.SourceECInstanceId, change.TargetECInstanceId);
|
|
1906
|
-
}
|
|
1907
|
-
else {
|
|
1908
|
-
await this.processElementDeleteOp(change.ECInstanceId, alreadyImportedElementInserts, alreadyImportedModelInserts, elemIdToScopeEsa, change.FederationGuid);
|
|
1909
|
-
}
|
|
1910
|
-
deleteIdsProcessed.add(change.ECInstanceId);
|
|
1850
|
+
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1911
1851
|
}
|
|
1912
1852
|
csReader.close();
|
|
1913
1853
|
}
|
|
1914
|
-
await this.handleCustomChanges(hasElementChangedCache, deleteIdsProcessed);
|
|
1915
|
-
this._hasElementChangedCache = hasElementChangedCache;
|
|
1916
1854
|
return;
|
|
1917
1855
|
}
|
|
1918
1856
|
/**
|
|
1919
|
-
*
|
|
1920
|
-
*
|
|
1921
|
-
*
|
|
1922
|
-
*
|
|
1923
|
-
* @param
|
|
1924
|
-
* @param targetIdOfRelationshipInSource the element Id acting as the target of the relationship in the sourceDb
|
|
1925
|
-
* @returns
|
|
1926
|
-
*/
|
|
1927
|
-
async processRelationshipDeleteOp(changedInstanceId, classFullName, sourceIdOfRelationshipInSource, targetIdOfRelationshipInSource) {
|
|
1928
|
-
// we need a connected iModel with changes to remap elements with deletions
|
|
1929
|
-
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1930
|
-
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index &&
|
|
1931
|
-
(this.exporter.sourceDbChanges === undefined ||
|
|
1932
|
-
!this.exporter.sourceDbChanges.hasChanges);
|
|
1933
|
-
if (notConnectedModel || noChanges)
|
|
1934
|
-
return;
|
|
1935
|
-
const sourceIdOfRelationshipInTarget = await this.getTargetIdFromSourceId(sourceIdOfRelationshipInSource, true);
|
|
1936
|
-
const targetIdOfRelationshipInTarget = await this.getTargetIdFromSourceId(targetIdOfRelationshipInSource, true);
|
|
1937
|
-
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
1938
|
-
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1939
|
-
classFullName,
|
|
1940
|
-
sourceIdInTarget: sourceIdOfRelationshipInTarget,
|
|
1941
|
-
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
1942
|
-
});
|
|
1943
|
-
}
|
|
1944
|
-
else if (this.sourceDb === this.provenanceSourceDb) {
|
|
1945
|
-
const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
|
|
1946
|
-
classFullName,
|
|
1947
|
-
sourceId: sourceIdOfRelationshipInSource,
|
|
1948
|
-
targetId: targetIdOfRelationshipInSource,
|
|
1949
|
-
});
|
|
1950
|
-
if (relProvenance && relProvenance.relationshipId)
|
|
1951
|
-
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1952
|
-
classFullName,
|
|
1953
|
-
relId: relProvenance.relationshipId,
|
|
1954
|
-
provenanceAspectId: relProvenance.aspectId,
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
/**
|
|
1959
|
-
* This function is called by the transformer as it is about to process the changesets passed to it in [[IModelTransformOptions.argsForProcessChanges]].
|
|
1960
|
-
* This would be after the exporter has already processed the same set of changesets passed to the transformer in [[IModelTransformOptions.argsForProcessChanges]].
|
|
1961
|
-
* This function should be used to modify the exporter's sourceDbChanges, if necessary, using [[ChangedInstanceIds.addCustomChange]]. See [[ChangedInstanceIds.addCustomChange]] for more information.
|
|
1962
|
-
* @param sourceDbChanges will only be defined if the transformer was called with [[IModelTransformOptions.argsForProcessChanges]].
|
|
1963
|
-
* @note If defined, sourceDbChanges will already be populated with the changesets passed to the transformer, if any when this function is called by the transformer.
|
|
1964
|
-
* @note The transformer will have built up the remap table between the source and target iModels before calling this function. This means that functions like [[IModelTransformer.context.findTargetElementId]] will return meaningful results.
|
|
1857
|
+
* This will be called when transformer is called with [[IModelTransformOptions.argsForProcessChanges]] to process changes.
|
|
1858
|
+
* It will be executed after changes in changesets are populated into `sourceDbChanges` and before data processing begins.
|
|
1859
|
+
* Remap table between the source and target iModels will be built at that time, meaning that functions like [[IModelTransformer.context.findTargetElementId]] will return meaningful results.
|
|
1860
|
+
* This function should be used to modify the `sourceDbChanges`, if necessary, using `add custom change` methods in [[ChangedInstanceIds]], such as [[ChangedInstanceIds.addCustomElementChange]], [[ChangedInstanceIds.addCustomModelChange]] and other.
|
|
1861
|
+
* @param sourceDbChanges the ChangedInstanceIds already populated by the exporter with the changes in source changesets, if any, passed to the transformer.
|
|
1965
1862
|
* @note Its expected that this function be overridden by a subclass of transformer if it needs to modify sourceDbChanges.
|
|
1966
1863
|
*/
|
|
1967
1864
|
async addCustomChanges(_sourceDbChanges) { }
|
|
@@ -1970,11 +1867,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1970
1867
|
* @param change the change to process, must be of changeType "Deleted"
|
|
1971
1868
|
* @param mapOfDeletedElemIdToScopeEsas a map of elementIds to changedECInstances (which are ESAs). the elementId is not the id of the esa itself, but the elementid that the esa was stored on before the esa's deletion.
|
|
1972
1869
|
* All ESAs in this map are part of the transformer's scope / ESA data and are tracked in case the ESA is deleted in the target.
|
|
1870
|
+
* @param isRelationship is relationship or not
|
|
1973
1871
|
* @param alreadyImportedElementInserts used to handle entity recreation and not delete already handled element inserts.
|
|
1974
1872
|
* @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
|
|
1975
1873
|
* @returns void
|
|
1976
1874
|
*/
|
|
1977
|
-
async
|
|
1875
|
+
async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1978
1876
|
// we need a connected iModel with changes to remap elements with deletions
|
|
1979
1877
|
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1980
1878
|
const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index &&
|
|
@@ -1982,69 +1880,92 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1982
1880
|
!this.exporter.sourceDbChanges.hasChanges);
|
|
1983
1881
|
if (notConnectedModel || noChanges)
|
|
1984
1882
|
return;
|
|
1985
|
-
let targetId = await this.getTargetIdFromSourceId(changedInstanceId, false, mapOfDeletedElemIdToScopeEsas, federationGuid);
|
|
1986
|
-
if (targetId === undefined && this.sourceDb === this.provenanceSourceDb) {
|
|
1987
|
-
targetId = this._queryProvenanceForElement(changedInstanceId);
|
|
1988
|
-
}
|
|
1989
|
-
// since we are processing one changeset at a time, we can see local source deletes
|
|
1990
|
-
// of entities that were never synced and can be safely ignored
|
|
1991
|
-
const deletionNotInTarget = !targetId;
|
|
1992
|
-
if (deletionNotInTarget)
|
|
1993
|
-
return;
|
|
1994
|
-
this.context.remapElement(changedInstanceId, targetId);
|
|
1995
|
-
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
1996
|
-
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
1997
|
-
if (alreadyImportedElementInserts.has(targetId)) {
|
|
1998
|
-
this.exporter.sourceDbChanges?.element.deleteIds.delete(changedInstanceId);
|
|
1999
|
-
}
|
|
2000
|
-
if (alreadyImportedModelInserts.has(targetId)) {
|
|
2001
|
-
this.exporter.sourceDbChanges?.model.deleteIds.delete(changedInstanceId);
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
/**
|
|
2005
|
-
* Find the corresponding id in the targetDb given a id from the sourceDb
|
|
2006
|
-
* @param id the id in the source that we want to find the target id for
|
|
2007
|
-
* @param isRelationship Changes the way we look for the federationGuid , if true we look for the federationGuid on the element itself, if false we expect it to be passed in because it was part of the ChangedECInstance.
|
|
2008
|
-
* Typically the source and targetIds of the relationship and not the relationshipId itself is passed to this function
|
|
2009
|
-
* @param mapOfDeletedElemIdToScopeEsas a map of elementIds to changedECInstances (which are ESAs). the elementId is not the id of the esa itself, but the elementid that the esa was stored on before the esa's deletion.
|
|
2010
|
-
* All ESAs in this map are part of the transformer's scope / ESA data and are tracked in case the ESA is deleted in the target.
|
|
2011
|
-
* @param federationGuid
|
|
2012
|
-
* @returns id of the corresponding entity in the targetDb or undefined if not found
|
|
2013
|
-
*/
|
|
2014
|
-
async getTargetIdFromSourceId(id, isRelationship, mapOfDeletedElemIdToScopeEsas, federationGuid) {
|
|
2015
1883
|
/**
|
|
2016
1884
|
* if our ChangedECInstance is in the provenanceDb, then we can use the ids we find in the ChangedECInstance to query for ESAs.
|
|
2017
1885
|
* This is because the ESAs are stored on an element Id thats present in the provenanceDb.
|
|
2018
1886
|
*/
|
|
2019
1887
|
const changeDataInProvenanceDb = this.sourceDb === this.provenanceDb;
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
if (
|
|
1888
|
+
const getTargetIdFromSourceId = async (id) => {
|
|
1889
|
+
let identifierValue;
|
|
1890
|
+
let element;
|
|
1891
|
+
if (isRelationship) {
|
|
1892
|
+
element = this.sourceDb.elements.tryGetElement(id);
|
|
1893
|
+
}
|
|
1894
|
+
const fedGuid = isRelationship
|
|
1895
|
+
? element?.federationGuid
|
|
1896
|
+
: change.FederationGuid;
|
|
1897
|
+
if (changeDataInProvenanceDb) {
|
|
1898
|
+
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
1899
|
+
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([
|
|
1900
|
+
this.targetScopeElementId,
|
|
1901
|
+
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1902
|
+
id,
|
|
1903
|
+
]))) {
|
|
1904
|
+
identifierValue = row.Identifier;
|
|
1905
|
+
}
|
|
1906
|
+
identifierValue =
|
|
1907
|
+
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.Identifier;
|
|
1908
|
+
}
|
|
1909
|
+
// Check for targetId by an esa first
|
|
1910
|
+
if (changeDataInProvenanceDb && identifierValue) {
|
|
1911
|
+
const targetId = identifierValue;
|
|
1912
|
+
return targetId;
|
|
1913
|
+
}
|
|
1914
|
+
// Check for targetId using sourceId's fedguid if we didn't find an esa.
|
|
1915
|
+
if (fedGuid) {
|
|
1916
|
+
const targetId = this._queryElemIdByFedGuid(this.targetDb, fedGuid);
|
|
2030
1917
|
return targetId;
|
|
1918
|
+
}
|
|
1919
|
+
return undefined;
|
|
1920
|
+
};
|
|
1921
|
+
const changedInstanceId = change.ECInstanceId;
|
|
1922
|
+
if (isRelationship) {
|
|
1923
|
+
const sourceIdOfRelationshipInSource = change.SourceECInstanceId;
|
|
1924
|
+
const targetIdOfRelationshipInSource = change.TargetECInstanceId;
|
|
1925
|
+
const classFullName = change.$meta?.classFullName;
|
|
1926
|
+
const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
|
|
1927
|
+
const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
|
|
1928
|
+
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
1929
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1930
|
+
classFullName: classFullName ?? "",
|
|
1931
|
+
sourceIdInTarget: sourceIdOfRelationshipInTarget,
|
|
1932
|
+
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
else if (this.sourceDb === this.provenanceSourceDb) {
|
|
1936
|
+
const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
|
|
1937
|
+
classFullName: classFullName ?? "",
|
|
1938
|
+
sourceId: sourceIdOfRelationshipInSource,
|
|
1939
|
+
targetId: targetIdOfRelationshipInSource,
|
|
1940
|
+
});
|
|
1941
|
+
if (relProvenance && relProvenance.relationshipId)
|
|
1942
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1943
|
+
classFullName: classFullName ?? "",
|
|
1944
|
+
relId: relProvenance.relationshipId,
|
|
1945
|
+
provenanceAspectId: relProvenance.aspectId,
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
2031
1948
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
1949
|
+
else {
|
|
1950
|
+
let targetId = await getTargetIdFromSourceId(changedInstanceId);
|
|
1951
|
+
if (targetId === undefined && this.sourceDb === this.provenanceSourceDb) {
|
|
1952
|
+
targetId = this._queryProvenanceForElement(changedInstanceId);
|
|
1953
|
+
}
|
|
1954
|
+
// since we are processing one changeset at a time, we can see local source deletes
|
|
1955
|
+
// of entities that were never synced and can be safely ignored
|
|
1956
|
+
const deletionNotInTarget = !targetId;
|
|
1957
|
+
if (deletionNotInTarget)
|
|
1958
|
+
return;
|
|
1959
|
+
this.context.remapElement(changedInstanceId, targetId);
|
|
1960
|
+
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
1961
|
+
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
1962
|
+
if (alreadyImportedElementInserts.has(targetId)) {
|
|
1963
|
+
this.exporter.sourceDbChanges?.element.deleteIds.delete(changedInstanceId);
|
|
1964
|
+
}
|
|
1965
|
+
if (alreadyImportedModelInserts.has(targetId)) {
|
|
1966
|
+
this.exporter.sourceDbChanges?.model.deleteIds.delete(changedInstanceId);
|
|
2041
1967
|
}
|
|
2042
|
-
identifierValue =
|
|
2043
|
-
identifierValue ?? mapOfDeletedElemIdToScopeEsas?.get(id)?.Identifier;
|
|
2044
|
-
if (identifierValue)
|
|
2045
|
-
return identifierValue;
|
|
2046
1968
|
}
|
|
2047
|
-
return undefined;
|
|
2048
1969
|
}
|
|
2049
1970
|
async _tryInitChangesetData(args) {
|
|
2050
1971
|
if (!args ||
|