@itwin/imodel-transformer 0.1.8-fedguidopt.0 → 0.1.8

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TemplateModelCloner = exports.IModelTransformer = exports.TransformerEvent = void 0;
3
+ exports.TemplateModelCloner = exports.IModelTransformer = void 0;
4
4
  /*---------------------------------------------------------------------------------------------
5
5
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
6
6
  * See LICENSE.md in the project root for license terms and full copyright notice.
@@ -9,7 +9,6 @@ exports.TemplateModelCloner = exports.IModelTransformer = exports.TransformerEve
9
9
  * @module iModels
10
10
  */
11
11
  const path = require("path");
12
- const events_1 = require("events");
13
12
  const Semver = require("semver");
14
13
  const nodeAssert = require("assert");
15
14
  const core_bentley_1 = require("@itwin/core-bentley");
@@ -86,16 +85,6 @@ function mapId64(idContainer, func) {
86
85
  }
87
86
  return results;
88
87
  }
89
- /** events that the transformer emits, e.g. for signaling profilers @internal */
90
- var TransformerEvent;
91
- (function (TransformerEvent) {
92
- TransformerEvent["beginProcessSchemas"] = "beginProcessSchemas";
93
- TransformerEvent["endProcessSchemas"] = "endProcessSchemas";
94
- TransformerEvent["beginProcessAll"] = "beginProcessAll";
95
- TransformerEvent["endProcessAll"] = "endProcessAll";
96
- TransformerEvent["beginProcessChanges"] = "beginProcessChanges";
97
- TransformerEvent["endProcessChanges"] = "endProcessChanges";
98
- })(TransformerEvent = exports.TransformerEvent || (exports.TransformerEvent = {}));
99
88
  /** Base class used to transform a source iModel into a different target iModel.
100
89
  * @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md), [IModelExporter]($transformer), [IModelImporter]($transformer)
101
90
  * @beta
@@ -125,26 +114,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
125
114
  this._pendingReferences = new PendingReferenceMap_1.PendingReferenceMap();
126
115
  /** map of partially committed entities to their partial commit progress */
127
116
  this._partiallyCommittedEntities = new EntityMap_1.EntityMap();
128
- /**
129
- * Internal event emitter that is used by the transformer to signal events to profilers
130
- * @internal
131
- */
132
- this.events = new events_1.EventEmitter();
133
- this._targetScopeProvenanceProps = undefined;
134
- this._cachedTargetScopeVersion = undefined;
135
- // if undefined, it can be initialized by calling [[this._cacheSourceChanges]]
136
- this._hasElementChangedCache = undefined;
137
- this._deletedSourceRelationshipData = undefined;
138
117
  this._yieldManager = new core_bentley_1.YieldManager();
139
118
  /** The directory where schemas will be exported, a random temporary directory */
140
119
  this._schemaExportDir = path.join(core_backend_1.KnownLocations.tmpdir, core_bentley_1.Guid.createValue());
141
120
  this._longNamedSchemasMap = new Map();
142
121
  /** state to prevent reinitialization, @see [[initialize]] */
143
122
  this._initialized = false;
144
- /** length === 0 when _changeDataState = "no-change", length > 0 means "has-changes", otherwise undefined */
145
- this._changeSummaryIds = undefined;
146
- this._changeDataState = "uninited";
147
- /** previous provenance, either a federation guid, a `${sourceFedGuid}/${targetFedGuid}` pair, or required aspect props */
148
123
  this._lastProvenanceEntityInfo = nullLastProvenanceEntityInfo;
149
124
  // initialize IModelTransformOptions
150
125
  this._options = {
@@ -192,16 +167,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
192
167
  this.targetDb = this.importer.targetDb;
193
168
  // create the IModelCloneContext, it must be initialized later
194
169
  this.context = new IModelCloneContext_1.IModelCloneContext(this.sourceDb, this.targetDb);
195
- this._registerEvents();
196
- }
197
- /** @internal */
198
- _registerEvents() {
199
- this.events.on(TransformerEvent.beginProcessAll, () => {
200
- core_bentley_1.Logger.logTrace(loggerCategory, "processAll()");
201
- });
202
- this.events.on(TransformerEvent.beginProcessChanges, () => {
203
- core_bentley_1.Logger.logTrace(loggerCategory, "processChanges()");
204
- });
205
170
  }
206
171
  /** Dispose any native resources associated with this IModelTransformer. */
