@itwin/imodel-transformer 1.0.0-dev.1 → 1.0.0-dev.2
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/IModelCloneContext.d.ts +1 -4
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +0 -30
- 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 +4 -43
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +1 -49
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +19 -57
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +119 -304
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/package.json +1 -1
|
@@ -688,6 +688,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
688
688
|
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? false,
|
|
689
689
|
});
|
|
690
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
|
|
693
|
+
* The identifier on the ESA is the id of the element in the [[IModelTransformer.provenanceSourceDb]]
|
|
694
|
+
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
695
|
+
* @param entityInProvenanceSourceId
|
|
696
|
+
* @returns the elementId that the ESA is stored on, esa.Element.Id
|
|
697
|
+
*/
|
|
691
698
|
_queryProvenanceForElement(entityInProvenanceSourceId) {
|
|
692
699
|
return this.provenanceDb.withPreparedStatement(`
|
|
693
700
|
SELECT esa.Element.Id
|
|
@@ -705,6 +712,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
705
712
|
return undefined;
|
|
706
713
|
});
|
|
707
714
|
}
|
|
715
|
+
/**
|
|
716
|
+
* Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
|
|
717
|
+
* The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
|
|
718
|
+
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
719
|
+
* @param entityInProvenanceSourceId
|
|
720
|
+
* @returns
|
|
721
|
+
*/
|
|
708
722
|
_queryProvenanceForRelationship(entityInProvenanceSourceId, sourceRelInfo) {
|
|
709
723
|
return this.provenanceDb.withPreparedStatement(`
|
|
710
724
|
SELECT
|
|
@@ -850,6 +864,20 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
850
864
|
onTransformElement(sourceElement) {
|
|
851
865
|
core_bentley_1.Logger.logTrace(loggerCategory, `onTransformElement(${sourceElement.id}) "${sourceElement.getDisplayLabel()}"`);
|
|
852
866
|
const targetElementProps = this.context.cloneElement(sourceElement, { binaryGeometry: this._options.cloneUsingBinaryGeometry });
|
|
867
|
+
// Special case: source element is the root subject
|
|
868
|
+
if (sourceElement.id === core_common_1.IModel.rootSubjectId) {
|
|
869
|
+
const targetElementId = this.context.findTargetElementId(sourceElement.id);
|
|
870
|
+
// When remapping rootSubject from source to non root subject in target, the code.scope gets remapped incorrectly.
|
|
871
|
+
// This is because the rootSubject has no parent and its code.scope is unique in that it is the id of itself.
|
|
872
|
+
// For all other subjects which do have parents the code.scope and its parent should be in agreement.
|
|
873
|
+
if (targetElementId !== core_bentley_1.Id64.invalid &&
|
|
874
|
+
targetElementId !== core_common_1.IModel.rootSubjectId) {
|
|
875
|
+
const targetElement = this.targetDb.elements.getElement(targetElementId);
|
|
876
|
+
targetElementProps.parent =
|
|
877
|
+
targetElement.parent ?? targetElementProps.parent;
|
|
878
|
+
targetElementProps.code.scope = targetElement.code.scope;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
853
881
|
if (sourceElement instanceof core_backend_1.Subject) {
|
|
854
882
|
if (targetElementProps.jsonProperties?.Subject?.Job) {
|
|
855
883
|
// don't propagate source channels into target (legacy bridge case)
|
|
@@ -1344,21 +1372,24 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1344
1372
|
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
1345
1373
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1346
1374
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1347
|
-
const
|
|
1375
|
+
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1376
|
+
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1377
|
+
const [syncChangesetsToClearKey, syncChangesetsToUpdateKey] = this
|
|
1348
1378
|
.isReverseSynchronization
|
|
1349
1379
|
? [
|
|
1350
|
-
|
|
1351
|
-
|
|
1380
|
+
pendingReverseSyncChangesetIndicesKey,
|
|
1381
|
+
pendingSyncChangesetIndicesKey,
|
|
1352
1382
|
]
|
|
1353
1383
|
: [
|
|
1354
|
-
|
|
1355
|
-
|
|
1384
|
+
pendingSyncChangesetIndicesKey,
|
|
1385
|
+
pendingReverseSyncChangesetIndicesKey,
|
|
1356
1386
|
];
|
|
1357
1387
|
for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1358
|
-
|
|
1359
|
-
//
|
|
1360
|
-
|
|
1361
|
-
|
|
1388
|
+
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1389
|
+
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
1390
|
+
jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
|
|
1391
|
+
return csIndex > this._startingChangesetIndices.source;
|
|
1392
|
+
});
|
|
1362
1393
|
// if reverse sync then we may have received provenance changes which should be marked as sync changes
|
|
1363
1394
|
if (this.isReverseSynchronization) {
|
|
1364
1395
|
nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
|
|
@@ -1479,15 +1510,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1479
1510
|
core_bentley_1.Logger.logWarning(loggerCategory, "tried to delete a relationship that wasn't in change data");
|
|
1480
1511
|
return;
|
|
1481
1512
|
}
|
|
1482
|
-
const
|
|
1483
|
-
{
|
|
1513
|
+
const id = deletedRelData.relId ??
|
|
1514
|
+
this.targetDb.relationships.tryGetInstance(deletedRelData.classFullName, {
|
|
1484
1515
|
sourceId: deletedRelData.sourceIdInTarget,
|
|
1485
1516
|
targetId: deletedRelData.targetIdInTarget,
|
|
1486
|
-
};
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1517
|
+
})?.id;
|
|
1518
|
+
if (id) {
|
|
1519
|
+
this.importer.deleteRelationship({
|
|
1520
|
+
id,
|
|
1521
|
+
classFullName: deletedRelData.classFullName,
|
|
1522
|
+
});
|
|
1491
1523
|
}
|
|
1492
1524
|
if (deletedRelData.provenanceAspectId) {
|
|
1493
1525
|
try {
|
|
@@ -1531,8 +1563,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1531
1563
|
const json = JSON.parse(statement.getValue(2).getString());
|
|
1532
1564
|
const targetRelInstanceId = json.targetRelInstanceId ?? json.provenanceRelInstanceId;
|
|
1533
1565
|
if (targetRelInstanceId) {
|
|
1534
|
-
|
|
1535
|
-
|
|
1566
|
+
this.importer.deleteRelationship({
|
|
1567
|
+
id: targetRelInstanceId,
|
|
1568
|
+
classFullName: core_backend_1.ElementRefersToElements.classFullName,
|
|
1569
|
+
});
|
|
1536
1570
|
}
|
|
1537
1571
|
aspectDeleteIds.push(statement.getValue(0).getId());
|
|
1538
1572
|
}
|
|
@@ -1815,7 +1849,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1815
1849
|
if (changeType !== "Deleted" ||
|
|
1816
1850
|
relationshipECClassIdsToSkip.has(ecClassId))
|
|
1817
1851
|
continue;
|
|
1818
|
-
this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1852
|
+
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1819
1853
|
}
|
|
1820
1854
|
csReader.close();
|
|
1821
1855
|
}
|
|
@@ -1832,116 +1866,98 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1832
1866
|
* @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
|
|
1833
1867
|
* @returns void
|
|
1834
1868
|
*/
|
|
1835
|
-
processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1869
|
+
async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1836
1870
|
// we need a connected iModel with changes to remap elements with deletions
|
|
1837
1871
|
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1838
1872
|
const noChanges = this._synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
1839
1873
|
if (notConnectedModel || noChanges)
|
|
1840
1874
|
return;
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
const
|
|
1846
|
-
|
|
1847
|
-
const sourceElemFedGuid = change.FederationGuid;
|
|
1875
|
+
/**
|
|
1876
|
+
* if our ChangedECInstance is in the provenanceDb, then we can use the ids we find in the ChangedECInstance to query for ESAs.
|
|
1877
|
+
* This is because the ESAs are stored on an element Id thats present in the provenanceDb.
|
|
1878
|
+
*/
|
|
1879
|
+
const changeDataInProvenanceDb = this.sourceDb === this.provenanceDb;
|
|
1880
|
+
const getTargetIdFromSourceId = async (id) => {
|
|
1848
1881
|
let identifierValue;
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
// look for aspect where the ecInstanceId = the aspect.element.id
|
|
1853
|
-
if (aspect.element.id === instId &&
|
|
1854
|
-
aspect.scope.id === this.targetScopeElementId)
|
|
1855
|
-
identifierValue = aspect.identifier;
|
|
1856
|
-
}
|
|
1857
|
-
// Think I need to query the esas given the instId.. not sure what db to do it on though.. soruce or target.. or provenance?
|
|
1858
|
-
// I need to know the id of the element dpeneding on which db its stored in.
|
|
1882
|
+
let element;
|
|
1883
|
+
if (isRelationship) {
|
|
1884
|
+
element = this.sourceDb.elements.tryGetElement(id);
|
|
1859
1885
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1886
|
+
const fedGuid = isRelationship
|
|
1887
|
+
? element?.federationGuid
|
|
1888
|
+
: change.FederationGuid;
|
|
1889
|
+
if (changeDataInProvenanceDb) {
|
|
1890
|
+
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
1891
|
+
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([
|
|
1892
|
+
this.targetScopeElementId,
|
|
1893
|
+
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1894
|
+
id,
|
|
1895
|
+
]))) {
|
|
1896
|
+
identifierValue = row.Identifier;
|
|
1897
|
+
}
|
|
1898
|
+
identifierValue =
|
|
1899
|
+
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.Identifier;
|
|
1864
1900
|
}
|
|
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);
|
|
1901
|
+
// Check for targetId by an esa first
|
|
1902
|
+
if (changeDataInProvenanceDb && identifierValue) {
|
|
1903
|
+
const targetId = identifierValue;
|
|
1904
|
+
return targetId;
|
|
1882
1905
|
}
|
|
1883
|
-
if
|
|
1884
|
-
|
|
1906
|
+
// Check for targetId using sourceId's fedguid if we didn't find an esa.
|
|
1907
|
+
if (fedGuid) {
|
|
1908
|
+
const targetId = this._queryElemIdByFedGuid(this.targetDb, fedGuid);
|
|
1909
|
+
return targetId;
|
|
1885
1910
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1911
|
+
return undefined;
|
|
1912
|
+
};
|
|
1913
|
+
const changedInstanceId = change.ECInstanceId;
|
|
1914
|
+
if (isRelationship) {
|
|
1890
1915
|
const sourceIdOfRelationshipInSource = change.SourceECInstanceId;
|
|
1891
1916
|
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, {
|
|
1917
|
+
const classFullName = change.$meta?.classFullName;
|
|
1918
|
+
const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
|
|
1919
|
+
const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
|
|
1920
|
+
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
1921
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1925
1922
|
classFullName: classFullName ?? "",
|
|
1926
|
-
sourceIdInTarget,
|
|
1927
|
-
targetIdInTarget,
|
|
1923
|
+
sourceIdInTarget: sourceIdOfRelationshipInTarget,
|
|
1924
|
+
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
1928
1925
|
});
|
|
1929
1926
|
}
|
|
1930
|
-
else {
|
|
1931
|
-
|
|
1932
|
-
const relProvenance = this._queryProvenanceForRelationship(instId, {
|
|
1927
|
+
else if (this.sourceDb === this.provenanceSourceDb) {
|
|
1928
|
+
const relProvenance = this._queryProvenanceForRelationship(changedInstanceId, {
|
|
1933
1929
|
classFullName: classFullName ?? "",
|
|
1934
1930
|
sourceId: sourceIdOfRelationshipInSource,
|
|
1935
1931
|
targetId: targetIdOfRelationshipInSource,
|
|
1936
1932
|
});
|
|
1937
1933
|
if (relProvenance && relProvenance.relationshipId)
|
|
1938
|
-
this._deletedSourceRelationshipData.set(
|
|
1934
|
+
this._deletedSourceRelationshipData.set(changedInstanceId, {
|
|
1939
1935
|
classFullName: classFullName ?? "",
|
|
1940
1936
|
relId: relProvenance.relationshipId,
|
|
1941
1937
|
provenanceAspectId: relProvenance.aspectId,
|
|
1942
1938
|
});
|
|
1943
1939
|
}
|
|
1944
1940
|
}
|
|
1941
|
+
else {
|
|
1942
|
+
let targetId = await getTargetIdFromSourceId(changedInstanceId);
|
|
1943
|
+
if (targetId === undefined && this.sourceDb === this.provenanceSourceDb) {
|
|
1944
|
+
targetId = this._queryProvenanceForElement(changedInstanceId);
|
|
1945
|
+
}
|
|
1946
|
+
// since we are processing one changeset at a time, we can see local source deletes
|
|
1947
|
+
// of entities that were never synced and can be safely ignored
|
|
1948
|
+
const deletionNotInTarget = !targetId;
|
|
1949
|
+
if (deletionNotInTarget)
|
|
1950
|
+
return;
|
|
1951
|
+
this.context.remapElement(changedInstanceId, targetId);
|
|
1952
|
+
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
1953
|
+
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
1954
|
+
if (alreadyImportedElementInserts.has(targetId)) {
|
|
1955
|
+
this.exporter.sourceDbChanges?.element.deleteIds.delete(changedInstanceId);
|
|
1956
|
+
}
|
|
1957
|
+
if (alreadyImportedModelInserts.has(targetId)) {
|
|
1958
|
+
this.exporter.sourceDbChanges?.model.deleteIds.delete(changedInstanceId);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1945
1961
|
}
|
|
1946
1962
|
async _tryInitChangesetData(args) {
|
|
1947
1963
|
if (!args ||
|
|
@@ -2016,7 +2032,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2016
2032
|
await this.exporter.exportCodeSpecs();
|
|
2017
2033
|
await this.exporter.exportFonts();
|
|
2018
2034
|
if (this._options.skipPropagateChangesToRootElements) {
|
|
2019
|
-
// FIXME<NICK>: This option in exportAll was a maybe.
|
|
2020
2035
|
// The RepositoryModel and root Subject of the target iModel should not be transformed.
|
|
2021
2036
|
await this.exporter.exportChildElements(core_common_1.IModel.rootSubjectId); // start below the root Subject
|
|
2022
2037
|
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
|
|
@@ -2051,202 +2066,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2051
2066
|
: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
2052
2067
|
};
|
|
2053
2068
|
}
|
|
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
2069
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2251
2070
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2252
2071
|
* @note the transformer saves and pushes changes when its work is complete.
|
|
@@ -2303,10 +2122,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2303
2122
|
}
|
|
2304
2123
|
exports.IModelTransformer = IModelTransformer;
|
|
2305
2124
|
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
2125
|
/** IModelTransformer that clones the contents of a template model.
|
|
2311
2126
|
* @beta
|
|
2312
2127
|
*/
|