@itwin/imodel-transformer 2.0.0-dev.68 → 2.0.0-dev.69
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/ChangesetScanner.d.ts +111 -31
- package/lib/cjs/ChangesetScanner.d.ts.map +1 -1
- package/lib/cjs/ChangesetScanner.js +130 -47
- package/lib/cjs/ChangesetScanner.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +28 -13
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +65 -81
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +48 -15
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +174 -207
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/SourceClassKinds.d.ts +22 -0
- package/lib/cjs/SourceClassKinds.d.ts.map +1 -0
- package/lib/cjs/SourceClassKinds.js +60 -0
- package/lib/cjs/SourceClassKinds.js.map +1 -0
- package/lib/cjs/imodel-transformer.d.ts +1 -0
- package/lib/cjs/imodel-transformer.d.ts.map +1 -1
- package/lib/cjs/imodel-transformer.js.map +1 -1
- package/package.json +1 -1
|
@@ -541,7 +541,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
541
541
|
const combinedTransform = targetTransformInv.multiplyTransformTransform(srcTransform);
|
|
542
542
|
return combinedTransform;
|
|
543
543
|
}
|
|
544
|
-
// if undefined, it can be initialized by calling [[this.
|
|
544
|
+
// if undefined, it can be initialized by calling [[this.prepareSourceChanges]]
|
|
545
545
|
_deletedSourceRelationshipData = undefined;
|
|
546
546
|
/** Target relationship ids and target endpoint keys of [[_deletedSourceRelationshipData]] entries, for fast matching in [[onExportRelationship]]. */
|
|
547
547
|
_pendingRelationshipDeleteTargets = new Set();
|
|
@@ -1390,9 +1390,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1390
1390
|
/** state to prevent reinitialization, @see [[initialize]] */
|
|
1391
1391
|
_initialized = false;
|
|
1392
1392
|
_sourceChangeDataState = "uninited";
|
|
1393
|
-
|
|
1394
|
-
_csFileProps = undefined;
|
|
1395
|
-
_deletionRecordsByChangeset;
|
|
1393
|
+
_deletionBatches;
|
|
1396
1394
|
/**
|
|
1397
1395
|
* Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
|
|
1398
1396
|
* are intending to process changes. Callers may wish to explicitly call initialize if they need to execute code after initialize but before [[process]] is called.
|
|
@@ -1408,22 +1406,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1408
1406
|
await this._cloneContext.initialize();
|
|
1409
1407
|
const exporterInitOptions = await this.getExportInitOpts(this._options.argsForProcessChanges ?? {});
|
|
1410
1408
|
await this.initializeChangesetScanAndExporter(exporterInitOptions);
|
|
1411
|
-
// Exporter must be initialized prior to
|
|
1412
|
-
await this.
|
|
1409
|
+
// Exporter must be initialized prior to remapping deletions in order to properly handle entity recreations (an entity delete followed by an insert of that same entity).
|
|
1410
|
+
await this.prepareSourceChanges();
|
|
1413
1411
|
this._initialized = true;
|
|
1414
1412
|
}
|
|
1415
1413
|
async initializeChangesetScanAndExporter(exporterInitOptions) {
|
|
1416
|
-
|
|
1417
|
-
|
|
1414
|
+
const ranges = this._changesetRanges;
|
|
1415
|
+
if (ranges !== undefined &&
|
|
1416
|
+
ranges.length > 0 &&
|
|
1418
1417
|
this.sourceDb.isBriefcaseDb()) {
|
|
1419
|
-
|
|
1418
|
+
let changedInstanceIds = this.exporter.sourceDbChanges ??
|
|
1420
1419
|
("changedInstanceIds" in exporterInitOptions
|
|
1421
1420
|
? exporterInitOptions.changedInstanceIds
|
|
1422
|
-
:
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
}
|
|
1421
|
+
: undefined);
|
|
1422
|
+
if (changedInstanceIds !== undefined) {
|
|
1423
|
+
// The caller supplied changed IDs, so only deletion metadata is read.
|
|
1424
|
+
this._deletionBatches = await ChangesetScanner_1.ChangesetScanner.scan(this.sourceDb, await ChangesetScanner_1.ChangesetScanner.download(this.sourceDb, ranges), {});
|
|
1425
|
+
}
|
|
1426
|
+
else {
|
|
1427
|
+
const scanResult = await this.collectChanges(ranges);
|
|
1428
|
+
changedInstanceIds = scanResult.changedInstanceIds;
|
|
1429
|
+
this._deletionBatches = scanResult.deletionBatches;
|
|
1430
|
+
}
|
|
1427
1431
|
await this.exporter.initialize({
|
|
1428
1432
|
changedInstanceIds,
|
|
1429
1433
|
skipPropagateChangesToRootElements: exporterInitOptions.skipPropagateChangesToRootElements,
|
|
@@ -1434,19 +1438,42 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1434
1438
|
}
|
|
1435
1439
|
}
|
|
1436
1440
|
/**
|
|
1437
|
-
* Reads
|
|
1438
|
-
*
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1441
|
+
* Reads the source changes for the changeset ranges being processed. Override to supply changes from another
|
|
1442
|
+
* source, such as a precomputed cache, instead of downloading and reading changesets.
|
|
1443
|
+
*
|
|
1444
|
+
* An override can supply some ranges itself and pass the rest to `super.collectChanges()`. Pass the same
|
|
1445
|
+
* `changedInstanceIds` to every call and handle the ranges in order, so that changes to the same instance in
|
|
1446
|
+
* different ranges combine correctly. Use [[ChangedInstanceIds.addChangeRecord]] to add each supplied change.
|
|
1447
|
+
* See [Supplying source changes]($docs/learning/transformer/change-scanning.md) for the full contract and an example.
|
|
1448
|
+
* @param ranges Ordered, inclusive `[first, last]` changeset index ranges. They already exclude changesets
|
|
1449
|
+
* that must be skipped, such as those pushed by a previous synchronization in the other direction, so an
|
|
1450
|
+
* override must cover exactly these ranges.
|
|
1451
|
+
* @param changedInstanceIds Receives the changes read from the ranges. Defaults to a new, empty instance.
|
|
1452
|
+
* @returns The changed instance IDs to export and the records used to find the targets of deleted instances.
|
|
1453
|
+
* See [[CollectedChanges]] for what the result must include.
|
|
1454
|
+
* @note The default implementation downloads the changesets, reads them with `ChangesetReader`, and returns one
|
|
1455
|
+
* deletion batch per range.
|
|
1456
|
+
* @note Not called when the exporter already has changed instance IDs, for example when `changedInstanceIds`
|
|
1457
|
+
* is passed in [[IModelTransformOptions.argsForProcessChanges]]. The transformer then still downloads and reads
|
|
1458
|
+
* the changesets to find deletion records.
|
|
1459
|
+
* @beta
|
|
1441
1460
|
*/
|
|
1442
|
-
async
|
|
1461
|
+
async collectChanges(ranges, changedInstanceIds = new IModelExporter_1.ChangedInstanceIds(this.sourceDb)) {
|
|
1462
|
+
const deletionBatches = await ChangesetScanner_1.ChangesetScanner.scan(this.sourceDb, await ChangesetScanner_1.ChangesetScanner.download(this.sourceDb, ranges), { changedInstanceIds });
|
|
1463
|
+
return { changedInstanceIds, deletionBatches };
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Remaps tracked elements, adds custom changes, and remaps deleted source entities to their targets using the
|
|
1467
|
+
* deletion batches read for the changeset ranges being processed.
|
|
1468
|
+
*/
|
|
1469
|
+
async prepareSourceChanges() {
|
|
1443
1470
|
await this._provenanceManager.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1444
1471
|
this.context.remapElement(sourceElementId, targetElementId);
|
|
1445
1472
|
});
|
|
1446
1473
|
if (this.exporter.sourceDbChanges)
|
|
1447
1474
|
await this.addCustomChanges(this.exporter.sourceDbChanges);
|
|
1448
|
-
const
|
|
1449
|
-
if (
|
|
1475
|
+
const ranges = this._changesetRanges;
|
|
1476
|
+
if (ranges === undefined || ranges.length === 0) {
|
|
1450
1477
|
if (this.exporter.sourceDbChanges === undefined ||
|
|
1451
1478
|
!this.exporter.sourceDbChanges.hasChanges)
|
|
1452
1479
|
return;
|
|
@@ -1454,21 +1481,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1454
1481
|
if (this._sourceChangeDataState === "no-changes")
|
|
1455
1482
|
this._sourceChangeDataState = "has-changes";
|
|
1456
1483
|
}
|
|
1457
|
-
const
|
|
1458
|
-
if (
|
|
1484
|
+
const deletionBatches = this._deletionBatches;
|
|
1485
|
+
if (deletionBatches === undefined)
|
|
1459
1486
|
return;
|
|
1460
|
-
const relationshipECClassIdsToSkip = new Set();
|
|
1461
|
-
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)", undefined, { usePrimaryConn: true })) {
|
|
1462
|
-
relationshipECClassIdsToSkip.add(row.ECInstanceId);
|
|
1463
|
-
}
|
|
1464
|
-
const relationshipECClassIds = new Set();
|
|
1465
|
-
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)", undefined, { usePrimaryConn: true })) {
|
|
1466
|
-
relationshipECClassIds.add(row.ECInstanceId);
|
|
1467
|
-
}
|
|
1468
|
-
const elementAspectECClassIds = new Set();
|
|
1469
|
-
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementAspect)", undefined, { usePrimaryConn: true })) {
|
|
1470
|
-
elementAspectECClassIds.add(row.ECInstanceId);
|
|
1471
|
-
}
|
|
1472
1487
|
// For later use when processing deletes.
|
|
1473
1488
|
const alreadyImportedElementInserts = new Set();
|
|
1474
1489
|
const alreadyImportedModelInserts = new Set();
|
|
@@ -1485,30 +1500,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1485
1500
|
this._deletedSourceRelationshipData = new Map();
|
|
1486
1501
|
this._pendingRelationshipDeleteTargets.clear();
|
|
1487
1502
|
this._reimportedTargetRelationshipIds.clear();
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
const
|
|
1492
|
-
for (const
|
|
1493
|
-
if (
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
change.scopeId === this.targetScopeElementId &&
|
|
1497
|
-
change.kind === core_backend_1.ExternalSourceAspect.Kind.Element &&
|
|
1498
|
-
change.elementId !== undefined) {
|
|
1499
|
-
elemIdToScopeEsa.set(change.elementId, change);
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
// Loop to process deletes.
|
|
1503
|
-
for (const change of changes) {
|
|
1504
|
-
if (relationshipECClassIdsToSkip.has(change.ecClassId))
|
|
1505
|
-
continue;
|
|
1506
|
-
if (isElementAspectDeletion(change))
|
|
1507
|
-
continue;
|
|
1508
|
-
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(change.ecClassId), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1503
|
+
for (const batch of deletionBatches) {
|
|
1504
|
+
// Deletion records can only resolve targets within their own batch, so these lookups are rebuilt for each batch.
|
|
1505
|
+
// They cover elements and scope ESAs deleted in the source, which can no longer be read from it.
|
|
1506
|
+
const deletedScopeEsaByElementId = new Map();
|
|
1507
|
+
for (const aspect of batch.externalSourceAspects) {
|
|
1508
|
+
if (aspect.scopeId === this.targetScopeElementId &&
|
|
1509
|
+
aspect.kind === core_backend_1.ExternalSourceAspect.Kind.Element)
|
|
1510
|
+
deletedScopeEsaByElementId.set(aspect.elementId, aspect);
|
|
1509
1511
|
}
|
|
1512
|
+
const deletedFederationGuidByElementId = new Map();
|
|
1513
|
+
for (const element of batch.elements)
|
|
1514
|
+
if (element.federationGuid)
|
|
1515
|
+
deletedFederationGuidByElementId.set(element.ecInstanceId, element.federationGuid);
|
|
1516
|
+
for (const relationship of batch.relationships)
|
|
1517
|
+
await this.processDeletedRelationship(relationship, deletedScopeEsaByElementId, deletedFederationGuidByElementId);
|
|
1518
|
+
for (const element of batch.elements)
|
|
1519
|
+
await this.processDeletedElement(element.ecInstanceId, element.federationGuid, deletedScopeEsaByElementId, alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1520
|
+
for (const model of batch.models)
|
|
1521
|
+
await this.processDeletedElement(model.ecInstanceId, undefined, deletedScopeEsaByElementId, alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1510
1522
|
}
|
|
1511
|
-
return;
|
|
1512
1523
|
}
|
|
1513
1524
|
/**
|
|
1514
1525
|
* This will be called when transformer is called with [[IModelTransformOptions.argsForProcessChanges]] to process changes.
|
|
@@ -1520,141 +1531,110 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1520
1531
|
*/
|
|
1521
1532
|
async addCustomChanges(_sourceDbChanges) { }
|
|
1522
1533
|
/**
|
|
1523
|
-
*
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1527
|
-
* @param
|
|
1528
|
-
* @param
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
*/
|
|
1532
|
-
async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1533
|
-
// we need a connected iModel with changes to remap elements with deletions
|
|
1534
|
-
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1535
|
-
const noChanges = (await this.getSynchronizationVersion()).index ===
|
|
1536
|
-
this.sourceDb.changeset.index &&
|
|
1537
|
-
(this.exporter.sourceDbChanges === undefined ||
|
|
1538
|
-
!this.exporter.sourceDbChanges.hasChanges);
|
|
1539
|
-
if (notConnectedModel || noChanges)
|
|
1540
|
-
return;
|
|
1534
|
+
* Finds the target ID of a source element while processing deletions, first through the transformation's
|
|
1535
|
+
* ExternalSourceAspects when the source holds the provenance, then through the federation GUID. The element may be
|
|
1536
|
+
* deleted itself, or be a surviving endpoint of a deleted relationship.
|
|
1537
|
+
* @param sourceId the source element ID.
|
|
1538
|
+
* @param federationGuid the source element's federation GUID, if known.
|
|
1539
|
+
* @param deletedScopeEsaByElementId this transformation scope's deleted ESAs in the same batch, keyed by the ID of the element that owned each one.
|
|
1540
|
+
*/
|
|
1541
|
+
async findTargetElementIdForDeletion(sourceId, federationGuid, deletedScopeEsaByElementId) {
|
|
1541
1542
|
/**
|
|
1542
1543
|
* If the deleted source entity is in the provenanceDb, then we can use its ids to query for ESAs.
|
|
1543
1544
|
* This is because the ESAs are stored on an element Id thats present in the provenanceDb.
|
|
1544
1545
|
*/
|
|
1545
|
-
|
|
1546
|
-
const getTargetIdFromSourceId = async (id) => {
|
|
1546
|
+
if (this.sourceDb === (await this.getProvenanceDb())) {
|
|
1547
1547
|
let identifierValue;
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
if (changeDataInProvenanceDb) {
|
|
1556
|
-
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
1557
|
-
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([
|
|
1558
|
-
this.targetScopeElementId,
|
|
1559
|
-
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1560
|
-
id,
|
|
1561
|
-
]), { usePrimaryConn: true })) {
|
|
1562
|
-
identifierValue = row.Identifier;
|
|
1563
|
-
}
|
|
1564
|
-
identifierValue =
|
|
1565
|
-
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.identifier;
|
|
1548
|
+
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
1549
|
+
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([
|
|
1550
|
+
this.targetScopeElementId,
|
|
1551
|
+
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1552
|
+
sourceId,
|
|
1553
|
+
]), { usePrimaryConn: true })) {
|
|
1554
|
+
identifierValue = row.Identifier;
|
|
1566
1555
|
}
|
|
1556
|
+
identifierValue =
|
|
1557
|
+
identifierValue ?? deletedScopeEsaByElementId.get(sourceId)?.identifier;
|
|
1567
1558
|
// Check for targetId by an esa first
|
|
1568
|
-
if (
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
else if (this.sourceDb ===
|
|
1616
|
-
(await this._provenanceManager.getProvenanceSourceDb())) {
|
|
1617
|
-
const relProvenance = await this._provenanceManager.queryProvenanceForRelationship(changedInstanceId, {
|
|
1618
|
-
classFullName: classFullName ?? "",
|
|
1619
|
-
sourceId: sourceIdOfRelationshipInSource,
|
|
1620
|
-
targetId: targetIdOfRelationshipInSource,
|
|
1559
|
+
if (identifierValue)
|
|
1560
|
+
return identifierValue;
|
|
1561
|
+
}
|
|
1562
|
+
// Check for targetId using sourceId's fedguid if we didn't find an esa.
|
|
1563
|
+
if (federationGuid)
|
|
1564
|
+
return this.targetDb.elements.getIdFromFederationGuid(federationGuid);
|
|
1565
|
+
return undefined;
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Helper function for [[prepareSourceChanges]]. Records how to find the target relationship of a deleted source relationship.
|
|
1569
|
+
* @param deletion the deleted source relationship.
|
|
1570
|
+
* @param deletedScopeEsaByElementId this transformation scope's deleted ESAs in the same batch, keyed by the ID of the element that owned each one.
|
|
1571
|
+
* @param deletedFederationGuidByElementId federation GUIDs of the elements deleted in the same batch, for endpoints that no longer exist in the source.
|
|
1572
|
+
*/
|
|
1573
|
+
async processDeletedRelationship(deletion, deletedScopeEsaByElementId, deletedFederationGuidByElementId) {
|
|
1574
|
+
const { ecInstanceId } = deletion;
|
|
1575
|
+
const classFullName = this.sourceDb.getClassNameFromId(deletion.ecClassId);
|
|
1576
|
+
const getEndpointInTarget = async (sourceId) => this.findTargetElementIdForDeletion(sourceId, this.sourceDb.elements.tryGetElement(sourceId)?.federationGuid ??
|
|
1577
|
+
deletedFederationGuidByElementId.get(sourceId), deletedScopeEsaByElementId);
|
|
1578
|
+
const sourceIdInTarget = await getEndpointInTarget(deletion.sourceECInstanceId);
|
|
1579
|
+
const targetIdInTarget = await getEndpointInTarget(deletion.targetECInstanceId);
|
|
1580
|
+
const isProvenanceSource = this.sourceDb === (await this._provenanceManager.getProvenanceSourceDb());
|
|
1581
|
+
const queryRelationshipProvenance = async () => this._provenanceManager.queryProvenanceForRelationship(ecInstanceId, {
|
|
1582
|
+
classFullName,
|
|
1583
|
+
sourceId: deletion.sourceECInstanceId,
|
|
1584
|
+
targetId: deletion.targetECInstanceId,
|
|
1585
|
+
});
|
|
1586
|
+
if (sourceIdInTarget && targetIdInTarget) {
|
|
1587
|
+
// relationships with ESA provenance must have their aspect deleted too, even when the target relationship is found by endpoints
|
|
1588
|
+
const provenanceAspectId = this._options.forceExternalSourceAspectProvenance && isProvenanceSource
|
|
1589
|
+
? (await queryRelationshipProvenance())?.aspectId
|
|
1590
|
+
: undefined;
|
|
1591
|
+
this._deletedSourceRelationshipData?.set(ecInstanceId, {
|
|
1592
|
+
classFullName,
|
|
1593
|
+
sourceIdInTarget,
|
|
1594
|
+
targetIdInTarget,
|
|
1595
|
+
provenanceAspectId,
|
|
1596
|
+
});
|
|
1597
|
+
this._pendingRelationshipDeleteTargets.add(relationshipEndpointsKey(sourceIdInTarget, targetIdInTarget));
|
|
1598
|
+
}
|
|
1599
|
+
else if (isProvenanceSource) {
|
|
1600
|
+
const relProvenance = await queryRelationshipProvenance();
|
|
1601
|
+
if (relProvenance && relProvenance.relationshipId) {
|
|
1602
|
+
this._deletedSourceRelationshipData?.set(ecInstanceId, {
|
|
1603
|
+
classFullName,
|
|
1604
|
+
relId: relProvenance.relationshipId,
|
|
1605
|
+
provenanceAspectId: relProvenance.aspectId,
|
|
1621
1606
|
});
|
|
1622
|
-
|
|
1623
|
-
this._deletedSourceRelationshipData?.set(changedInstanceId, {
|
|
1624
|
-
classFullName: classFullName ?? "",
|
|
1625
|
-
relId: relProvenance.relationshipId,
|
|
1626
|
-
provenanceAspectId: relProvenance.aspectId,
|
|
1627
|
-
});
|
|
1628
|
-
this._pendingRelationshipDeleteTargets.add(relProvenance.relationshipId);
|
|
1629
|
-
}
|
|
1607
|
+
this._pendingRelationshipDeleteTargets.add(relProvenance.relationshipId);
|
|
1630
1608
|
}
|
|
1631
1609
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
if (
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Helper function for [[prepareSourceChanges]]. Remaps a deleted source element or model to its target.
|
|
1613
|
+
* @param sourceId the deleted source element or model.
|
|
1614
|
+
* @param federationGuid the deleted source element's federation GUID, if it had one. Undefined for models.
|
|
1615
|
+
* @param deletedScopeEsaByElementId this transformation scope's deleted ESAs in the same batch, keyed by the ID of the element that owned each one.
|
|
1616
|
+
* @param alreadyImportedElementInserts used to handle entity recreation and not delete already handled element inserts.
|
|
1617
|
+
* @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
|
|
1618
|
+
*/
|
|
1619
|
+
async processDeletedElement(sourceId, federationGuid, deletedScopeEsaByElementId, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1620
|
+
let targetId = await this.findTargetElementIdForDeletion(sourceId, federationGuid, deletedScopeEsaByElementId);
|
|
1621
|
+
if (targetId === undefined &&
|
|
1622
|
+
this.sourceDb === (await this._provenanceManager.getProvenanceSourceDb())) {
|
|
1623
|
+
const contextTargetId = this.context.findTargetElementId(sourceId);
|
|
1624
|
+
if (core_bentley_1.Id64.isValidId64(contextTargetId))
|
|
1625
|
+
targetId = contextTargetId;
|
|
1626
|
+
}
|
|
1627
|
+
// Entities inserted and deleted since the last synchronization were never synced and can be safely ignored.
|
|
1628
|
+
if (!targetId)
|
|
1629
|
+
return;
|
|
1630
|
+
this.context.remapElement(sourceId, targetId);
|
|
1631
|
+
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
1632
|
+
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
1633
|
+
if (alreadyImportedElementInserts.has(targetId)) {
|
|
1634
|
+
this.exporter.sourceDbChanges?.element.deleteIds.delete(sourceId);
|
|
1635
|
+
}
|
|
1636
|
+
if (alreadyImportedModelInserts.has(targetId)) {
|
|
1637
|
+
this.exporter.sourceDbChanges?.model.deleteIds.delete(sourceId);
|
|
1658
1638
|
}
|
|
1659
1639
|
}
|
|
1660
1640
|
async _tryInitChangesetData(args) {
|
|
@@ -1668,7 +1648,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1668
1648
|
const noChanges = syncVersion.index === this.sourceDb.changeset.index;
|
|
1669
1649
|
if (noChanges) {
|
|
1670
1650
|
this._sourceChangeDataState = "no-changes";
|
|
1671
|
-
this.
|
|
1651
|
+
this._changesetRanges = [];
|
|
1672
1652
|
return;
|
|
1673
1653
|
}
|
|
1674
1654
|
const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
|
|
@@ -1705,20 +1685,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1705
1685
|
core_bentley_1.Logger.logTrace(loggerCategory, `changesets to skip: ${changesetsToSkip}`);
|
|
1706
1686
|
this._changesetRanges = (0, Algo_1.rangesFromRangeAndSkipped)(startChangesetIndex, endChangesetIndex, changesetsToSkip);
|
|
1707
1687
|
core_bentley_1.Logger.logTrace(loggerCategory, `ranges: ${this._changesetRanges}`);
|
|
1708
|
-
|
|
1709
|
-
for (const [first, end] of this._changesetRanges) {
|
|
1710
|
-
// TODO: should the first changeset in a reverse sync really be included even though its 'initialized branch provenance'? The answer is no, its a bug that needs to be fixed.
|
|
1711
|
-
const fileProps = await core_backend_1.BriefcaseManager.downloadChangesets({
|
|
1712
|
-
iModelId: this.sourceDb.iModelId,
|
|
1713
|
-
targetDir: core_backend_1.BriefcaseManager.getChangeSetsPath(this.sourceDb.iModelId),
|
|
1714
|
-
range: { first, end },
|
|
1715
|
-
});
|
|
1716
|
-
csFileProps.push(...fileProps);
|
|
1717
|
-
}
|
|
1718
|
-
this._csFileProps = csFileProps;
|
|
1719
|
-
/** Theres a possibility that our csFileProps length is still 0 here, since we skip cs indices found in the pendingSync and pendingReverseSync indices arrays. */
|
|
1688
|
+
/** Skipping indices found in the pendingSync and pendingReverseSync arrays can leave no ranges to process. */
|
|
1720
1689
|
this._sourceChangeDataState =
|
|
1721
|
-
this.
|
|
1690
|
+
this._changesetRanges.length === 0 ? "no-changes" : "has-changes";
|
|
1722
1691
|
}
|
|
1723
1692
|
/** Asserts that the EditTxn is active before any write operations. */
|
|
1724
1693
|
assertEditTxnActive() {
|
|
@@ -1844,17 +1813,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1844
1813
|
const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
|
|
1845
1814
|
return {
|
|
1846
1815
|
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
1847
|
-
...(this.
|
|
1848
|
-
? {
|
|
1849
|
-
:
|
|
1850
|
-
? {
|
|
1851
|
-
:
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
},
|
|
1857
|
-
}),
|
|
1816
|
+
...(this._changesetRanges
|
|
1817
|
+
? { changesetRanges: this._changesetRanges }
|
|
1818
|
+
: startChangeset
|
|
1819
|
+
? { startChangeset }
|
|
1820
|
+
: {
|
|
1821
|
+
startChangeset: {
|
|
1822
|
+
index: (await this.getSynchronizationVersion()).index + 1,
|
|
1823
|
+
},
|
|
1824
|
+
}),
|
|
1858
1825
|
};
|
|
1859
1826
|
}
|
|
1860
1827
|
/** Combine an array of source elements into a single target element.
|