@itwin/imodel-transformer 2.0.0-dev.29 → 2.0.0-dev.30

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.
@@ -24,6 +24,7 @@ const EntityUnifier_1 = require("./EntityUnifier");
24
24
  const Algo_1 = require("./Algo");
25
25
  const SyncTypeResolver_1 = require("./SyncTypeResolver");
26
26
  const ProvenanceManager_1 = require("./ProvenanceManager");
27
+ const ChangesetScanner_1 = require("./ChangesetScanner");
27
28
  const IModelTransformerError_1 = require("./IModelTransformerError");
28
29
  const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelTransformer;
29
30
  /**
@@ -1287,6 +1288,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1287
1288
  _sourceChangeDataState = "uninited";
1288
1289
  /** length === 0 when _changeDataState = "no-change", length > 0 means "has-changes", otherwise undefined */
1289
1290
  _csFileProps = undefined;
1291
+ _deletionRecordsByChangeset;
1290
1292
  /**
1291
1293
  * Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
1292
1294
  * 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.
@@ -1300,12 +1302,33 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1300
1302
  await this.initScopeProvenance();
1301
1303
  await this._tryInitChangesetData(this._options.argsForProcessChanges);
1302
1304
  await this.context.initialize();
1303
- // need exporter initialized to do remapdeletedsourceentities.
1304
- await this.exporter.initialize(await this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
1305
+ const exporterInitOptions = await this.getExportInitOpts(this._options.argsForProcessChanges ?? {});
1306
+ await this.initializeChangesetScanAndExporter(exporterInitOptions);
1305
1307
  // Exporter must be initialized prior to processing changesets in order to properly handle entity recreations (an entity delete followed by an insert of that same entity).
1306
1308
  await this.processChangesets();
1307
1309
  this._initialized = true;
1308
1310
  }
1311
+ async initializeChangesetScanAndExporter(exporterInitOptions) {
1312
+ if (this._csFileProps !== undefined &&
1313
+ this._csFileProps.length > 0 &&
1314
+ this.sourceDb.isBriefcaseDb()) {
1315
+ const changedInstanceIds = this.exporter.sourceDbChanges ??
1316
+ ("changedInstanceIds" in exporterInitOptions
1317
+ ? exporterInitOptions.changedInstanceIds
1318
+ : new IModelExporter_1.ChangedInstanceIds(this.sourceDb));
1319
+ this._deletionRecordsByChangeset = await ChangesetScanner_1.ChangesetScanner.scan(this.sourceDb, this._csFileProps, changedInstanceIds, {
1320
+ populateChangedInstanceIds: this.exporter.sourceDbChanges === undefined &&
1321
+ !("changedInstanceIds" in exporterInitOptions),
1322
+ });
1323
+ await this.exporter.initialize({
1324
+ changedInstanceIds,
1325
+ skipPropagateChangesToRootElements: exporterInitOptions.skipPropagateChangesToRootElements,
1326
+ });
1327
+ }
1328
+ else {
1329
+ await this.exporter.initialize(exporterInitOptions);
1330
+ }
1331
+ }
1309
1332
  /**
1310
1333
  * Reads all the changeset files in the private member of the transformer: _csFileProps
1311
1334
  * and finds the corresponding target entity for any deleted source entities and remaps the sourceId to the targetId.
@@ -1318,7 +1341,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1318
1341
  });
1319
1342
  if (this.exporter.sourceDbChanges)
1320
1343
  await this.addCustomChanges(this.exporter.sourceDbChanges);
1321
- if (this._csFileProps === undefined || this._csFileProps.length === 0) {
1344
+ const csFileProps = this._csFileProps;
1345
+ if (csFileProps === undefined || csFileProps.length === 0) {
1322
1346
  if (this.exporter.sourceDbChanges === undefined ||
1323
1347
  !this.exporter.sourceDbChanges.hasChanges)
1324
1348
  return;
@@ -1326,6 +1350,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1326
1350
  if (this._sourceChangeDataState === "no-changes")
1327
1351
  this._sourceChangeDataState = "has-changes";
1328
1352
  }
1353
+ const deletionRecordsByChangeset = this._deletionRecordsByChangeset;
1354
+ if (deletionRecordsByChangeset === undefined)
1355
+ return;
1329
1356
  const relationshipECClassIdsToSkip = new Set();
1330
1357
  for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)", undefined, { usePrimaryConn: true })) {
1331
1358
  relationshipECClassIdsToSkip.add(row.ECInstanceId);
@@ -1348,63 +1375,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1348
1375
  alreadyImportedModelInserts.add(targetModelId);
1349
1376
  });
1350
1377
  this._deletedSourceRelationshipData = new Map();
1351
- for (const csFile of this._csFileProps ?? []) {
1352
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1353
- const csReader = core_backend_1.SqliteChangesetReader.openFile({
1354
- fileName: csFile.pathname,
1355
- db: this.sourceDb,
1356
- disableSchemaCheck: true,
1357
- });
1358
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1359
- const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
1360
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1361
- const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier(this.sourceDb);
1362
- while (csAdaptor.step()) {
1363
- ecChangeUnifier.appendFrom(csAdaptor);
1364
- }
1365
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1366
- const changes = [...ecChangeUnifier.instances];
1378
+ for (const changes of deletionRecordsByChangeset) {
1367
1379
  /** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
1368
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1369
1380
  const elemIdToScopeEsa = new Map();
1370
1381
  for (const change of changes) {
1371
- if (change.ECClassId !== undefined &&
1372
- relationshipECClassIdsToSkip.has(change.ECClassId))
1382
+ if (relationshipECClassIdsToSkip.has(change.ecClassId))
1373
1383
  continue;
1374
- const changeType = change.$meta?.op;
1375
- if (changeType === "Deleted" &&
1376
- change?.$meta?.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
1377
- change.Scope.Id === this.targetScopeElementId &&
1378
- change.Kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
1379
- elemIdToScopeEsa.set(change.Element.Id, change);
1384
+ if (change.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
1385
+ change.scopeId === this.targetScopeElementId &&
1386
+ change.kind === core_backend_1.ExternalSourceAspect.Kind.Element &&
1387
+ change.elementId !== undefined) {
1388
+ elemIdToScopeEsa.set(change.elementId, change);
1380
1389
  }
1381
1390
  }
1382
1391
  // Loop to process deletes.
1383
1392
  for (const change of changes) {
1384
- const changeType = change.$meta?.op;
1385
- const ecClassId = change.ECClassId ?? change.$meta?.fallbackClassId;
1386
- if (ecClassId === undefined)
1387
- core_bentley_1.ITwinError.throwError({
1388
- iTwinErrorId: {
1389
- scope: IModelTransformerError_1.IModelTransformerErrorScope,
1390
- key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
1391
- },
1392
- message: `ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change?.$meta?.tables}`,
1393
- });
1394
- if (changeType === undefined)
1395
- core_bentley_1.ITwinError.throwError({
1396
- iTwinErrorId: {
1397
- scope: IModelTransformerError_1.IModelTransformerErrorScope,
1398
- key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
1399
- },
1400
- message: `ChangeType was undefined for id: ${change.ECInstanceId}.`,
1401
- });
1402
- if (changeType !== "Deleted" ||
1403
- relationshipECClassIdsToSkip.has(ecClassId))
1393
+ if (relationshipECClassIdsToSkip.has(change.ecClassId))
1404
1394
  continue;
1405
- await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId ?? ""), alreadyImportedElementInserts, alreadyImportedModelInserts);
1395
+ await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(change.ecClassId), alreadyImportedElementInserts, alreadyImportedModelInserts);
1406
1396
  }
1407
- csReader.close();
1408
1397
  }
1409
1398
  return;
1410
1399
  }
@@ -1427,11 +1416,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1427
1416
  * @param alreadyImportedModelInserts used to handle entity recreation and not delete already handled model inserts.
1428
1417
  * @returns void
1429
1418
  */
