@itwin/imodel-transformer 0.1.8-fedguidopt.0 → 0.1.9
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/IModelTransformer.d.ts +12 -73
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +160 -496
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/transformer.d.ts.map +1 -1
- package/lib/cjs/transformer.js.map +1 -1
- package/package.json +28 -27
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TemplateModelCloner = exports.IModelTransformer =
|
|
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
|
-
/**
|
|
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
|
-
|
|
229
|
+
aspectProps.id = this.queryExternalSourceAspectId(aspectProps);
|
|
270
230
|
return aspectProps;
|
|
271
231
|
}
|
|
272
|
-
|
|
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
|
-
//
|
|
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
|
|
@@ -323,100 +252,41 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
323
252
|
}
|
|
324
253
|
if (!this._options.noProvenance) {
|
|
325
254
|
this.provenanceDb.elements.insertAspect(aspectProps);
|
|
326
|
-
this._isFirstSynchronization = true; // couldn't tell this is the first time without provenance
|
|
327
255
|
}
|
|
328
256
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
`;
|
|
257
|
+
}
|
|
258
|
+
queryExternalSourceAspectId(aspectProps) {
|
|
259
|
+
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
260
|
return this.provenanceDb.withPreparedStatement(sql, (statement) => {
|
|
342
261
|
statement.bindId("elementId", aspectProps.element.id);
|
|
343
262
|
if (aspectProps.scope === undefined)
|
|
344
|
-
return
|
|
263
|
+
return undefined; // return undefined instead of binding an invalid id
|
|
345
264
|
statement.bindId("scopeId", aspectProps.scope.id);
|
|
346
265
|
statement.bindString("kind", aspectProps.kind);
|
|
347
266
|
statement.bindString("identifier", aspectProps.identifier);
|
|
348
|
-
|
|
349
|
-
return [undefined, undefined];
|
|
350
|
-
const aspectId = statement.getValue(0).getId();
|
|
351
|
-
const version = statement.getValue(1).getString();
|
|
352
|
-
return [aspectId, version];
|
|
267
|
+
return (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) ? statement.getValue(0).getId() : undefined;
|
|
353
268
|
});
|
|
354
269
|
}
|
|
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
|
-
*/
|
|
270
|
+
/** Iterate all matching ExternalSourceAspects in the provenance iModel (target unless reverse sync) and call a function for each one. */
|
|
359
271
|
forEachTrackedElement(fn) {
|
|
360
272
|
if (!this.provenanceDb.containsClass(core_backend_1.ExternalSourceAspect.classFullName)) {
|
|
361
273
|
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadSchema, "The BisCore schema version of the target database is too old");
|
|
362
274
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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();
|
|
275
|
+
const sql = `SELECT Identifier,Element.Id FROM ${core_backend_1.ExternalSourceAspect.classFullName} WHERE Scope.Id=:scopeId AND Kind=:kind`;
|
|
276
|
+
this.provenanceDb.withPreparedStatement(sql, (statement) => {
|
|
277
|
+
statement.bindId("scopeId", this.targetScopeElementId);
|
|
278
|
+
statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
279
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
280
|
+
const aspectIdentifier = statement.getValue(0).getString(); // ExternalSourceAspect.Identifier is of type string
|
|
281
|
+
const elementId = statement.getValue(1).getId();
|
|
282
|
+
if (this._options.isReverseSynchronization) {
|
|
283
|
+
fn(elementId, aspectIdentifier); // provenance coming from the sourceDb
|
|
405
284
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
&& currSourceRow.federationGuid <= currContainerRow.federationGuid)) {
|
|
409
|
-
if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
410
|
-
return;
|
|
411
|
-
sourceRow = sourceStmt.getRow();
|
|
285
|
+
else {
|
|
286
|
+
fn(aspectIdentifier, elementId); // provenance coming from the targetDb
|
|
412
287
|
}
|
|
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
288
|
}
|
|
419
|
-
})
|
|
289
|
+
});
|
|
420
290
|
}
|
|
421
291
|
/** Initialize the source to target Element mapping from ExternalSourceAspects in the target iModel.
|
|
422
292
|
* @note This method is called from all `process*` functions and should never need to be called directly.
|
|
@@ -429,62 +299,62 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
429
299
|
this.context.remapElement(sourceElementId, targetElementId);
|
|
430
300
|
});
|
|
431
301
|
if (args)
|
|
432
|
-
return this.remapDeletedSourceElements();
|
|
302
|
+
return this.remapDeletedSourceElements(args);
|
|
433
303
|
}
|
|
434
304
|
/** When processing deleted elements in a reverse synchronization, the [[provenanceDb]] (usually a branch iModel) has already
|
|
435
305
|
* deleted the [ExternalSourceAspect]($backend)s that tell us which elements in the reverse synchronization target (usually
|
|
436
306
|
* a master iModel) should be deleted. We must use the changesets to get the values of those before they were deleted.
|
|
437
307
|
*/
|
|
438
|
-
async remapDeletedSourceElements() {
|
|
308
|
+
async remapDeletedSourceElements(args) {
|
|
439
309
|
// we need a connected iModel with changes to remap elements with deletions
|
|
440
|
-
|
|
441
|
-
const noChanges = this._targetScopeVersion.index === this.sourceDb.changeset.index;
|
|
442
|
-
if (notConnectedModel || noChanges)
|
|
310
|
+
if (this.sourceDb.iTwinId === undefined)
|
|
443
311
|
return;
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
312
|
+
try {
|
|
313
|
+
const startChangesetId = args.startChangesetId ?? this.sourceDb.changeset.id;
|
|
314
|
+
const endChangesetId = this.sourceDb.changeset.id;
|
|
315
|
+
const [firstChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetId, endChangesetId]
|
|
316
|
+
.map(async (id) => core_backend_1.IModelHost.hubAccess
|
|
317
|
+
.queryChangeset({
|
|
318
|
+
iModelId: this.sourceDb.iModelId,
|
|
319
|
+
changeset: { id },
|
|
320
|
+
accessToken: args.accessToken,
|
|
321
|
+
})
|
|
322
|
+
.then((changeset) => changeset.index)));
|
|
323
|
+
const changesetIds = await core_backend_1.ChangeSummaryManager.createChangeSummaries({
|
|
324
|
+
accessToken: args.accessToken,
|
|
325
|
+
iModelId: this.sourceDb.iModelId,
|
|
326
|
+
iTwinId: this.sourceDb.iTwinId,
|
|
327
|
+
range: { first: firstChangesetIndex, end: endChangesetIndex },
|
|
328
|
+
});
|
|
329
|
+
core_backend_1.ChangeSummaryManager.attachChangeCache(this.sourceDb);
|
|
330
|
+
for (const changesetId of changesetIds) {
|
|
331
|
+
this.sourceDb.withPreparedStatement(`
|
|
332
|
+
SELECT esac.Element.Id, esac.Identifier
|
|
333
|
+
FROM ecchange.change.InstanceChange ic
|
|
334
|
+
JOIN BisCore.ExternalSourceAspect.Changes(:changesetId, 'BeforeDelete') esac
|
|
335
|
+
ON ic.ChangedInstance.Id=esac.ECInstanceId
|
|
336
|
+
WHERE ic.OpCode=:opcode
|
|
337
|
+
AND ic.Summary.Id=:changesetId
|
|
338
|
+
AND esac.Scope.Id=:targetScopeElementId
|
|
339
|
+
-- not yet documented ecsql feature to check class id
|
|
340
|
+
AND ic.ChangedInstance.ClassId IS (ONLY BisCore.ExternalSourceAspect)
|
|
341
|
+
`, (stmt) => {
|
|
342
|
+
stmt.bindInteger("opcode", core_common_1.ChangeOpCode.Delete);
|
|
343
|
+
stmt.bindInteger("changesetId", changesetId);
|
|
344
|
+
stmt.bindInteger("targetScopeElementId", this.targetScopeElementId);
|
|
345
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
346
|
+
const targetId = stmt.getValue(0).getId();
|
|
347
|
+
const sourceId = stmt.getValue(1).getString(); // BisCore.ExternalSourceAspect.Identifier stores a hex Id64String
|
|
348
|
+
// TODO: maybe delete and don't just remap
|
|
349
|
+
this.context.remapElement(targetId, sourceId);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
477
352
|
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
484
|
-
return stmt.getValue(0).getId();
|
|
485
|
-
else
|
|
486
|
-
return undefined;
|
|
487
|
-
});
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
|
|
356
|
+
core_backend_1.ChangeSummaryManager.detachChangeCache(this.sourceDb);
|
|
357
|
+
}
|
|
488
358
|
}
|
|
489
359
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
490
360
|
* @note Not relevant for processChanges when change history is known.
|
|
@@ -502,9 +372,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
502
372
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
503
373
|
*/
|
|
504
374
|
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
375
|
if (this._options.isReverseSynchronization) {
|
|
509
376
|
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
|
|
510
377
|
}
|
|
@@ -516,7 +383,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
516
383
|
}
|
|
517
384
|
});
|
|
518
385
|
targetElementsToDelete.forEach((targetElementId) => {
|
|
519
|
-
|
|
386
|
+
try {
|
|
387
|
+
// TODO: make it possible to delete more elements at once to prevent redundant expensive
|
|
388
|
+
// element reference scanning
|
|
389
|
+
this.importer.deleteElement(targetElementId);
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
// ignore not found elements, iterative element tree deletion might have already deleted them
|
|
393
|
+
if (err.name !== "Not Found")
|
|
394
|
+
throw err;
|
|
395
|
+
}
|
|
520
396
|
});
|
|
521
397
|
}
|
|
522
398
|
/**
|
|
@@ -542,85 +418,24 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
542
418
|
}
|
|
543
419
|
return targetElementProps;
|
|
544
420
|
}
|
|
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
421
|
/** Returns true if a change within sourceElement is detected.
|
|
611
422
|
* @param sourceElement The Element from the source iModel
|
|
612
423
|
* @param targetElementId The Element from the target iModel to compare against.
|
|
613
424
|
* @note A subclass can override this method to provide custom change detection behavior.
|
|
614
425
|
*/
|
|
615
|
-
hasElementChanged(sourceElement,
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
426
|
+
hasElementChanged(sourceElement, targetElementId) {
|
|
427
|
+
const sourceAspects = this.targetDb.elements.getAspects(targetElementId, core_backend_1.ExternalSourceAspect.classFullName);
|
|
428
|
+
for (const sourceAspect of sourceAspects) {
|
|
429
|
+
if (sourceAspect.scope === undefined) // if the scope was lost, we can't correlate so assume it changed
|
|
430
|
+
return true;
|
|
431
|
+
if (sourceAspect.identifier === sourceElement.id &&
|
|
432
|
+
sourceAspect.scope.id === this.targetScopeElementId &&
|
|
433
|
+
sourceAspect.kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
|
|
434
|
+
const lastModifiedTime = sourceElement.iModel.elements.queryLastModifiedTime(sourceElement.id);
|
|
435
|
+
return lastModifiedTime !== sourceAspect.version;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
624
439
|
}
|
|
625
440
|
static transformCallbackFor(transformer, entity) {
|
|
626
441
|
if (entity instanceof core_backend_1.Element)
|
|
@@ -659,6 +474,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
659
474
|
collectUnmappedReferences(entity) {
|
|
660
475
|
const missingReferences = new core_common_1.EntityReferenceSet();
|
|
661
476
|
let thisPartialElem;
|
|
477
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
662
478
|
for (const referenceId of entity.getReferenceConcreteIds()) {
|
|
663
479
|
// TODO: probably need to rename from 'id' to 'ref' so these names aren't so ambiguous
|
|
664
480
|
const referenceIdInTarget = this.context.findTargetEntityId(referenceId);
|
|
@@ -808,10 +624,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
808
624
|
}
|
|
809
625
|
}
|
|
810
626
|
}
|
|
811
|
-
if (
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
627
|
+
if (undefined !== targetElementId && core_bentley_1.Id64.isValidId64(targetElementId)) {
|
|
628
|
+
// compare LastMod of sourceElement to ExternalSourceAspect of targetElement to see there are changes to import
|
|
629
|
+
if (!this.hasElementChanged(sourceElement, targetElementId)) {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
815
633
|
this.collectUnmappedReferences(sourceElement);
|
|
816
634
|
// TODO: untangle targetElementId state...
|
|
817
635
|
if (targetElementId === core_bentley_1.Id64.invalid)
|
|
@@ -823,29 +641,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
823
641
|
this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
|
|
824
642
|
// now that we've mapped this elem we can fix unmapped references to it
|
|
825
643
|
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
644
|
if (!this._options.noProvenance) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
else {
|
|
843
|
-
this.provenanceDb.elements.updateAspect(aspectProps);
|
|
844
|
-
}
|
|
845
|
-
aspectProps.id = aspectId;
|
|
846
|
-
provenance = aspectProps;
|
|
645
|
+
const aspectProps = this.initElementProvenance(sourceElement.id, targetElementProps.id);
|
|
646
|
+
let aspectId = this.queryExternalSourceAspectId(aspectProps);
|
|
647
|
+
if (aspectId === undefined) {
|
|
648
|
+
aspectId = this.provenanceDb.elements.insertAspect(aspectProps);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
this.provenanceDb.elements.updateAspect(aspectProps);
|
|
847
652
|
}
|
|
848
|
-
|
|
653
|
+
aspectProps.id = aspectId;
|
|
654
|
+
this.markLastProvenance(aspectProps, { isRelationship: false });
|
|
849
655
|
}
|
|
850
656
|
}
|
|
851
657
|
resolvePendingReferences(entity) {
|
|
@@ -959,20 +765,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
959
765
|
* @deprecated in 3.x. This method is no longer necessary since the transformer no longer needs to defer elements
|
|
960
766
|
*/
|
|
961
767
|
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
768
|
finalizeTransformation() {
|
|
975
|
-
this._updateTargetScopeVersion();
|
|
976
769
|
if (this._partiallyCommittedEntities.size > 0) {
|
|
977
770
|
core_bentley_1.Logger.logWarning(loggerCategory, [
|
|
978
771
|
"The following elements were never fully resolved:",
|
|
@@ -984,9 +777,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
984
777
|
partiallyCommittedElem.forceComplete();
|
|
985
778
|
}
|
|
986
779
|
}
|
|
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
780
|
}
|
|
991
781
|
/** Imports all relationships that subclass from the specified base class.
|
|
992
782
|
* @param baseRelClassFullName The specified base relationship class.
|
|
@@ -1004,84 +794,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1004
794
|
* This override calls [[onTransformRelationship]] and then [IModelImporter.importRelationship]($transformer) to update the target iModel.
|
|
1005
795
|
*/
|
|
1006
796
|
onExportRelationship(sourceRelationship) {
|
|
1007
|
-
const sourceFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.sourceId);
|
|
1008
|
-
const targetFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.targetId);
|
|
1009
797
|
const targetRelationshipProps = this.onTransformRelationship(sourceRelationship);
|
|
1010
798
|
const targetRelationshipInstanceId = this.importer.importRelationship(targetRelationshipProps);
|
|
1011
|
-
if (!this._options.noProvenance && core_bentley_1.Id64.
|
|
1012
|
-
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
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;
|
|
799
|
+
if (!this._options.noProvenance && core_bentley_1.Id64.isValidId64(targetRelationshipInstanceId)) {
|
|
800
|
+
const aspectProps = this.initRelationshipProvenance(sourceRelationship, targetRelationshipInstanceId);
|
|
801
|
+
if (undefined === aspectProps.id) {
|
|
802
|
+
aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
|
|
1020
803
|
}
|
|
1021
|
-
|
|
804
|
+
(0, core_bentley_1.assert)(aspectProps.id !== undefined);
|
|
805
|
+
this.markLastProvenance(aspectProps, { isRelationship: true });
|
|
1022
806
|
}
|
|
1023
807
|
}
|
|
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
808
|
/** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
|
|
1044
809
|
* This override propagates the delete to the target iModel via [IModelImporter.deleteRelationship]($transformer).
|
|
1045
810
|
*/
|
|
1046
811
|
onDeleteRelationship(sourceRelInstanceId) {
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
`;
|
|
812
|
+
const sql = `SELECT ECInstanceId,JsonProperties FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect` +
|
|
813
|
+
` WHERE aspect.Scope.Id=:scopeId AND aspect.Kind=:kind AND aspect.Identifier=:identifier LIMIT 1`;
|
|
1065
814
|
this.targetDb.withPreparedStatement(sql, (statement) => {
|
|
1066
|
-
statement.
|
|
1067
|
-
statement.
|
|
1068
|
-
statement.
|
|
815
|
+
statement.bindId("scopeId", this.targetScopeElementId);
|
|
816
|
+
statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
817
|
+
statement.bindString("identifier", sourceRelInstanceId);
|
|
1069
818
|
if (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
this.
|
|
819
|
+
const json = JSON.parse(statement.getValue(1).getString());
|
|
820
|
+
if (undefined !== json.targetRelInstanceId) {
|
|
821
|
+
const targetRelationship = this.targetDb.relationships.tryGetInstance(core_backend_1.ElementRefersToElements.classFullName, json.targetRelInstanceId);
|
|
822
|
+
if (targetRelationship) {
|
|
823
|
+
this.importer.deleteRelationship(targetRelationship.toJSON());
|
|
824
|
+
}
|
|
825
|
+
this.targetDb.elements.deleteAspect(statement.getValue(0).getId());
|
|
1077
826
|
}
|
|
1078
|
-
// FIXME: restore in ESA compatible method
|
|
1079
|
-
//this.targetDb.elements.deleteAspect(statement.getValue(0).getId());
|
|
1080
827
|
}
|
|
1081
828
|
});
|
|
1082
829
|
}
|
|
1083
830
|
/** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
|
|
1084
|
-
* @deprecated
|
|
1085
831
|
* @see processChanges
|
|
1086
832
|
* @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
833
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
@@ -1091,12 +837,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1091
837
|
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
|
|
1092
838
|
}
|
|
1093
839
|
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
|
-
`;
|
|
840
|
+
const sql = `SELECT ECInstanceId,Identifier,JsonProperties FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect WHERE aspect.Scope.Id=:scopeId AND aspect.Kind=:kind`;
|
|
1100
841
|
await this.targetDb.withPreparedStatement(sql, async (statement) => {
|
|
1101
842
|
statement.bindId("scopeId", this.targetScopeElementId);
|
|
1102
843
|
statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
@@ -1124,7 +865,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1124
865
|
const targetRelationshipProps = sourceRelationship.toJSON();
|
|
1125
866
|
targetRelationshipProps.sourceId = this.context.findTargetElementId(sourceRelationship.sourceId);
|
|
1126
867
|
targetRelationshipProps.targetId = this.context.findTargetElementId(sourceRelationship.targetId);
|
|
1127
|
-
// TODO: move to cloneRelationship in IModelCloneContext
|
|
1128
868
|
sourceRelationship.forEachProperty((propertyName, propertyMetaData) => {
|
|
1129
869
|
if ((core_common_1.PrimitiveTypeCode.Long === propertyMetaData.primitiveType) && ("Id" === propertyMetaData.extendedType)) {
|
|
1130
870
|
targetRelationshipProps[propertyName] = this.context.findTargetElementId(sourceRelationship.asAny[propertyName]);
|
|
@@ -1222,7 +962,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1222
962
|
* It is more efficient to process *data* changes after the schema changes have been saved.
|
|
1223
963
|
*/
|
|
1224
964
|
async processSchemas() {
|
|
1225
|
-
this.events.emit(TransformerEvent.beginProcessSchemas);
|
|
1226
965
|
// we do not need to initialize for this since no entities are exported
|
|
1227
966
|
try {
|
|
1228
967
|
core_backend_1.IModelJsFs.mkdirSync(this._schemaExportDir);
|
|
@@ -1240,7 +979,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1240
979
|
finally {
|
|
1241
980
|
core_backend_1.IModelJsFs.removeSync(this._schemaExportDir);
|
|
1242
981
|
this._longNamedSchemasMap.clear();
|
|
1243
|
-
this.events.emit(TransformerEvent.endProcessSchemas);
|
|
1244
982
|
}
|
|
1245
983
|
}
|
|
1246
984
|
/** Cause all fonts to be exported from the source iModel and imported into the target iModel.
|
|
@@ -1297,53 +1035,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1297
1035
|
if (this._initialized)
|
|
1298
1036
|
return;
|
|
1299
1037
|
await this.context.initialize();
|
|
1300
|
-
await this._tryInitChangesetData(args);
|
|
1301
1038
|
// eslint-disable-next-line deprecation/deprecation
|
|
1302
1039
|
await this.initFromExternalSourceAspects(args);
|
|
1303
1040
|
this._initialized = true;
|
|
1304
1041
|
}
|
|
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
1042
|
/** Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
1341
1043
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
1342
1044
|
*/
|
|
1343
1045
|
async processAll() {
|
|
1344
|
-
|
|
1046
|
+
core_bentley_1.Logger.logTrace(loggerCategory, "processAll()");
|
|
1345
1047
|
this.logSettings();
|
|
1346
|
-
this.
|
|
1048
|
+
this.validateScopeProvenance();
|
|
1347
1049
|
await this.initialize();
|
|
1348
1050
|
await this.exporter.exportCodeSpecs();
|
|
1349
1051
|
await this.exporter.exportFonts();
|
|
@@ -1361,18 +1063,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1361
1063
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
1362
1064
|
this.importer.computeProjectExtents();
|
|
1363
1065
|
this.finalizeTransformation();
|
|
1364
|
-
this.events.emit(TransformerEvent.endProcessAll);
|
|
1365
1066
|
}
|
|
1366
1067
|
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
1367
|
-
this._lastProvenanceEntityInfo
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
aspectVersion: sourceAspect.version ?? "",
|
|
1374
|
-
aspectKind: isRelationship ? core_backend_1.ExternalSourceAspect.Kind.Relationship : core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1375
|
-
};
|
|
1068
|
+
this._lastProvenanceEntityInfo = {
|
|
1069
|
+
entityId: sourceAspect.element.id,
|
|
1070
|
+
aspectId: sourceAspect.id,
|
|
1071
|
+
aspectVersion: sourceAspect.version ?? "",
|
|
1072
|
+
aspectKind: isRelationship ? core_backend_1.ExternalSourceAspect.Kind.Relationship : core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1073
|
+
};
|
|
1376
1074
|
}
|
|
1377
1075
|
/**
|
|
1378
1076
|
* Load the state of the active transformation from an open SQLiteDb
|
|
@@ -1384,35 +1082,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1384
1082
|
const lastProvenanceEntityInfo = db.withSqliteStatement(`SELECT entityId, aspectId, aspectVersion, aspectKind FROM ${IModelTransformer.lastProvenanceEntityInfoTable}`, (stmt) => {
|
|
1385
1083
|
if (core_bentley_1.DbResult.BE_SQLITE_ROW !== stmt.step())
|
|
1386
1084
|
throw Error("expected row when getting lastProvenanceEntityId from target state table");
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
:
|
|
1392
|
-
|
|
1393
|
-
aspectId: stmt.getValueString(1),
|
|
1394
|
-
aspectVersion: stmt.getValueString(2),
|
|
1395
|
-
aspectKind: stmt.getValueString(3),
|
|
1396
|
-
};
|
|
1085
|
+
return {
|
|
1086
|
+
entityId: stmt.getValueString(0),
|
|
1087
|
+
aspectId: stmt.getValueString(1),
|
|
1088
|
+
aspectVersion: stmt.getValueString(2),
|
|
1089
|
+
aspectKind: stmt.getValueString(3),
|
|
1090
|
+
};
|
|
1397
1091
|
});
|
|
1398
|
-
|
|
1399
|
-
//
|
|
1400
|
-
|
|
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
|
|
1092
|
+
const targetHasCorrectLastProvenance =
|
|
1093
|
+
// ignore provenance check if it's null since we can't bind those ids
|
|
1094
|
+
!core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.aspectId) ||
|
|
1414
1095
|
!core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.entityId) ||
|
|
1415
|
-
!core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.aspectId) ||
|
|
1416
1096
|
this.provenanceDb.withPreparedStatement(`
|
|
1417
1097
|
SELECT Version FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
1418
1098
|
WHERE Scope.Id=:scopeId
|
|
@@ -1509,9 +1189,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1509
1189
|
throw Error("Failed to create the js state table in the state database");
|
|
1510
1190
|
if (core_bentley_1.DbResult.BE_SQLITE_DONE !== db.executeSQL(`
|
|
1511
1191
|
CREATE TABLE ${IModelTransformer.lastProvenanceEntityInfoTable} (
|
|
1512
|
-
--
|
|
1192
|
+
-- because we cannot bind the invalid id which we use for our null state, we actually store the id as a hex string
|
|
1513
1193
|
entityId TEXT,
|
|
1514
|
-
-- the following are only valid if the above entityId is a hex id representation
|
|
1515
1194
|
aspectId TEXT,
|
|
1516
1195
|
aspectVersion TEXT,
|
|
1517
1196
|
aspectKind TEXT
|
|
@@ -1525,11 +1204,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1525
1204
|
throw Error("Failed to insert options into the state database");
|
|
1526
1205
|
});
|
|
1527
1206
|
db.withSqliteStatement(`INSERT INTO ${IModelTransformer.lastProvenanceEntityInfoTable} (entityId, aspectId, aspectVersion, aspectKind) VALUES (?,?,?,?)`, (stmt) => {
|
|
1528
|
-
|
|
1529
|
-
stmt.bindString(
|
|
1530
|
-
stmt.bindString(
|
|
1531
|
-
stmt.bindString(
|
|
1532
|
-
stmt.bindString(4, lastProvenanceEntityInfo?.aspectKind ?? "");
|
|
1207
|
+
stmt.bindString(1, this._lastProvenanceEntityInfo.entityId);
|
|
1208
|
+
stmt.bindString(2, this._lastProvenanceEntityInfo.aspectId);
|
|
1209
|
+
stmt.bindString(3, this._lastProvenanceEntityInfo.aspectVersion);
|
|
1210
|
+
stmt.bindString(4, this._lastProvenanceEntityInfo.aspectKind);
|
|
1533
1211
|
if (core_bentley_1.DbResult.BE_SQLITE_DONE !== stmt.step())
|
|
1534
1212
|
throw Error("Failed to insert options into the state database");
|
|
1535
1213
|
});
|
|
@@ -1558,16 +1236,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1558
1236
|
}
|
|
1559
1237
|
}
|
|
1560
1238
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1239
|
+
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
1240
|
+
* @param accessToken A valid access token string
|
|
1241
|
+
* @param startChangesetId Include changes from this changeset up through and including the current changeset.
|
|
1242
|
+
* If this parameter is not provided, then just the current changeset will be exported.
|
|
1243
|
+
* @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.
|
|
1244
|
+
*/
|
|
1567
1245
|
async processChanges(accessToken, startChangesetId) {
|
|
1568
|
-
|
|
1246
|
+
core_bentley_1.Logger.logTrace(loggerCategory, "processChanges()");
|
|
1569
1247
|
this.logSettings();
|
|
1570
|
-
this.
|
|
1248
|
+
this.validateScopeProvenance();
|
|
1571
1249
|
await this.initialize({ accessToken, startChangesetId });
|
|
1572
1250
|
await this.exporter.exportChanges(accessToken, startChangesetId);
|
|
1573
1251
|
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
@@ -1575,7 +1253,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1575
1253
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
1576
1254
|
this.importer.computeProjectExtents();
|
|
1577
1255
|
this.finalizeTransformation();
|
|
1578
|
-
this.events.emit(TransformerEvent.endProcessChanges);
|
|
1579
1256
|
}
|
|
1580
1257
|
}
|
|
1581
1258
|
/** @internal the name of the table where javascript state of the transformer is serialized in transformer state dumps */
|
|
@@ -1680,17 +1357,4 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
1680
1357
|
}
|
|
1681
1358
|
}
|
|
1682
1359
|
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
1360
|
//# sourceMappingURL=IModelTransformer.js.map
|