207
172
  dispose() {
@@ -230,12 +195,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
230
195
  get provenanceDb() {
231
196
  return this._options.isReverseSynchronization ? this.sourceDb : this.targetDb;
232
197
  }
233
- /** Return the IModelDb where IModelTransformer will NOT store its provenance.
234
- * @note This will be [[sourceDb]] except when it is a reverse synchronization. In that case it be [[targetDb]].
235
- */
236
- get provenanceSourceDb() {
237
- return this._options.isReverseSynchronization ? this.targetDb : this.sourceDb;
238
- }
198
+ /** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
239
199
  initElementProvenance(sourceElementId, targetElementId) {
240
200
  const elementId = this._options.isReverseSynchronization ? sourceElementId : targetElementId;
241
201
  const aspectIdentifier = this._options.isReverseSynchronization ? targetElementId : sourceElementId;
@@ -266,30 +226,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
266
226
  kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
267
227
  jsonProperties: JSON.stringify({ targetRelInstanceId }),
268
228
  };
269
- [aspectProps.id] = this.queryScopeExternalSource(aspectProps);
229
+ aspectProps.id = this.queryExternalSourceAspectId(aspectProps);
270
230
  return aspectProps;
271
231
  }
272
- /** the changeset in the scoping element's source version found for this transformation
273
- * @note: empty string and -1 for changeset and index if it has never been transformed
274
- */
275
- get _targetScopeVersion() {
276
- if (!this._cachedTargetScopeVersion) {
277
- nodeAssert(this._targetScopeProvenanceProps?.version !== undefined, "_targetScopeProvenanceProps was not set yet, or contains no version");
278
- const [id, index] = this._targetScopeProvenanceProps.version === ""
279
- ? ["", -1]
280
- : this._targetScopeProvenanceProps.version.split(";");
281
- this._cachedTargetScopeVersion = { index: Number(index), id, };
282
- nodeAssert(!Number.isNaN(this._cachedTargetScopeVersion.index), "bad parse: invalid index in version");
283
- }
284
- return this._cachedTargetScopeVersion;
285
- }
286
- /**
287
- * Make sure there are no conflicting other scope-type external source aspects on the *target scope element*,
288
- * If there are none at all, insert one, then this must be a first synchronization.
289
- * @returns the last synced version (changesetId) on the target scope's external source aspect,
290
- * (if this was a [BriefcaseDb]($backend))
291
- */
292
- initScopeProvenance() {
232
+ validateScopeProvenance() {
293
233
  const aspectProps = {
294
234
  classFullName: core_backend_1.ExternalSourceAspect.classFullName,
295
235
  element: { id: this.targetScopeElementId, relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName },
@@ -297,21 +237,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
297
237
  identifier: this._options.isReverseSynchronization ? this.targetDb.iModelId : this.sourceDb.iModelId,
298
238
  kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
299
239
  };
300
- // FIXME: handle older transformed iModels
301
- let version;
302
- [aspectProps.id, version] = this.queryScopeExternalSource(aspectProps) ?? []; // this query includes "identifier"
303
- aspectProps.version = version;
240
+ aspectProps.id = this.queryExternalSourceAspectId(aspectProps); // this query includes "identifier"
304
241
  if (undefined === aspectProps.id) {
305
- aspectProps.version = ""; // empty since never before transformed. Will be updated in [[finalizeTransformation]]
306
242
  // this query does not include "identifier" to find possible conflicts
307
- const sql = `
308
- SELECT ECInstanceId
309
- FROM ${core_backend_1.ExternalSourceAspect.classFullName}
310
- WHERE Element.Id=:elementId
311
- AND Scope.Id=:scopeId
312
- AND Kind=:kind
313
- LIMIT 1
314
- `;
243
+ const sql = `SELECT ECInstanceId FROM ${core_backend_1.ExternalSourceAspect.classFullName} WHERE Element.Id=:elementId AND Scope.Id=:scopeId AND Kind=:kind LIMIT 1`;
315
244
  const hasConflictingScope = this.provenanceDb.withPreparedStatement(sql, (statement) => {
316
245
  statement.bindId("elementId", aspectProps.element.id);
317
246
  statement.bindId("scopeId", aspectProps.scope.id); // this scope.id can never be invalid, we create it above
@@ -326,97 +255,39 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
326
255
  this._isFirstSynchronization = true; // couldn't tell this is the first time without provenance
327
256
  }
328
257
  }
329
- this._targetScopeProvenanceProps = aspectProps;
330
- }
331
- queryScopeExternalSource(aspectProps) {
332
- const sql = `
333
- SELECT ECInstanceId, Version
334
- FROM ${core_backend_1.ExternalSourceAspect.classFullName}
335
- WHERE Element.Id=:elementId
336
- AND Scope.Id=:scopeId
337
- AND Kind=:kind
338
- AND Identifier=:identifier
339
- LIMIT 1
340
- `;
258
+ }
259
+ queryExternalSourceAspectId(aspectProps) {
260
+ const sql = `SELECT ECInstanceId FROM ${core_backend_1.ExternalSourceAspect.classFullName} WHERE Element.Id=:elementId AND Scope.Id=:scopeId AND Kind=:kind AND Identifier=:identifier LIMIT 1`;
341
261
  return this.provenanceDb.withPreparedStatement(sql, (statement) => {
342
262
  statement.bindId("elementId", aspectProps.element.id);
343
263
  if (aspectProps.scope === undefined)
344
- return [undefined, undefined]; // return undefined instead of binding an invalid id
264
+ return undefined; // return undefined instead of binding an invalid id
345
265
  statement.bindId("scopeId", aspectProps.scope.id);
346
266
  statement.bindString("kind", aspectProps.kind);
347
267
  statement.bindString("identifier", aspectProps.identifier);
348
- if (core_bentley_1.DbResult.BE_SQLITE_ROW !== statement.step())
349
- return [undefined, undefined];
350
- const aspectId = statement.getValue(0).getId();
351
- const version = statement.getValue(1).getString();
352
- return [aspectId, version];
268
+ return (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) ? statement.getValue(0).getId() : undefined;
353
269
  });
354
270
  }
355
- /**
356
- * Iterate all matching ExternalSourceAspects in the provenance iModel (target unless reverse sync) and call a function for each one.
357
- * @note provenance is done by federation guids where possible
358
- */
271
+ /** Iterate all matching ExternalSourceAspects in the provenance iModel (target unless reverse sync) and call a function for each one. */
359
272
  forEachTrackedElement(fn) {
360
273
  if (!this.provenanceDb.containsClass(core_backend_1.ExternalSourceAspect.classFullName)) {
361
274
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadSchema, "The BisCore schema version of the target database is too old");
362
275
  }
363
- // query for provenanceDb
364
- const provenanceContainerQuery = `
365
- SELECT e.ECInstanceId, FederationGuid, esa.Identifier as AspectIdentifier
366
- FROM bis.Element e
367
- LEFT JOIN bis.ExternalSourceAspect esa ON e.ECInstanceId=esa.Element.Id
368
- WHERE e.ECInstanceId NOT IN (0x1, 0xe, 0x10) -- special non-federated iModel-local elements
369
- AND ((Scope.Id IS NULL AND KIND IS NULL) OR (Scope.Id=:scopeId AND Kind=:kind))
370
- ORDER BY FederationGuid
371
- `;
372
- // query for nonProvenanceDb, the source to which the provenance is referring
373
- const provenanceSourceQuery = `
374
- SELECT e.ECInstanceId, FederationGuid
375
- FROM bis.Element e
376
- WHERE e.ECInstanceId NOT IN (0x1, 0xe, 0x10) -- special non-federated iModel-local elements
377
- ORDER BY FederationGuid
378
- `;
379
- // iterate through sorted list of fed guids from both dbs to get the intersection
380
- // NOTE: if we exposed the native attach database support,
381
- // we could get the intersection of fed guids in one query, not sure if it would be faster
382
- this.provenanceSourceDb.withStatement(provenanceSourceQuery, (sourceStmt) => this.provenanceDb.withStatement(provenanceContainerQuery, (containerStmt) => {
383
- containerStmt.bindId("scopeId", this.targetScopeElementId);
384
- containerStmt.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
385
- if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
386
- return;
387
- let sourceRow = sourceStmt.getRow();
388
- if (containerStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
389
- return;
390
- let containerRow = containerStmt.getRow();
391
- const runFnInProvDirection = (sourceId, targetId) => this._options.isReverseSynchronization ? fn(sourceId, targetId) : fn(targetId, sourceId);
392
- while (true) {
393
- const currSourceRow = sourceRow, currContainerRow = containerRow;
394
- if (currSourceRow.federationGuid !== undefined
395
- && currContainerRow.federationGuid !== undefined
396
- && currSourceRow.federationGuid === currContainerRow.federationGuid) {
397
- fn(sourceRow.id, containerRow.id);
398
- }
399
- if (currContainerRow.federationGuid === undefined
400
- || (currSourceRow.federationGuid !== undefined
401
- && currSourceRow.federationGuid >= currContainerRow.federationGuid)) {
402
- if (containerStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
403
- return;
404
- containerRow = containerStmt.getRow();
276
+ const sql = `SELECT Identifier,Element.Id FROM ${core_backend_1.ExternalSourceAspect.classFullName} WHERE Scope.Id=:scopeId AND Kind=:kind`;
277
+ this.provenanceDb.withPreparedStatement(sql, (statement) => {
278
+ statement.bindId("scopeId", this.targetScopeElementId);
279
+ statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
280
+ while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
281
+ const aspectIdentifier = statement.getValue(0).getString(); // ExternalSourceAspect.Identifier is of type string
282
+ const elementId = statement.getValue(1).getId();
283
+ if (this._options.isReverseSynchronization) {
284
+ fn(elementId, aspectIdentifier); // provenance coming from the sourceDb
405
285
  }
406
- if (currSourceRow.federationGuid === undefined
407
- || (currContainerRow.federationGuid !== undefined
408
- && currSourceRow.federationGuid <= currContainerRow.federationGuid)) {
409
- if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
410
- return;
411
- sourceRow = sourceStmt.getRow();
286
+ else {
287
+ fn(aspectIdentifier, elementId); // provenance coming from the targetDb
412
288
  }
413
- // NOTE: needed test cases:
414
- // - provenance container or provenance source has no fedguids
415
- // - transforming split and join scenarios
416
- if (!currContainerRow.federationGuid && currContainerRow.aspectIdentifier)
417
- runFnInProvDirection(currContainerRow.id, currContainerRow.aspectIdentifier);
418
289
  }
419
- }));
290
+ });
420
291
  }
421
292
  /** Initialize the source to target Element mapping from ExternalSourceAspects in the target iModel.
422
293
  * @note This method is called from all `process*` functions and should never need to be called directly.
@@ -429,62 +300,62 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
429
300
  this.context.remapElement(sourceElementId, targetElementId);
430
301
  });
431
302
  if (args)
432
- return this.remapDeletedSourceElements();
303
+ return this.remapDeletedSourceElements(args);
433
304
  }
434
305
  /** When processing deleted elements in a reverse synchronization, the [[provenanceDb]] (usually a branch iModel) has already
435
306
  * deleted the [ExternalSourceAspect]($backend)s that tell us which elements in the reverse synchronization target (usually
436
307
  * a master iModel) should be deleted. We must use the changesets to get the values of those before they were deleted.
437
308
  */
438
- async remapDeletedSourceElements() {
309
+ async remapDeletedSourceElements(args) {
439
310
  // we need a connected iModel with changes to remap elements with deletions
440
- const notConnectedModel = this.sourceDb.iTwinId === undefined;
441
- const noChanges = this._targetScopeVersion.index === this.sourceDb.changeset.index;
442
- if (notConnectedModel || noChanges)
311
+ if (this.sourceDb.iTwinId === undefined)
443
312
  return;
444
- nodeAssert(this._changeSummaryIds, "change summaries should be initialized before we get here");
445
- nodeAssert(this._changeSummaryIds.length > 0, "change summaries should have at least one");
446
- const deletedElemSql = `
447
- SELECT ic.ChangedInstance.Id, ${this._coalesceChangeSummaryJoinedValue((_, i) => `ec${i}.FederationGuid`)}
448
- FROM ecchange.change.InstanceChange ic
449
- -- ask affan about whether this is worth it...
450
- ${this._changeSummaryIds.map((id, i) => `
451
- LEFT JOIN bis.Element.Changes(${id}, 'BeforeDelete') ec${i}
452
- ON ic.ChangedInstance.Id=ec${i}.ECInstanceId
453
- `).join('')}
454
- WHERE ic.OpCode=:opDelete
455
- AND InVirtualSet(:changeSummaryIds, ic.Summary.Id)
456
- -- not yet documented ecsql feature to check class id
457
- AND ic.ChangedInstance.ClassId IS (BisCore.Element)
458
- `;
459
- // must also support old ESA provenance if no fedguids
460
- this.sourceDb.withStatement(deletedElemSql, (stmt) => {
461
- stmt.bindInteger("opDelete", core_common_1.ChangeOpCode.Delete);
462
- stmt.bindIdSet("changeSummaryIds", this._changeSummaryIds);
463
- // instead of targetScopeElementId, we only operate on elements
464
- // that had colliding fed guids with the source...
465
- // currently that is enforced by us checking that the deleted element fedguid is in both
466
- // before remapping
467
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
468
- const sourceId = stmt.getValue(0).getId();
469
- // FIXME: if I could attach the second db, will probably be much faster to get target id
470
- const sourceFedGuid = stmt.getValue(1).getGuid();
471
- const targetId = this.queryElemIdByFedGuid(this.targetDb, sourceFedGuid);
472
- const deletionNotInTarget = !targetId;
473
- if (deletionNotInTarget)
474
- return;
475
- // TODO: maybe delete and don't just remap
476
- this.context.remapElement(sourceId, targetId);
313
+ try {
314
+ const startChangesetId = args.startChangesetId ?? this.sourceDb.changeset.id;
315
+ const endChangesetId = this.sourceDb.changeset.id;
316
+ const [firstChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetId, endChangesetId]
317
+ .map(async (id) => core_backend_1.IModelHost.hubAccess
318
+ .queryChangeset({
319
+ iModelId: this.sourceDb.iModelId,
320
+ changeset: { id },
321
+ accessToken: args.accessToken,
322
+ })
323
+ .then((changeset) => changeset.index)));
324
+ const changesetIds = await core_backend_1.ChangeSummaryManager.createChangeSummaries({
325
+ accessToken: args.accessToken,
326
+ iModelId: this.sourceDb.iModelId,
327
+ iTwinId: this.sourceDb.iTwinId,
328
+ range: { first: firstChangesetIndex, end: endChangesetIndex },
329
+ });
330
+ core_backend_1.ChangeSummaryManager.attachChangeCache(this.sourceDb);
331
+ for (const changesetId of changesetIds) {
332
+ this.sourceDb.withPreparedStatement(`
333
+ SELECT esac.Element.Id, esac.Identifier
334
+ FROM ecchange.change.InstanceChange ic
335
+ JOIN BisCore.ExternalSourceAspect.Changes(:changesetId, 'BeforeDelete') esac
336
+ ON ic.ChangedInstance.Id=esac.ECInstanceId
337
+ WHERE ic.OpCode=:opcode
338
+ AND ic.Summary.Id=:changesetId
339
+ AND esac.Scope.Id=:targetScopeElementId
340
+ -- not yet documented ecsql feature to check class id
341
+ AND ic.ChangedInstance.ClassId IS (ONLY BisCore.ExternalSourceAspect)
342
+ `, (stmt) => {
343
+ stmt.bindInteger("opcode", core_common_1.ChangeOpCode.Delete);
344
+ stmt.bindInteger("changesetId", changesetId);
345
+ stmt.bindInteger("targetScopeElementId", this.targetScopeElementId);
346
+ while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
347
+ const targetId = stmt.getValue(0).getId();
348
+ const sourceId = stmt.getValue(1).getString(); // BisCore.ExternalSourceAspect.Identifier stores a hex Id64String
349
+ // TODO: maybe delete and don't just remap
350
+ this.context.remapElement(targetId, sourceId);
351
+ }
352
+ });
477
353
  }
478
- });
479
- }
480
- queryElemIdByFedGuid(db, fedGuid) {
481
- return db.withPreparedStatement("SELECT ECInstanceId FROM Bis.Element WHERE FederationGuid=?", (stmt) => {
482
- stmt.bindGuid(1, fedGuid);
483
- if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
484
- return stmt.getValue(0).getId();
485
- else
486
- return undefined;
487
- });
354
+ }
355
+ finally {
356
+ if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
357
+ core_backend_1.ChangeSummaryManager.detachChangeCache(this.sourceDb);
358
+ }
488
359
  }
489
360
  /** Returns `true` if *brute force* delete detections should be run.
490
361
  * @note Not relevant for processChanges when change history is known.
@@ -502,9 +373,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
502
373
  * @throws [[IModelError]] If the required provenance information is not available to detect deletes.
503
374
  */
504
375
  async detectElementDeletes() {
505
- // FIXME: this is no longer possible to do without change data loading, but I don't think
506
- // anyone uses this obscure feature, maybe we can remove it?
507
- // NOTE: can implement this by checking for federation guids in the target that aren't
508
376
  if (this._options.isReverseSynchronization) {
509
377
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
510
378
  }
@@ -542,85 +410,24 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
542
410
  }
543
411
  return targetElementProps;
544
412
  }
545
- // handle sqlite coalesce requiring 2 arguments
546
- _coalesceChangeSummaryJoinedValue(f) {
547
- nodeAssert(this._changeSummaryIds?.length && this._changeSummaryIds.length > 0, "should have changeset data by now");
548
- const valueList = this._changeSummaryIds.map(f).join(',');
549
- return this._changeSummaryIds.length > 1 ? `coalesce(${valueList})` : valueList;
550
- }
551
- ;
552
- // FIXME: this is a PoC, don't load this all into memory
553
- _cacheSourceChanges() {
554
- nodeAssert(this._changeSummaryIds && this._changeSummaryIds.length > 0, "should have changeset data by now");
555
- this._hasElementChangedCache = new Set();
556
- this._deletedSourceRelationshipData = new Map();
557
- // somewhat complicated query because doing two things at once...
558
- // (not to mention the multijoin coalescing hack)
559
- // FIXME: perhaps the coalescing indicates that part should be done manually, not in the query?
560
- const query = `
561
- SELECT
562
- ic.ChangedInstance.Id AS InstId,
563
- -- NOTE: parse error even with () without iif
564
- iif(ic.ChangedInstance.ClassId IS (BisCore.Element), TRUE, FALSE) AS IsElemNotDeletedRel,
565
- coalesce(${
566
- // HACK: adding "NONE" for empty result seems to prevent a bug where getValue(3) stops working after the NULL columns
567
- this._changeSummaryIds.map((_, i) => `se${i}.FederationGuid, sec${i}.FederationGuid`).concat("'NONE'").join(',')}) AS SourceFedGuid,
568
- coalesce(${this._changeSummaryIds.map((_, i) => `te${i}.FederationGuid, tec${i}.FederationGuid`).concat("'NONE'").join(',')}) AS TargetFedGuid,
569
- ic.ChangedInstance.ClassId AS ClassId
570
- FROM ecchange.change.InstanceChange ic
571
- JOIN iModelChange.Changeset imc ON ic.Summary.Id=imc.Summary.Id
572
- -- ask affan about whether this is worth it... maybe the ""
573
- ${this._changeSummaryIds.map((id, i) => `
574
- LEFT JOIN bis.ElementRefersToElements.Changes(${id}, 'BeforeDelete') ertec${i}
575
- -- NOTE: see how the AND affects performance, it could be dropped
576
- ON ic.ChangedInstance.Id=ertec${i}.ECInstanceId
577
- AND NOT ic.ChangedInstance.ClassId IS (BisCore.Element)
578
- -- FIXME: test a deletion of both an element and a relationship at the same time
579
- LEFT JOIN bis.Element se${i}
580
- ON se${i}.ECInstanceId=ertec${i}.SourceECInstanceId
581
- LEFT JOIN bis.Element te${i}
582
- ON te${i}.ECInstanceId=ertec${i}.TargetECInstanceId
583
- LEFT JOIN bis.Element.Changes(${id}, 'BeforeDelete') sec${i}
584
- ON sec${i}.ECInstanceId=ertec${i}.SourceECInstanceId
585
- LEFT JOIN bis.Element.Changes(${id}, 'BeforeDelete') tec${i}
586
- ON tec${i}.ECInstanceId=ertec${i}.TargetECInstanceId
587
- `).join('')}
588
- -- ignore deleted elems, we take care of those separately
589
- WHERE ((ic.ChangedInstance.ClassId IS (BisCore.Element) AND ic.OpCode<>:opUpdate)
590
- OR (ic.ChangedInstance.ClassId IS (BisCore.ElementRefersToElements) AND ic.OpCode=:opDelete))
591
- `;
592
- this.sourceDb.withPreparedStatement(query, (stmt) => {
593
- stmt.bindInteger("opDelete", core_common_1.ChangeOpCode.Delete);
594
- stmt.bindInteger("opUpdate", core_common_1.ChangeOpCode.Update);
595
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
596
- // REPORT: stmt.getValue(>3) seems to be bugged but the values survive .getRow so using that for now
597
- const instId = stmt.getValue(0).getId();
598
- const isElemNotDeletedRel = stmt.getValue(1).getBoolean();
599
- if (isElemNotDeletedRel)
600
- this._hasElementChangedCache.add(instId);
601
- else {
602
- const sourceFedGuid = stmt.getValue(2).getGuid();
603
- const targetFedGuid = stmt.getValue(3).getGuid();
604
- const classFullName = stmt.getValue(4).getClassNameForClassId();
605
- this._deletedSourceRelationshipData.set(instId, { classFullName, sourceFedGuid, targetFedGuid });
606
- }
607
- }
608
- });
609
- }
610
413
  /** Returns true if a change within sourceElement is detected.
611
414
  * @param sourceElement The Element from the source iModel
612
415
  * @param targetElementId The Element from the target iModel to compare against.
613
416
  * @note A subclass can override this method to provide custom change detection behavior.
614
417
  */
615
- hasElementChanged(sourceElement, _targetElementId) {
616
- if (this._changeDataState === "no-changes")
617
- return false;
618
- if (this._changeDataState === "unconnected")
619
- return true;
620
- nodeAssert(this._changeDataState === "has-changes", "change data should be initialized by now");
621
- if (this._hasElementChangedCache === undefined)
622
- this._cacheSourceChanges();
623
- return this._hasElementChangedCache.has(sourceElement.id);
418
+ hasElementChanged(sourceElement, targetElementId) {
419
+ const sourceAspects = this.targetDb.elements.getAspects(targetElementId, core_backend_1.ExternalSourceAspect.classFullName);
420
+ for (const sourceAspect of sourceAspects) {
421
+ if (sourceAspect.scope === undefined) // if the scope was lost, we can't correlate so assume it changed
422
+ return true;
423
+ if (sourceAspect.identifier === sourceElement.id &&
424
+ sourceAspect.scope.id === this.targetScopeElementId &&
425
+ sourceAspect.kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
426
+ const lastModifiedTime = sourceElement.iModel.elements.queryLastModifiedTime(sourceElement.id);
427
+ return lastModifiedTime !== sourceAspect.version;
428
+ }
429
+ }
430
+ return true;
624
431
  }
625
432
  static transformCallbackFor(transformer, entity) {
626
433
  if (entity instanceof core_backend_1.Element)
@@ -808,10 +615,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
808
615
  }
809
616
  }
810
617
  }
811
- if (targetElementId !== undefined
812
- && core_bentley_1.Id64.isValid(targetElementId)
813
- && !this.hasElementChanged(sourceElement, targetElementId))
814
- return;
618
+ if (undefined !== targetElementId && core_bentley_1.Id64.isValidId64(targetElementId)) {
619
+ // compare LastMod of sourceElement to ExternalSourceAspect of targetElement to see there are changes to import
620
+ if (!this.hasElementChanged(sourceElement, targetElementId)) {
621
+ return;
622
+ }
623
+ }
815
624
  this.collectUnmappedReferences(sourceElement);
816
625
  // TODO: untangle targetElementId state...
817
626
  if (targetElementId === core_bentley_1.Id64.invalid)
@@ -823,29 +632,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
823
632
  this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
824
633
  // now that we've mapped this elem we can fix unmapped references to it
825
634
  this.resolvePendingReferences(sourceElement);
826
- // the transformer does not currently 'split' or 'join' any elements, therefore, it does not
827
- // insert external source aspects because federation guids are sufficient for this.
828
- // Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
829
- // when splitting/joining elements
830
- // physical consolidation is an example of a 'joining' transform
831
- // FIXME: document this externally!
832
- // verify at finalization time that we don't lose provenance on new elements
833
- // make public and improve `initElementProvenance` API for usage by consolidators
834
635
  if (!this._options.noProvenance) {
835
- let provenance = sourceElement.federationGuid;
836
- if (!provenance) {
837
- const aspectProps = this.initElementProvenance(sourceElement.id, targetElementProps.id);
838
- let [aspectId] = this.queryScopeExternalSource(aspectProps);
839
- if (aspectId === undefined) {
840
- aspectId = this.provenanceDb.elements.insertAspect(aspectProps);
841
- }
842
- else {
843
- this.provenanceDb.elements.updateAspect(aspectProps);
844
- }
845
- aspectProps.id = aspectId;
846
- provenance = aspectProps;
636
+ const aspectProps = this.initElementProvenance(sourceElement.id, targetElementProps.id);
637
+ let aspectId = this.queryExternalSourceAspectId(aspectProps);
638
+ if (aspectId === undefined) {
639
+ aspectId = this.provenanceDb.elements.insertAspect(aspectProps);
847
640
  }
848
- this.markLastProvenance(provenance, { isRelationship: false });
641
+ else {
642
+ this.provenanceDb.elements.updateAspect(aspectProps);
643
+ }
644
+ aspectProps.id = aspectId;
645
+ this.markLastProvenance(aspectProps, { isRelationship: false });
849
646
  }
850
647
  }
851
648
  resolvePendingReferences(entity) {
@@ -959,20 +756,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
959
756
  * @deprecated in 3.x. This method is no longer necessary since the transformer no longer needs to defer elements
960
757
  */
961
758
  async processDeferredElements(_numRetries = 3) { }
962
- /** called at the end ([[finalizeTransformation]]) of a transformation,
963
- * updates the target scope element to say that transformation up through the
964
- * source's changeset has been performed.
965
- */
966
- _updateTargetScopeVersion() {
967
- nodeAssert(this._targetScopeProvenanceProps);
968
- if (this._changeDataState === "has-changes") {
969
- this._targetScopeProvenanceProps.version = `${this.provenanceSourceDb.changeset.id};${this.provenanceSourceDb.changeset.index}`;
970
- this.provenanceDb.elements.updateAspect(this._targetScopeProvenanceProps);
971
- }
972
- }
973
- // FIXME: is this necessary when manually using lowlevel transform APIs?
974
759
  finalizeTransformation() {
975
- this._updateTargetScopeVersion();
976
760
  if (this._partiallyCommittedEntities.size > 0) {
977
761
  core_bentley_1.Logger.logWarning(loggerCategory, [
978
762
  "The following elements were never fully resolved:",
@@ -984,9 +768,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
984
768
  partiallyCommittedElem.forceComplete();
985
769
  }
986
770
  }
987
- // FIXME: make processAll have a try {} finally {} that cleans this up
988
- if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
989
- core_backend_1.ChangeSummaryManager.detachChangeCache(this.sourceDb);
990
771
  }
991
772
  /** Imports all relationships that subclass from the specified base class.
992
773
  * @param baseRelClassFullName The specified base relationship class.
@@ -1004,84 +785,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1004
785
  * This override calls [[onTransformRelationship]] and then [IModelImporter.importRelationship]($transformer) to update the target iModel.
1005
786
  */
1006
787
  onExportRelationship(sourceRelationship) {
1007
- const sourceFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.sourceId);
1008
- const targetFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.targetId);
1009
788
  const targetRelationshipProps = this.onTransformRelationship(sourceRelationship);
1010
789
  const targetRelationshipInstanceId = this.importer.importRelationship(targetRelationshipProps);
1011
- if (!this._options.noProvenance && core_bentley_1.Id64.isValid(targetRelationshipInstanceId)) {
1012
- let provenance = sourceFedGuid && targetFedGuid && `${sourceFedGuid}/${targetFedGuid}`;
1013
- if (!provenance) {
1014
- const aspectProps = this.initRelationshipProvenance(sourceRelationship, targetRelationshipInstanceId);
1015
- if (undefined === aspectProps.id) {
1016
- aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
1017
- }
1018
- (0, core_bentley_1.assert)(aspectProps.id !== undefined);
1019
- provenance = aspectProps;
790
+ if (!this._options.noProvenance && core_bentley_1.Id64.isValidId64(targetRelationshipInstanceId)) {
791
+ const aspectProps = this.initRelationshipProvenance(sourceRelationship, targetRelationshipInstanceId);
792
+ if (undefined === aspectProps.id) {
793
+ aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
1020
794
  }
1021
- this.markLastProvenance(provenance, { isRelationship: true });
795
+ (0, core_bentley_1.assert)(aspectProps.id !== undefined);
796
+ this.markLastProvenance(aspectProps, { isRelationship: true });
1022
797
  }
1023
798
  }
1024
- // FIXME: need to check if the element class was remapped and use that id instead
1025
- // is this really the best way to get class id? shouldn't we cache it somewhere?
1026
- // NOTE: maybe if we lower remapElementClass into here, we can use that
1027
- _getRelClassId(db, classFullName) {
1028
- // is it better to use un-cached `SELECT (ONLY ${classFullName})`?
1029
- return db.withPreparedStatement(`
1030
- SELECT c.ECInstanceId
1031
- FROM ECDbMeta.ECClassDef c
1032
- JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
1033
- WHERE s.Name=? AND c.Name=?
1034
- `, (stmt) => {
1035
- const [schemaName, className] = classFullName.split(".");
1036
- stmt.bindString(1, schemaName);
1037
- stmt.bindString(2, className);
1038
- if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
1039
- return stmt.getValue(0).getId();
1040
- (0, core_bentley_1.assert)(false, "relationship was not found");
1041
- });
1042
- }
1043
799
  /** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
1044
800
  * This override propagates the delete to the target iModel via [IModelImporter.deleteRelationship]($transformer).
1045
801
  */
1046
802
  onDeleteRelationship(sourceRelInstanceId) {
1047
- nodeAssert(this._deletedSourceRelationshipData, "should be defined at initialization by now");
1048
- const deletedRelData = this._deletedSourceRelationshipData.get(sourceRelInstanceId);
1049
- if (!deletedRelData) {
1050
- core_bentley_1.Logger.logWarning(loggerCategory, "tried to delete a relationship that wasn't in change data");
1051
- return;
1052
- }
1053
- const targetRelClassId = this._getRelClassId(this.targetDb, deletedRelData.classFullName);
1054
- // NOTE: if no remapping, could store the sourceRel class name earlier and reuse it instead of add to query
1055
- // TODO: name this query
1056
- const sql = `
1057
- SELECT SourceECInstanceId, TargetECInstanceId, erte.ECClassId
1058
- FROM BisCore.ElementRefersToElements erte
1059
- JOIN BisCore.Element se ON se.ECInstanceId=SourceECInstanceId
1060
- JOIN BisCore.Element te ON te.ECInstanceId=TargetECInstanceId
1061
- WHERE se.FederationGuid=:sourceFedGuid
1062
- AND te.FederationGuid=:targetFedGuid
1063
- AND erte.ECClassId=:relClassId
1064
- `;
803
+ const sql = `SELECT ECInstanceId,JsonProperties FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect` +
804
+ ` WHERE aspect.Scope.Id=:scopeId AND aspect.Kind=:kind AND aspect.Identifier=:identifier LIMIT 1`;
1065
805
  this.targetDb.withPreparedStatement(sql, (statement) => {
1066
- statement.bindGuid("sourceFedGuid", deletedRelData.sourceFedGuid);
1067
- statement.bindGuid("targetFedGuid", deletedRelData.targetFedGuid);
1068
- statement.bindId("relClassId", targetRelClassId);
806
+ statement.bindId("scopeId", this.targetScopeElementId);
807
+ statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
808
+ statement.bindString("identifier", sourceRelInstanceId);
1069
809
  if (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1070
- const sourceId = statement.getValue(0).getId();
1071
- const targetId = statement.getValue(1).getId();
1072
- const targetRelClassFullName = statement.getValue(2).getClassNameForClassId();
1073
- // FIXME: make importer.deleteRelationship not need full props
1074
- const targetRelationship = this.targetDb.relationships.tryGetInstance(targetRelClassFullName, { sourceId, targetId });
1075
- if (targetRelationship) {
1076
- this.importer.deleteRelationship(targetRelationship.toJSON());
810
+ const json = JSON.parse(statement.getValue(1).getString());
811
+ if (undefined !== json.targetRelInstanceId) {
812
+ const targetRelationship = this.targetDb.relationships.tryGetInstance(core_backend_1.ElementRefersToElements.classFullName, json.targetRelInstanceId);
813
+ if (targetRelationship) {
814
+ this.importer.deleteRelationship(targetRelationship.toJSON());
815
+ }
816
+ this.targetDb.elements.deleteAspect(statement.getValue(0).getId());
1077
817
  }
1078
- // FIXME: restore in ESA compatible method
1079
- //this.targetDb.elements.deleteAspect(statement.getValue(0).getId());
1080
818
  }
1081
819
  });
1082
820
  }
1083
821
  /** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
1084
- * @deprecated
1085
822
  * @see processChanges
1086
823
  * @note This method is called from [[processAll]] and is not needed by [[processChanges]], so it only needs to be called directly when processing a subset of an iModel.
1087
824
  * @throws [[IModelError]] If the required provenance information is not available to detect deletes.
@@ -1091,12 +828,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1091
828
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
1092
829
  }
1093
830
  const aspectDeleteIds = [];
1094
- const sql = `
1095
- SELECT ECInstanceId, Identifier, JsonProperties
1096
- FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect
1097
- WHERE aspect.Scope.Id=:scopeId
1098
- AND aspect.Kind=:kind
1099
- `;
831
+ const sql = `SELECT ECInstanceId,Identifier,JsonProperties FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect WHERE aspect.Scope.Id=:scopeId AND aspect.Kind=:kind`;
1100
832
  await this.targetDb.withPreparedStatement(sql, async (statement) => {
1101
833
  statement.bindId("scopeId", this.targetScopeElementId);
1102
834
  statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
@@ -1124,7 +856,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1124
856
  const targetRelationshipProps = sourceRelationship.toJSON();
1125
857
  targetRelationshipProps.sourceId = this.context.findTargetElementId(sourceRelationship.sourceId);
1126
858
  targetRelationshipProps.targetId = this.context.findTargetElementId(sourceRelationship.targetId);
1127
- // TODO: move to cloneRelationship in IModelCloneContext
1128
859
  sourceRelationship.forEachProperty((propertyName, propertyMetaData) => {
1129
860
  if ((core_common_1.PrimitiveTypeCode.Long === propertyMetaData.primitiveType) && ("Id" === propertyMetaData.extendedType)) {
1130
861
  targetRelationshipProps[propertyName] = this.context.findTargetElementId(sourceRelationship.asAny[propertyName]);
@@ -1222,7 +953,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1222
953
  * It is more efficient to process *data* changes after the schema changes have been saved.
1223
954
  */
1224
955
  async processSchemas() {
1225
- this.events.emit(TransformerEvent.beginProcessSchemas);
1226
956
  // we do not need to initialize for this since no entities are exported
1227
957
  try {
1228
958
  core_backend_1.IModelJsFs.mkdirSync(this._schemaExportDir);
@@ -1240,7 +970,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1240
970
  finally {
1241
971
  core_backend_1.IModelJsFs.removeSync(this._schemaExportDir);
1242
972
  this._longNamedSchemasMap.clear();
1243
- this.events.emit(TransformerEvent.endProcessSchemas);
1244
973
  }
1245
974
  }
1246
975
  /** Cause all fonts to be exported from the source iModel and imported into the target iModel.
@@ -1297,53 +1026,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1297
1026
  if (this._initialized)
1298
1027
  return;
1299
1028
  await this.context.initialize();
1300
- await this._tryInitChangesetData(args);
1301
1029
  // eslint-disable-next-line deprecation/deprecation
1302
1030
  await this.initFromExternalSourceAspects(args);
1303
1031
  this._initialized = true;
1304
1032
  }
1305
- async _tryInitChangesetData(args) {
1306
- if (!args || this.sourceDb.iTwinId === undefined) {
1307
- this._changeDataState = "unconnected";
1308
- return;
1309
- }
1310
- const noChanges = this._targetScopeVersion.index === this.sourceDb.changeset.index;
1311
- if (noChanges) {
1312
- this._changeDataState = "no-changes";
1313
- this._changeSummaryIds = [];
1314
- return;
1315
- }
1316
- // NOTE: that we do NOT download the changesummary for the last transformed version, we want
1317
- // to ignore those already processed changes
1318
- const startChangesetIndexOrId = args?.startChangesetId ?? this._targetScopeVersion.index + 1;
1319
- const endChangesetId = this.sourceDb.changeset.id;
1320
- const [startChangesetIndex, endChangesetIndex] = await Promise.all(([startChangesetIndexOrId, endChangesetId])
1321
- .map(async (indexOrId) => typeof indexOrId === "number"
1322
- ? indexOrId
1323
- : core_backend_1.IModelHost.hubAccess
1324
- .queryChangeset({
1325
- iModelId: this.sourceDb.iModelId,
1326
- changeset: { id: indexOrId },
1327
- accessToken: args.accessToken,
1328
- })
1329
- .then((changeset) => changeset.index)));
1330
- // FIXME: do we need the startChangesetId?
1331
- this._changeSummaryIds = await core_backend_1.ChangeSummaryManager.createChangeSummaries({
1332
- accessToken: args.accessToken,
1333
- iModelId: this.sourceDb.iModelId,
1334
- iTwinId: this.sourceDb.iTwinId,
1335
- range: { first: startChangesetIndex, end: endChangesetIndex },
1336
- });
1337
- core_backend_1.ChangeSummaryManager.attachChangeCache(this.sourceDb);
1338
- this._changeDataState = "has-changes";
1339
- }
1340
1033
  /** Export everything from the source iModel and import the transformed entities into the target iModel.
1341
1034
  * @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
1342
1035
  */
1343
1036
  async processAll() {
1344
- this.events.emit(TransformerEvent.beginProcessAll);
1037
+ core_bentley_1.Logger.logTrace(loggerCategory, "processAll()");
1345
1038
  this.logSettings();
1346
- this.initScopeProvenance();
1039
+ this.validateScopeProvenance();
1347
1040
  await this.initialize();
1348
1041
  await this.exporter.exportCodeSpecs();
1349
1042
  await this.exporter.exportFonts();
@@ -1361,18 +1054,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1361
1054
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
1362
1055
  this.importer.computeProjectExtents();
1363
1056
  this.finalizeTransformation();
1364
- this.events.emit(TransformerEvent.endProcessAll);
1365
1057
  }
1366
1058
  markLastProvenance(sourceAspect, { isRelationship = false }) {
1367
- this._lastProvenanceEntityInfo
1368
- = typeof sourceAspect === "string"
1369
- ? sourceAspect
1370
- : {
1371
- entityId: sourceAspect.element.id,
1372
- aspectId: sourceAspect.id,
1373
- aspectVersion: sourceAspect.version ?? "",
1374
- aspectKind: isRelationship ? core_backend_1.ExternalSourceAspect.Kind.Relationship : core_backend_1.ExternalSourceAspect.Kind.Element,
1375
- };
1059
+ this._lastProvenanceEntityInfo = {
1060
+ entityId: sourceAspect.element.id,
1061
+ aspectId: sourceAspect.id,
1062
+ aspectVersion: sourceAspect.version ?? "",
1063
+ aspectKind: isRelationship ? core_backend_1.ExternalSourceAspect.Kind.Relationship : core_backend_1.ExternalSourceAspect.Kind.Element,
1064
+ };
1376
1065
  }
1377
1066
  /**
1378
1067
  * Load the state of the active transformation from an open SQLiteDb
@@ -1384,35 +1073,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1384
1073
  const lastProvenanceEntityInfo = db.withSqliteStatement(`SELECT entityId, aspectId, aspectVersion, aspectKind FROM ${IModelTransformer.lastProvenanceEntityInfoTable}`, (stmt) => {
1385
1074
  if (core_bentley_1.DbResult.BE_SQLITE_ROW !== stmt.step())
1386
1075
  throw Error("expected row when getting lastProvenanceEntityId from target state table");
1387
- const entityId = stmt.getValueString(0);
1388
- const isGuidOrGuidPair = entityId.includes('-');
1389
- return isGuidOrGuidPair
1390
- ? entityId
1391
- : {
1392
- entityId,
1393
- aspectId: stmt.getValueString(1),
1394
- aspectVersion: stmt.getValueString(2),
1395
- aspectKind: stmt.getValueString(3),
1396
- };
1076
+ return {
1077
+ entityId: stmt.getValueString(0),
1078
+ aspectId: stmt.getValueString(1),
1079
+ aspectVersion: stmt.getValueString(2),
1080
+ aspectKind: stmt.getValueString(3),
1081
+ };
1397
1082
  });
1398
- /*
1399
- // TODO: maybe save transformer state resumption state based on target changset and require calls
1400
- // to saveChanges
1401
- if () {
1402
- const [sourceFedGuid, targetFedGuid, relClassFullName] = lastProvenanceEntityInfo.split("/");
1403
- const isRelProvenance = targetFedGuid !== undefined;
1404
- const instanceId = isRelProvenance
1405
- ? this.targetDb.elements.getElement({federationGuid: sourceFedGuid})
1406
- : "";
1407
- //const classId =
1408
- if (isRelProvenance) {
1409
- }
1410
- }
1411
- */
1412
- const targetHasCorrectLastProvenance = typeof lastProvenanceEntityInfo === "string" ||
1413
- // ignore provenance check if it's null since we can't bind those ids
1083
+ const targetHasCorrectLastProvenance =
1084
+ // ignore provenance check if it's null since we can't bind those ids
1085
+ !core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.aspectId) ||
1414
1086
  !core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.entityId) ||
1415
- !core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.aspectId) ||
1416
1087
  this.provenanceDb.withPreparedStatement(`
1417
1088
  SELECT Version FROM ${core_backend_1.ExternalSourceAspect.classFullName}
1418
1089
  WHERE Scope.Id=:scopeId
@@ -1509,9 +1180,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1509
1180
  throw Error("Failed to create the js state table in the state database");
1510
1181
  if (core_bentley_1.DbResult.BE_SQLITE_DONE !== db.executeSQL(`
1511
1182
  CREATE TABLE ${IModelTransformer.lastProvenanceEntityInfoTable} (
1512
- -- either the invalid id for null provenance state, federation guid (or pair for rels) of the entity, or a hex element id
1183
+ -- because we cannot bind the invalid id which we use for our null state, we actually store the id as a hex string
1513
1184
  entityId TEXT,
1514
- -- the following are only valid if the above entityId is a hex id representation
1515
1185
  aspectId TEXT,
1516
1186
  aspectVersion TEXT,
1517
1187
  aspectKind TEXT
@@ -1525,11 +1195,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1525
1195
  throw Error("Failed to insert options into the state database");
1526
1196
  });
1527
1197
  db.withSqliteStatement(`INSERT INTO ${IModelTransformer.lastProvenanceEntityInfoTable} (entityId, aspectId, aspectVersion, aspectKind) VALUES (?,?,?,?)`, (stmt) => {
1528
- const lastProvenanceEntityInfo = this._lastProvenanceEntityInfo;
1529
- stmt.bindString(1, lastProvenanceEntityInfo?.entityId ?? this._lastProvenanceEntityInfo);
1530
- stmt.bindString(2, lastProvenanceEntityInfo?.aspectId ?? "");
1531
- stmt.bindString(3, lastProvenanceEntityInfo?.aspectVersion ?? "");
1532
- stmt.bindString(4, lastProvenanceEntityInfo?.aspectKind ?? "");
1198
+ stmt.bindString(1, this._lastProvenanceEntityInfo.entityId);
1199
+ stmt.bindString(2, this._lastProvenanceEntityInfo.aspectId);
1200
+ stmt.bindString(3, this._lastProvenanceEntityInfo.aspectVersion);
1201
+ stmt.bindString(4, this._lastProvenanceEntityInfo.aspectKind);
1533
1202
  if (core_bentley_1.DbResult.BE_SQLITE_DONE !== stmt.step())
1534
1203
  throw Error("Failed to insert options into the state database");
1535
1204
  });
@@ -1558,16 +1227,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1558
1227
  }
1559
1228
  }
1560
1229
  /** Export changes from the source iModel and import the transformed entities into the target iModel.
1561
- * Inserts, updates, and deletes are determined by inspecting the changeset(s).
1562
- * @param accessToken A valid access token string
1563
- * @param startChangesetId Include changes from this changeset up through and including the current changeset.
1564
- * If this parameter is not provided, then just the current changeset will be exported.
1565
- * @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range.
1566
- */
1230
+ * Inserts, updates, and deletes are determined by inspecting the changeset(s).
1231
+ * @param accessToken A valid access token string
1232
+ * @param startChangesetId Include changes from this changeset up through and including the current changeset.
1233
+ * If this parameter is not provided, then just the current changeset will be exported.
1234
+ * @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range.
1235
+ */
1567
1236
  async processChanges(accessToken, startChangesetId) {
1568
- this.events.emit(TransformerEvent.beginProcessChanges, startChangesetId);
1237
+ core_bentley_1.Logger.logTrace(loggerCategory, "processChanges()");
1569
1238
  this.logSettings();
1570
- this.initScopeProvenance();
1239
+ this.validateScopeProvenance();
1571
1240
  await this.initialize({ accessToken, startChangesetId });
1572
1241
  await this.exporter.exportChanges(accessToken, startChangesetId);
1573
1242
  await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
@@ -1575,7 +1244,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1575
1244
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
1576
1245
  this.importer.computeProjectExtents();
1577
1246
  this.finalizeTransformation();
1578
- this.events.emit(TransformerEvent.endProcessChanges);
1579
1247
  }
1580
1248
  }
1581
1249
  /** @internal the name of the table where javascript state of the transformer is serialized in transformer state dumps */
@@ -1680,17 +1348,4 @@ class TemplateModelCloner extends IModelTransformer {
1680
1348
  }
1681
1349
  }
1682
1350
  exports.TemplateModelCloner = TemplateModelCloner;
1683
- function queryElemFedGuid(db, elemId) {
1684
- return db.withPreparedStatement(`
1685
- SELECT FederationGuid
1686
- FROM bis.Element
1687
- WHERE ECInstanceId=?
1688
- `, (stmt) => {
1689
- stmt.bindId(1, elemId);
1690
- (0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW);
1691
- const result = stmt.getValue(0).getGuid();
1692
- (0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_DONE);
1693
- return result;
1694
- });
1695
- }
1696
1351
  //# sourceMappingURL=IModelTransformer.js.map