1430
- async processDeletedOp(
1431
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1432
- change,
1433
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1434
- mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
1419
+ async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
1435
1420
  // we need a connected iModel with changes to remap elements with deletions
1436
1421
  const notConnectedModel = this.sourceDb.iTwinId === undefined;
1437
1422
  const noChanges = (await this.getSynchronizationVersion()).index ===
@@ -1441,7 +1426,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1441
1426
  if (notConnectedModel || noChanges)
1442
1427
  return;
1443
1428
  /**
1444
- * if our ChangedECInstance is in the provenanceDb, then we can use the ids we find in the ChangedECInstance to query for ESAs.
1429
+ * If the deleted source entity is in the provenanceDb, then we can use its ids to query for ESAs.
1445
1430
  * This is because the ESAs are stored on an element Id thats present in the provenanceDb.
1446
1431
  */
1447
1432
  const changeDataInProvenanceDb = this.sourceDb === (await this.getProvenanceDb());
@@ -1453,7 +1438,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1453
1438
  }
1454
1439
  const fedGuid = isRelationship
1455
1440
  ? element?.federationGuid
1456
- : change.FederationGuid;
1441
+ : change.federationGuid;
1457
1442
  if (changeDataInProvenanceDb) {
1458
1443
  // TODO: clarify what happens if there are multiple (e.g. elements were merged)
1459
1444
  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([
@@ -1464,7 +1449,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1464
1449
  identifierValue = row.Identifier;
1465
1450
  }
1466
1451
  identifierValue =
1467
- identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.Identifier;
1452
+ identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.identifier;
1468
1453
  }
1469
1454
  // Check for targetId by an esa first
1470
1455
  if (changeDataInProvenanceDb && identifierValue) {
@@ -1478,11 +1463,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1478
1463
  }
1479
1464
  return undefined;
1480
1465
  };
1481
- const changedInstanceId = change.ECInstanceId;
1466
+ const changedInstanceId = change.ecInstanceId;
1482
1467
  if (isRelationship) {
1483
- const sourceIdOfRelationshipInSource = change.SourceECInstanceId;
1484
- const targetIdOfRelationshipInSource = change.TargetECInstanceId;
1485
- const classFullName = change.$meta?.classFullName;
1468
+ const sourceIdOfRelationshipInSource = change.sourceECInstanceId;
1469
+ const targetIdOfRelationshipInSource = change.targetECInstanceId;
1470
+ const classFullName = change.classFullName;
1471
+ if (sourceIdOfRelationshipInSource === undefined ||
1472
+ targetIdOfRelationshipInSource === undefined) {
1473
+ core_bentley_1.ITwinError.throwError({
1474
+ iTwinErrorId: {
1475
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
1476
+ key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
1477
+ },
1478
+ message: `Relationship deletion ${changedInstanceId} is missing an endpoint.`,
1479
+ });
1480
+ }
1486
1481
  const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
1487
1482
  const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
1488
1483
  if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
@@ -1711,6 +1706,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1711
1706
  async getExportInitOpts(opts) {
1712
1707
  if (!this._options.argsForProcessChanges)
1713
1708
  return {};
1709
+ if ("changedInstanceIds" in opts) {
1710
+ return {
1711
+ changedInstanceIds: opts.changedInstanceIds,
1712
+ skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
1713
+ };
1714
+ }
1714
1715
  const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
1715
1716
  return {
1716
1717
  skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,