@itwin/imodel-transformer 0.3.18-fedguidopt.7 → 0.4.1-dev.0
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/CHANGELOG.md +26 -1
- package/lib/cjs/BranchProvenanceInitializer.d.ts +3 -10
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +13 -84
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +16 -0
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +1 -0
- package/lib/cjs/DetachedExportElementAspectsStrategy.js +98 -0
- package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -0
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts +19 -0
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts.map +1 -0
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js +43 -0
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -0
- package/lib/cjs/ExportElementAspectsStrategy.d.ts +35 -0
- package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +1 -0
- package/lib/cjs/ExportElementAspectsStrategy.js +48 -0
- package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -0
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +12 -0
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +1 -0
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +43 -0
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -0
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +2 -0
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +15 -40
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +52 -125
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +29 -161
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +286 -934
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/package.json +14 -14
- package/lib/cjs/Algo.d.ts +0 -7
- package/lib/cjs/Algo.d.ts.map +0 -1
- package/lib/cjs/Algo.js +0 -50
- package/lib/cjs/Algo.js.map +0 -1
|
@@ -22,7 +22,6 @@ const PendingReferenceMap_1 = require("./PendingReferenceMap");
|
|
|
22
22
|
const EntityMap_1 = require("./EntityMap");
|
|
23
23
|
const IModelCloneContext_1 = require("./IModelCloneContext");
|
|
24
24
|
const EntityUnifier_1 = require("./EntityUnifier");
|
|
25
|
-
const Algo_1 = require("./Algo");
|
|
26
25
|
const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelTransformer;
|
|
27
26
|
const nullLastProvenanceEntityInfo = {
|
|
28
27
|
entityId: core_bentley_1.Id64.invalid,
|
|
@@ -95,12 +94,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
95
94
|
get targetScopeElementId() {
|
|
96
95
|
return this._options.targetScopeElementId;
|
|
97
96
|
}
|
|
98
|
-
get _isReverseSynchronization() {
|
|
99
|
-
return this._isSynchronization && this._options.isReverseSynchronization;
|
|
100
|
-
}
|
|
101
|
-
get _isForwardSynchronization() {
|
|
102
|
-
return this._isSynchronization && !this._options.isReverseSynchronization;
|
|
103
|
-
}
|
|
104
97
|
/** The element classes that are considered to define provenance in the iModel */
|
|
105
98
|
static get provenanceElementClasses() {
|
|
106
99
|
return [core_backend_1.FolderLink, core_backend_1.SynchronizationConfigLink, core_backend_1.ExternalSource, core_backend_1.ExternalSourceAttachment];
|
|
@@ -119,46 +112,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
119
112
|
/** map of (unprocessed element, referencing processed element) pairs to the partially committed element that needs the reference resolved
|
|
120
113
|
* and have some helper methods below for now */
|
|
121
114
|
this._pendingReferences = new PendingReferenceMap_1.PendingReferenceMap();
|
|
122
|
-
/** a set of elements for which source provenance will be explicitly tracked by ExternalSourceAspects */
|
|
123
|
-
this._elementsWithExplicitlyTrackedProvenance = new Set();
|
|
124
115
|
/** map of partially committed entities to their partial commit progress */
|
|
125
116
|
this._partiallyCommittedEntities = new EntityMap_1.EntityMap();
|
|
126
|
-
this._isSynchronization = false;
|
|
127
|
-
this._changesetRanges = undefined;
|
|
128
117
|
/** Set of entity keys which were not exported and don't need to be tracked for pending reference resolution.
|
|
129
118
|
* @note Currently only tracks elements which were not exported.
|
|
130
119
|
*/
|
|
131
120
|
this._skippedEntities = new Set();
|
|
132
|
-
// FIXME: add test using this
|
|
133
|
-
/**
|
|
134
|
-
* Previously the transformer would insert provenance always pointing to the "target" relationship.
|
|
135
|
-
* It should (and now by default does) instead insert provenance pointing to the provenanceSource
|
|
136
|
-
* SEE: https://github.com/iTwin/imodel-transformer/issues/54
|
|
137
|
-
* This exists only to facilitate testing that the transformer can handle the older, flawed method
|
|
138
|
-
*/
|
|
139
|
-
this._forceOldRelationshipProvenanceMethod = false;
|
|
140
|
-
/** NOTE: the json properties must be converted to string before insertion */
|
|
141
|
-
this._targetScopeProvenanceProps = undefined;
|
|
142
|
-
/**
|
|
143
|
-
* Index of the changeset that the transformer was at when the transformation begins (was constructed).
|
|
144
|
-
* Used to determine at the end which changesets were part of a synchronization.
|
|
145
|
-
*/
|
|
146
|
-
this._startingChangesetIndices = undefined;
|
|
147
|
-
this._cachedSynchronizationVersion = undefined;
|
|
148
|
-
this._targetClassNameToClassIdCache = new Map();
|
|
149
|
-
// if undefined, it can be initialized by calling [[this._cacheSourceChanges]]
|
|
150
|
-
this._hasElementChangedCache = undefined;
|
|
151
|
-
this._deletedSourceRelationshipData = undefined;
|
|
152
121
|
this._yieldManager = new core_bentley_1.YieldManager();
|
|
153
122
|
/** The directory where schemas will be exported, a random temporary directory */
|
|
154
123
|
this._schemaExportDir = path.join(core_backend_1.KnownLocations.tmpdir, core_bentley_1.Guid.createValue());
|
|
155
124
|
this._longNamedSchemasMap = new Map();
|
|
156
125
|
/** state to prevent reinitialization, @see [[initialize]] */
|
|
157
126
|
this._initialized = false;
|
|
158
|
-
/** length === 0 when _changeDataState = "no-change", length > 0 means "has-changes", otherwise undefined */
|
|
159
|
-
this._changeSummaryIds = undefined;
|
|
160
|
-
this._sourceChangeDataState = "uninited";
|
|
161
|
-
/** previous provenance, either a federation guid, a `${sourceFedGuid}/${targetFedGuid}` pair, or required aspect props */
|
|
162
127
|
this._lastProvenanceEntityInfo = nullLastProvenanceEntityInfo;
|
|
163
128
|
// initialize IModelTransformOptions
|
|
164
129
|
this._options = {
|
|
@@ -206,13 +171,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
206
171
|
this.targetDb = this.importer.targetDb;
|
|
207
172
|
// create the IModelCloneContext, it must be initialized later
|
|
208
173
|
this.context = new IModelCloneContext_1.IModelCloneContext(this.sourceDb, this.targetDb);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
};
|
|
174
|
+
// this internal is guaranteed stable for just transformer usage
|
|
175
|
+
/* eslint-disable @itwin/no-internal */
|
|
176
|
+
if ("codeValueBehavior" in this.sourceDb) {
|
|
177
|
+
this.sourceDb.codeValueBehavior = "exact";
|
|
178
|
+
this.targetDb.codeValueBehavior = "exact";
|
|
215
179
|
}
|
|
180
|
+
/* eslint-enable @itwin/no-internal */
|
|
216
181
|
}
|
|
217
182
|
/** Dispose any native resources associated with this IModelTransformer. */
|
|
218
183
|
dispose() {
|
|
@@ -250,6 +215,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
250
215
|
/** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
|
|
251
216
|
static initElementProvenanceOptions(sourceElementId, targetElementId, args) {
|
|
252
217
|
const elementId = args.isReverseSynchronization ? sourceElementId : targetElementId;
|
|
218
|
+
const version = args.isReverseSynchronization
|
|
219
|
+
? args.targetDb.elements.queryLastModifiedTime(targetElementId)
|
|
220
|
+
: args.sourceDb.elements.queryLastModifiedTime(sourceElementId);
|
|
253
221
|
const aspectIdentifier = args.isReverseSynchronization ? targetElementId : sourceElementId;
|
|
254
222
|
const aspectProps = {
|
|
255
223
|
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
@@ -257,39 +225,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
257
225
|
scope: { id: args.targetScopeElementId },
|
|
258
226
|
identifier: aspectIdentifier,
|
|
259
227
|
kind: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
260
|
-
version
|
|
261
|
-
};
|
|
262
|
-
return aspectProps;
|
|
263
|
-
}
|
|
264
|
-
static initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, args) {
|
|
265
|
-
const provenanceDb = args.isReverseSynchronization ? args.sourceDb : args.targetDb;
|
|
266
|
-
const aspectIdentifier = args.isReverseSynchronization ? targetRelInstanceId : sourceRelInstanceId;
|
|
267
|
-
const provenanceRelInstanceId = args.isReverseSynchronization ? sourceRelInstanceId : targetRelInstanceId;
|
|
268
|
-
const elementId = provenanceDb.withPreparedStatement("SELECT SourceECInstanceId FROM bis.ElementRefersToElements WHERE ECInstanceId=?", (stmt) => {
|
|
269
|
-
stmt.bindId(1, provenanceRelInstanceId);
|
|
270
|
-
nodeAssert(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW);
|
|
271
|
-
return stmt.getValue(0).getId();
|
|
272
|
-
});
|
|
273
|
-
const jsonProperties = args.forceOldRelationshipProvenanceMethod
|
|
274
|
-
? { targetRelInstanceId }
|
|
275
|
-
: { provenanceRelInstanceId };
|
|
276
|
-
const aspectProps = {
|
|
277
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
278
|
-
element: { id: elementId, relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName },
|
|
279
|
-
scope: { id: args.targetScopeElementId },
|
|
280
|
-
identifier: aspectIdentifier,
|
|
281
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
|
|
282
|
-
jsonProperties: JSON.stringify(jsonProperties),
|
|
228
|
+
version,
|
|
283
229
|
};
|
|
284
230
|
return aspectProps;
|
|
285
231
|
}
|
|
286
232
|
/** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
|
|
287
233
|
initElementProvenance(sourceElementId, targetElementId) {
|
|
288
234
|
return IModelTransformer.initElementProvenanceOptions(sourceElementId, targetElementId, {
|
|
289
|
-
// FIXME: deprecate isReverseSync option and instead detect from targetScopeElement provenance
|
|
290
235
|
isReverseSynchronization: !!this._options.isReverseSynchronization,
|
|
291
236
|
targetScopeElementId: this.targetScopeElementId,
|
|
292
237
|
sourceDb: this.sourceDb,
|
|
238
|
+
targetDb: this.targetDb,
|
|
293
239
|
});
|
|
294
240
|
}
|
|
295
241
|
/** Create an ExternalSourceAspectProps in a standard way for a Relationship in an iModel --> iModel transformations.
|
|
@@ -298,73 +244,32 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
298
244
|
* The ECInstanceId of the relationship in the target iModel will be stored in the JsonProperties of the ExternalSourceAspect.
|
|
299
245
|
*/
|
|
300
246
|
initRelationshipProvenance(sourceRelationship, targetRelInstanceId) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
get _synchronizationVersion() {
|
|
315
|
-
if (!this._cachedSynchronizationVersion) {
|
|
316
|
-
nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps was not set yet");
|
|
317
|
-
const version = this._options.isReverseSynchronization
|
|
318
|
-
? this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion
|
|
319
|
-
: this._targetScopeProvenanceProps.version;
|
|
320
|
-
nodeAssert(version !== undefined, "no version contained in target scope");
|
|
321
|
-
const [id, index] = version === ""
|
|
322
|
-
? ["", -1]
|
|
323
|
-
: version.split(";");
|
|
324
|
-
this._cachedSynchronizationVersion = { index: Number(index), id };
|
|
325
|
-
nodeAssert(!Number.isNaN(this._cachedSynchronizationVersion.index), "bad parse: invalid index in version");
|
|
326
|
-
}
|
|
327
|
-
return this._cachedSynchronizationVersion;
|
|
247
|
+
const targetRelationship = this.targetDb.relationships.getInstance(core_backend_1.ElementRefersToElements.classFullName, targetRelInstanceId);
|
|
248
|
+
const elementId = this._options.isReverseSynchronization ? sourceRelationship.sourceId : targetRelationship.sourceId;
|
|
249
|
+
const aspectIdentifier = this._options.isReverseSynchronization ? targetRelInstanceId : sourceRelationship.id;
|
|
250
|
+
const aspectProps = {
|
|
251
|
+
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
252
|
+
element: { id: elementId, relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName },
|
|
253
|
+
scope: { id: this.targetScopeElementId },
|
|
254
|
+
identifier: aspectIdentifier,
|
|
255
|
+
kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
|
|
256
|
+
jsonProperties: JSON.stringify({ targetRelInstanceId }),
|
|
257
|
+
};
|
|
258
|
+
aspectProps.id = this.queryExternalSourceAspectId(aspectProps);
|
|
259
|
+
return aspectProps;
|
|
328
260
|
}
|
|
329
|
-
|
|
330
|
-
* Make sure there are no conflicting other scope-type external source aspects on the *target scope element*,
|
|
331
|
-
* If there are none at all, insert one, then this must be a first synchronization.
|
|
332
|
-
* @returns the last synced version (changesetId) on the target scope's external source aspect,
|
|
333
|
-
* if this was a [BriefcaseDb]($backend)
|
|
334
|
-
*/
|
|
335
|
-
initScopeProvenance() {
|
|
261
|
+
validateScopeProvenance() {
|
|
336
262
|
const aspectProps = {
|
|
337
|
-
id: undefined,
|
|
338
|
-
version: undefined,
|
|
339
263
|
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
340
264
|
element: { id: this.targetScopeElementId, relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName },
|
|
341
265
|
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
342
|
-
identifier: this.
|
|
266
|
+
identifier: this._options.isReverseSynchronization ? this.targetDb.iModelId : this.sourceDb.iModelId,
|
|
343
267
|
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
344
|
-
jsonProperties: undefined,
|
|
345
268
|
};
|
|
346
|
-
|
|
347
|
-
// or reverseSyncVersion set correctly
|
|
348
|
-
const externalSource = this.queryScopeExternalSource(aspectProps, { getJsonProperties: true }); // this query includes "identifier"
|
|
349
|
-
aspectProps.id = externalSource.aspectId;
|
|
350
|
-
aspectProps.version = externalSource.version;
|
|
351
|
-
aspectProps.jsonProperties = externalSource.jsonProperties ? JSON.parse(externalSource.jsonProperties) : {};
|
|
269
|
+
aspectProps.id = this.queryExternalSourceAspectId(aspectProps); // this query includes "identifier"
|
|
352
270
|
if (undefined === aspectProps.id) {
|
|
353
|
-
aspectProps.version = ""; // empty since never before transformed. Will be updated in [[finalizeTransformation]]
|
|
354
|
-
aspectProps.jsonProperties = {
|
|
355
|
-
pendingReverseSyncChangesetIndices: [],
|
|
356
|
-
pendingSyncChangesetIndices: [],
|
|
357
|
-
reverseSyncVersion: "", // empty since never before transformed. Will be updated in first reverse sync
|
|
358
|
-
};
|
|
359
271
|
// this query does not include "identifier" to find possible conflicts
|
|
360
|
-
const sql = `
|
|
361
|
-
SELECT ECInstanceId
|
|
362
|
-
FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
363
|
-
WHERE Element.Id=:elementId
|
|
364
|
-
AND Scope.Id=:scopeId
|
|
365
|
-
AND Kind=:kind
|
|
366
|
-
LIMIT 1
|
|
367
|
-
`;
|
|
272
|
+
const sql = `SELECT ECInstanceId FROM ${core_backend_1.ExternalSourceAspect.classFullName} WHERE Element.Id=:elementId AND Scope.Id=:scopeId AND Kind=:kind LIMIT 1`;
|
|
368
273
|
const hasConflictingScope = this.provenanceDb.withPreparedStatement(sql, (statement) => {
|
|
369
274
|
statement.bindId("elementId", aspectProps.element.id);
|
|
370
275
|
statement.bindId("scopeId", aspectProps.scope.id); // this scope.id can never be invalid, we create it above
|
|
@@ -375,128 +280,46 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
375
280
|
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Provenance scope conflict");
|
|
376
281
|
}
|
|
377
282
|
if (!this._options.noProvenance) {
|
|
378
|
-
this.provenanceDb.elements.insertAspect(
|
|
379
|
-
...aspectProps,
|
|
380
|
-
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
381
|
-
});
|
|
283
|
+
this.provenanceDb.elements.insertAspect(aspectProps);
|
|
382
284
|
}
|
|
383
285
|
}
|
|
384
|
-
this._targetScopeProvenanceProps = aspectProps;
|
|
385
286
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
* May also return a reverseSyncVersion from json properties if requested
|
|
389
|
-
*/
|
|
390
|
-
queryScopeExternalSource(aspectProps, { getJsonProperties = false } = {}) {
|
|
391
|
-
const sql = `
|
|
392
|
-
SELECT ECInstanceId, Version
|
|
393
|
-
${getJsonProperties ? ", JsonProperties" : ""}
|
|
394
|
-
FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
395
|
-
WHERE Element.Id=:elementId
|
|
396
|
-
AND Scope.Id=:scopeId
|
|
397
|
-
AND Kind=:kind
|
|
398
|
-
AND Identifier=:identifier
|
|
399
|
-
LIMIT 1
|
|
400
|
-
`;
|
|
401
|
-
const emptyResult = { aspectId: undefined, version: undefined, jsonProperties: undefined };
|
|
287
|
+
queryExternalSourceAspectId(aspectProps) {
|
|
288
|
+
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`;
|
|
402
289
|
return this.provenanceDb.withPreparedStatement(sql, (statement) => {
|
|
403
290
|
statement.bindId("elementId", aspectProps.element.id);
|
|
404
291
|
if (aspectProps.scope === undefined)
|
|
405
|
-
return
|
|
292
|
+
return undefined; // return undefined instead of binding an invalid id
|
|
406
293
|
statement.bindId("scopeId", aspectProps.scope.id);
|
|
407
294
|
statement.bindString("kind", aspectProps.kind);
|
|
408
295
|
statement.bindString("identifier", aspectProps.identifier);
|
|
409
|
-
|
|
410
|
-
return emptyResult;
|
|
411
|
-
const aspectId = statement.getValue(0).getId();
|
|
412
|
-
const version = statement.getValue(1).getString();
|
|
413
|
-
const jsonProperties = getJsonProperties ? statement.getValue(2).getString() : undefined;
|
|
414
|
-
return { aspectId, version, jsonProperties };
|
|
296
|
+
return (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) ? statement.getValue(0).getId() : undefined;
|
|
415
297
|
});
|
|
416
298
|
}
|
|
417
|
-
/**
|
|
418
|
-
* Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
|
|
419
|
-
* and call a function for each one.
|
|
420
|
-
* @note provenance is done by federation guids where possible
|
|
421
|
-
* @note this may execute on each element more than once! Only use in cases where that is handled
|
|
422
|
-
*/
|
|
299
|
+
/** Iterate all matching ExternalSourceAspects in the provenance iModel (target unless reverse sync) and call a function for each one. */
|
|
423
300
|
static forEachTrackedElement(args) {
|
|
424
|
-
if (args.provenanceDb === args.provenanceSourceDb)
|
|
425
|
-
return;
|
|
426
301
|
if (!args.provenanceDb.containsClass(core_backend_1.ExternalSourceAspect.classFullName)) {
|
|
427
302
|
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadSchema, "The BisCore schema version of the target database is too old");
|
|
428
303
|
}
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
// iterate through sorted list of fed guids from both dbs to get the intersection
|
|
439
|
-
// NOTE: if we exposed the native attach database support,
|
|
440
|
-
// we could get the intersection of fed guids in one query, not sure if it would be faster
|
|
441
|
-
// OR we could do a raw sqlite query...
|
|
442
|
-
sourceDb.withStatement(elementIdByFedGuidQuery, (sourceStmt) => targetDb.withStatement(elementIdByFedGuidQuery, (targetStmt) => {
|
|
443
|
-
if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
444
|
-
return;
|
|
445
|
-
let sourceRow = sourceStmt.getRow();
|
|
446
|
-
if (targetStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
447
|
-
return;
|
|
448
|
-
let targetRow = targetStmt.getRow();
|
|
449
|
-
// NOTE: these comparisons rely upon the lowercase of the guid,
|
|
450
|
-
// and the fact that '0' < '9' < a' < 'f' in ascii/utf8
|
|
451
|
-
while (true) {
|
|
452
|
-
const currSourceRow = sourceRow, currTargetRow = targetRow;
|
|
453
|
-
if (currSourceRow.federationGuid !== undefined
|
|
454
|
-
&& currTargetRow.federationGuid !== undefined
|
|
455
|
-
&& currSourceRow.federationGuid === currTargetRow.federationGuid) {
|
|
456
|
-
// data flow direction is always sourceDb -> targetDb and it does not depend on where the explicit element provenance is stored
|
|
457
|
-
args.fn(sourceRow.id, targetRow.id);
|
|
458
|
-
}
|
|
459
|
-
if (currTargetRow.federationGuid === undefined
|
|
460
|
-
|| (currSourceRow.federationGuid !== undefined
|
|
461
|
-
&& currSourceRow.federationGuid >= currTargetRow.federationGuid)) {
|
|
462
|
-
if (targetStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
463
|
-
return;
|
|
464
|
-
targetRow = targetStmt.getRow();
|
|
304
|
+
const sql = `SELECT Identifier,Element.Id FROM ${core_backend_1.ExternalSourceAspect.classFullName} WHERE Scope.Id=:scopeId AND Kind=:kind`;
|
|
305
|
+
args.provenanceDb.withPreparedStatement(sql, (statement) => {
|
|
306
|
+
statement.bindId("scopeId", args.targetScopeElementId);
|
|
307
|
+
statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
308
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
309
|
+
const aspectIdentifier = statement.getValue(0).getString(); // ExternalSourceAspect.Identifier is of type string
|
|
310
|
+
const elementId = statement.getValue(1).getId();
|
|
311
|
+
if (args.isReverseSynchronization) {
|
|
312
|
+
args.fn(elementId, aspectIdentifier); // provenance coming from the sourceDb
|
|
465
313
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
&& currSourceRow.federationGuid <= currTargetRow.federationGuid)) {
|
|
469
|
-
if (sourceStmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
470
|
-
return;
|
|
471
|
-
sourceRow = sourceStmt.getRow();
|
|
314
|
+
else {
|
|
315
|
+
args.fn(aspectIdentifier, elementId); // provenance coming from the targetDb
|
|
472
316
|
}
|
|
473
317
|
}
|
|
474
|
-
}));
|
|
475
|
-
// query for provenanceDb
|
|
476
|
-
const provenanceAspectsQuery = `
|
|
477
|
-
SELECT esa.Identifier, Element.Id
|
|
478
|
-
FROM bis.ExternalSourceAspect esa
|
|
479
|
-
WHERE Scope.Id=:scopeId
|
|
480
|
-
AND Kind=:kind
|
|
481
|
-
`;
|
|
482
|
-
// Technically this will a second time call the function (as documented) on
|
|
483
|
-
// victims of the old provenance method that have both fedguids and an inserted aspect.
|
|
484
|
-
// But this is a private function with one known caller where that doesn't matter
|
|
485
|
-
args.provenanceDb.withPreparedStatement(provenanceAspectsQuery, (stmt) => {
|
|
486
|
-
const runFnInDataFlowDirection = (sourceId, targetId) => args.isReverseSynchronization ? args.fn(sourceId, targetId) : args.fn(targetId, sourceId);
|
|
487
|
-
stmt.bindId("scopeId", args.targetScopeElementId);
|
|
488
|
-
stmt.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
489
|
-
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
490
|
-
// ExternalSourceAspect.Identifier is of type string
|
|
491
|
-
const aspectIdentifier = stmt.getValue(0).getString();
|
|
492
|
-
const elementId = stmt.getValue(1).getId();
|
|
493
|
-
runFnInDataFlowDirection(elementId, aspectIdentifier);
|
|
494
|
-
}
|
|
495
318
|
});
|
|
496
319
|
}
|
|
497
320
|
forEachTrackedElement(fn) {
|
|
498
321
|
return IModelTransformer.forEachTrackedElement({
|
|
499
|
-
provenanceSourceDb: this.
|
|
322
|
+
provenanceSourceDb: this._options.isReverseSynchronization ? this.sourceDb : this.targetDb,
|
|
500
323
|
provenanceDb: this.provenanceDb,
|
|
501
324
|
targetScopeElementId: this.targetScopeElementId,
|
|
502
325
|
isReverseSynchronization: !!this._options.isReverseSynchronization,
|
|
@@ -514,366 +337,105 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
514
337
|
this.context.remapElement(sourceElementId, targetElementId);
|
|
515
338
|
});
|
|
516
339
|
if (args)
|
|
517
|
-
return this.
|
|
340
|
+
return this.remapDeletedSourceElements(args);
|
|
518
341
|
}
|
|
519
|
-
/**
|
|
520
|
-
*
|
|
521
|
-
*
|
|
342
|
+
/** When processing deleted elements in a reverse synchronization, the [[provenanceDb]] (usually a branch iModel) has already
|
|
343
|
+
* deleted the [ExternalSourceAspect]($backend)s that tell us which elements in the reverse synchronization target (usually
|
|
344
|
+
* a master iModel) should be deleted. We must use the changesets to get the values of those before they were deleted.
|
|
522
345
|
*/
|
|
523
|
-
async
|
|
346
|
+
async remapDeletedSourceElements(args) {
|
|
524
347
|
// we need a connected iModel with changes to remap elements with deletions
|
|
525
|
-
|
|
526
|
-
const noChanges = this._synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
527
|
-
if (notConnectedModel || noChanges)
|
|
348
|
+
if (this.sourceDb.iTwinId === undefined)
|
|
528
349
|
return;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
ic.ChangedInstance.Id AS InstanceId,
|
|
552
|
-
NULL AS InstId2, -- need these columns for relationship ends in the unioned query
|
|
553
|
-
NULL AS InstId3,
|
|
554
|
-
ec.FederationGuid AS FedGuid,
|
|
555
|
-
NULL AS FedGuid2,
|
|
556
|
-
ic.ChangedInstance.ClassId AS ClassId
|
|
557
|
-
${queryCanAccessProvenance ? `
|
|
558
|
-
/*
|
|
559
|
-
-- can't coalesce these due to a bug, so do it in JS
|
|
560
|
-
, coalesce(
|
|
561
|
-
IIF(esa.Scope.Id=:targetScopeElement, esa.Identifier, NULL),
|
|
562
|
-
IIF(esac.Scope.Id=:targetScopeElement, esac.Identifier, NULL)
|
|
563
|
-
) AS Identifier1
|
|
564
|
-
*/
|
|
565
|
-
, CASE WHEN esa.Scope.Id = ${this.targetScopeElementId} THEN esa.Identifier ELSE NULL END AS Identifier1A
|
|
566
|
-
-- FIXME: using :targetScopeElement parameter in this second potential identifier breaks ecsql
|
|
567
|
-
, CASE WHEN esac.Scope.Id = ${this.targetScopeElementId} THEN esac.Identifier ELSE NULL END AS Identifier1B
|
|
568
|
-
, NULL AS Identifier2A
|
|
569
|
-
, NULL AS Identifier2B
|
|
570
|
-
` : ""}
|
|
571
|
-
FROM ecchange.change.InstanceChange ic
|
|
572
|
-
LEFT JOIN bis.Element.Changes(:changeSummaryId, 'BeforeDelete') ec
|
|
573
|
-
ON ic.ChangedInstance.Id=ec.ECInstanceId
|
|
574
|
-
${queryCanAccessProvenance ? `
|
|
575
|
-
LEFT JOIN bis.ExternalSourceAspect esa
|
|
576
|
-
ON ec.ECInstanceId=esa.Element.Id
|
|
577
|
-
LEFT JOIN bis.ExternalSourceAspect.Changes(:changeSummaryId, 'BeforeDelete') esac
|
|
578
|
-
ON ec.ECInstanceId=esac.Element.Id
|
|
579
|
-
` : ""}
|
|
580
|
-
WHERE ic.OpCode=:opDelete
|
|
581
|
-
AND ic.Summary.Id=:changeSummaryId
|
|
582
|
-
AND ic.ChangedInstance.ClassId IS (BisCore.Element)
|
|
583
|
-
|
|
584
|
-
UNION ALL
|
|
585
|
-
|
|
586
|
-
SELECT
|
|
587
|
-
0 AS IsElemNotRel,
|
|
588
|
-
ic.ChangedInstance.Id AS InstanceId,
|
|
589
|
-
coalesce(se.ECInstanceId, sec.ECInstanceId) AS InstId2,
|
|
590
|
-
coalesce(te.ECInstanceId, tec.ECInstanceId) AS InstId3,
|
|
591
|
-
coalesce(se.FederationGuid, sec.FederationGuid) AS FedGuid1,
|
|
592
|
-
coalesce(te.FederationGuid, tec.FederationGuid) AS FedGuid2,
|
|
593
|
-
ic.ChangedInstance.ClassId AS ClassId
|
|
594
|
-
${queryCanAccessProvenance ? `
|
|
595
|
-
, sesa.Identifier AS Identifier1A
|
|
596
|
-
, sesac.Identifier AS Identifier1B
|
|
597
|
-
, tesa.Identifier AS Identifier2A
|
|
598
|
-
, tesac.Identifier AS Identifier2B
|
|
599
|
-
` : ""}
|
|
600
|
-
FROM ecchange.change.InstanceChange ic
|
|
601
|
-
LEFT JOIN bis.ElementRefersToElements.Changes(:changeSummaryId, 'BeforeDelete') ertec
|
|
602
|
-
ON ic.ChangedInstance.Id=ertec.ECInstanceId
|
|
603
|
-
-- FIXME: test a deletion of both an element and a relationship at the same time
|
|
604
|
-
LEFT JOIN bis.Element se
|
|
605
|
-
ON se.ECInstanceId=ertec.SourceECInstanceId
|
|
606
|
-
LEFT JOIN bis.Element te
|
|
607
|
-
ON te.ECInstanceId=ertec.TargetECInstanceId
|
|
608
|
-
LEFT JOIN bis.Element.Changes(:changeSummaryId, 'BeforeDelete') sec
|
|
609
|
-
ON sec.ECInstanceId=ertec.SourceECInstanceId
|
|
610
|
-
LEFT JOIN bis.Element.Changes(:changeSummaryId, 'BeforeDelete') tec
|
|
611
|
-
ON tec.ECInstanceId=ertec.TargetECInstanceId
|
|
612
|
-
${queryCanAccessProvenance ? `
|
|
613
|
-
-- NOTE: need to join on both se/te and sec/tec incase the element was deleted
|
|
614
|
-
LEFT JOIN bis.ExternalSourceAspect sesa
|
|
615
|
-
ON se.ECInstanceId=sesa.Element.Id -- don't use *esac*.Identifier because it's a string
|
|
616
|
-
LEFT JOIN bis.ExternalSourceAspect.Changes(:changeSummaryId, 'BeforeDelete') sesac
|
|
617
|
-
ON sec.ECInstanceId=sesac.Element.Id
|
|
618
|
-
LEFT JOIN bis.ExternalSourceAspect tesa
|
|
619
|
-
ON te.ECInstanceId=tesa.Element.Id
|
|
620
|
-
LEFT JOIN bis.ExternalSourceAspect.Changes(:changeSummaryId, 'BeforeDelete') tesac
|
|
621
|
-
ON tec.ECInstanceId=tesac.Element.Id
|
|
622
|
-
` : ""}
|
|
623
|
-
WHERE ic.OpCode=:opDelete
|
|
624
|
-
AND ic.Summary.Id=:changeSummaryId
|
|
625
|
-
AND ic.ChangedInstance.ClassId IS (BisCore.ElementRefersToElements)
|
|
626
|
-
${queryCanAccessProvenance ? `
|
|
627
|
-
AND (sesa.Scope.Id=:targetScopeElement OR sesa.Scope.Id IS NULL)
|
|
628
|
-
AND (sesa.Kind='Relationship' OR sesa.Kind IS NULL)
|
|
629
|
-
AND (sesac.Scope.Id=:targetScopeElement OR sesac.Scope.Id IS NULL)
|
|
630
|
-
AND (sesac.Kind='Relationship' OR sesac.Kind IS NULL)
|
|
631
|
-
AND (tesa.Scope.Id=:targetScopeElement OR tesa.Scope.Id IS NULL)
|
|
632
|
-
AND (tesa.Kind='Relationship' OR tesa.Kind IS NULL)
|
|
633
|
-
AND (tesac.Scope.Id=:targetScopeElement OR tesac.Scope.Id IS NULL)
|
|
634
|
-
AND (tesac.Kind='Relationship' OR tesac.Kind IS NULL)
|
|
635
|
-
` : ""}
|
|
636
|
-
`;
|
|
637
|
-
for (const changeSummaryId of this._changeSummaryIds) {
|
|
638
|
-
// FIXME: test deletion in both forward and reverse sync
|
|
639
|
-
this.sourceDb.withPreparedStatement(deletedEntitySql, (stmt) => {
|
|
640
|
-
stmt.bindInteger("opDelete", core_common_1.ChangeOpCode.Delete);
|
|
641
|
-
if (queryCanAccessProvenance)
|
|
642
|
-
stmt.bindId("targetScopeElement", this.targetScopeElementId);
|
|
643
|
-
stmt.bindId("changeSummaryId", changeSummaryId);
|
|
644
|
-
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
645
|
-
const isElemNotRel = stmt.getValue(0).getBoolean();
|
|
646
|
-
const instId = stmt.getValue(1).getId();
|
|
647
|
-
if (isElemNotRel) {
|
|
648
|
-
const sourceElemFedGuid = stmt.getValue(4).getGuid();
|
|
649
|
-
// "Identifier" is a string, so null value returns '' which doesn't work with ??, and I don't like ||
|
|
650
|
-
let identifierValue;
|
|
651
|
-
// identifier must be coalesced in JS due to an ESCQL bug, so there are multiple columns
|
|
652
|
-
if (queryCanAccessProvenance) {
|
|
653
|
-
identifierValue = stmt.getValue(7);
|
|
654
|
-
if (identifierValue.isNull)
|
|
655
|
-
identifierValue = stmt.getValue(8);
|
|
656
|
-
}
|
|
657
|
-
// TODO: if I could attach the second db, will probably be much faster to get target id
|
|
658
|
-
// as part of the whole query rather than with _queryElemIdByFedGuid
|
|
659
|
-
const targetId = (queryCanAccessProvenance && identifierValue
|
|
660
|
-
&& !identifierValue.isNull
|
|
661
|
-
&& identifierValue.getString())
|
|
662
|
-
// maybe batching these queries would perform better but we should
|
|
663
|
-
// try to attach the second db and query both together anyway
|
|
664
|
-
|| (sourceElemFedGuid && this._queryElemIdByFedGuid(this.targetDb, sourceElemFedGuid))
|
|
665
|
-
// FIXME: describe why it's safe to assume nothing has been deleted in provenanceDb
|
|
666
|
-
|| this._queryProvenanceForElement(instId);
|
|
667
|
-
// since we are processing one changeset at a time, we can see local source deletes
|
|
668
|
-
// of entities that were never synced and can be safely ignored
|
|
669
|
-
const deletionNotInTarget = !targetId;
|
|
670
|
-
if (deletionNotInTarget)
|
|
671
|
-
continue;
|
|
672
|
-
this.context.remapElement(instId, targetId);
|
|
673
|
-
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
674
|
-
// In such case an entity update will be triggered and we no longer need to delete the entity.
|
|
675
|
-
if (alreadyImportedElementInserts.has(targetId)) {
|
|
676
|
-
this.exporter.sourceDbChanges?.element.deleteIds.delete(instId);
|
|
677
|
-
}
|
|
678
|
-
if (alreadyImportedModelInserts.has(targetId)) {
|
|
679
|
-
this.exporter.sourceDbChanges?.model.deleteIds.delete(instId);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
else { // is deleted relationship
|
|
683
|
-
const classFullName = stmt.getValue(6).getClassNameForClassId();
|
|
684
|
-
const [sourceIdInTarget, targetIdInTarget] = [
|
|
685
|
-
// identifier must be coalesced in JS due to an ESCQL bug, so there are multiple columns
|
|
686
|
-
{ guidColumn: 4, identifierColumns: { a: 7, b: 8 }, isTarget: false },
|
|
687
|
-
{ guidColumn: 5, identifierColumns: { a: 9, b: 10 }, isTarget: true },
|
|
688
|
-
].map(({ guidColumn, identifierColumns }) => {
|
|
689
|
-
const fedGuid = stmt.getValue(guidColumn).getGuid();
|
|
690
|
-
let identifierValue;
|
|
691
|
-
// identifier must be coalesced in JS due to an ESCQL bug, so there are multiple columns
|
|
692
|
-
if (queryCanAccessProvenance) {
|
|
693
|
-
identifierValue = stmt.getValue(identifierColumns.a);
|
|
694
|
-
if (identifierValue.isNull)
|
|
695
|
-
identifierValue = stmt.getValue(identifierColumns.b);
|
|
696
|
-
}
|
|
697
|
-
return ((queryCanAccessProvenance && identifierValue
|
|
698
|
-
// FIXME: this is really far from idiomatic, try to undo that
|
|
699
|
-
&& !identifierValue.isNull
|
|
700
|
-
&& identifierValue.getString())
|
|
701
|
-
// maybe batching these queries would perform better but we should
|
|
702
|
-
// try to attach the second db and query both together anyway
|
|
703
|
-
|| (fedGuid && this._queryElemIdByFedGuid(this.targetDb, fedGuid)));
|
|
704
|
-
});
|
|
705
|
-
// since we are processing one changeset at a time, we can see local source deletes
|
|
706
|
-
// of entities that were never synced and can be safely ignored
|
|
707
|
-
if (sourceIdInTarget && targetIdInTarget) {
|
|
708
|
-
this._deletedSourceRelationshipData.set(instId, {
|
|
709
|
-
classFullName,
|
|
710
|
-
sourceIdInTarget,
|
|
711
|
-
targetIdInTarget,
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
else {
|
|
715
|
-
// FIXME: describe why it's safe to assume nothing has been deleted in provenanceDb
|
|
716
|
-
const relProvenance = this._queryProvenanceForRelationship(instId, {
|
|
717
|
-
classFullName,
|
|
718
|
-
sourceId: stmt.getValue(2).getId(),
|
|
719
|
-
targetId: stmt.getValue(3).getId(),
|
|
720
|
-
});
|
|
721
|
-
if (relProvenance && relProvenance.relationshipId)
|
|
722
|
-
this._deletedSourceRelationshipData.set(instId, {
|
|
723
|
-
classFullName,
|
|
724
|
-
relId: relProvenance.relationshipId,
|
|
725
|
-
provenanceAspectId: relProvenance.aspectId,
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
// NEXT: remap sourceId and targetId to target, get provenance there
|
|
731
|
-
// NOTE: it is possible during a forward sync for the target to already have deleted
|
|
732
|
-
// something that the source deleted, in which case we can safely ignore the gone provenance
|
|
350
|
+
try {
|
|
351
|
+
const startChangesetIndexOrId = args.startChangeset?.index
|
|
352
|
+
?? args.startChangeset?.id
|
|
353
|
+
?? this.sourceDb.changeset.index
|
|
354
|
+
?? this.sourceDb.changeset.id;
|
|
355
|
+
const endChangesetId = this.sourceDb.changeset.id;
|
|
356
|
+
const [firstChangesetIndex, endChangesetIndex] = await Promise.all(([startChangesetIndexOrId, endChangesetId])
|
|
357
|
+
.map(async (indexOrId) => typeof indexOrId === "number"
|
|
358
|
+
? indexOrId
|
|
359
|
+
: core_backend_1.IModelHost.hubAccess
|
|
360
|
+
.queryChangeset({
|
|
361
|
+
iModelId: this.sourceDb.iModelId,
|
|
362
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
363
|
+
changeset: { id: indexOrId },
|
|
364
|
+
accessToken: args.accessToken,
|
|
365
|
+
})
|
|
366
|
+
.then((changeset) => changeset.index)));
|
|
367
|
+
const changesetIds = await core_backend_1.ChangeSummaryManager.createChangeSummaries({
|
|
368
|
+
accessToken: args.accessToken,
|
|
369
|
+
iModelId: this.sourceDb.iModelId,
|
|
370
|
+
iTwinId: this.sourceDb.iTwinId,
|
|
371
|
+
range: { first: firstChangesetIndex, end: endChangesetIndex },
|
|
733
372
|
});
|
|
373
|
+
core_backend_1.ChangeSummaryManager.attachChangeCache(this.sourceDb);
|
|
374
|
+
for (const changesetId of changesetIds) {
|
|
375
|
+
this.sourceDb.withPreparedStatement(`
|
|
376
|
+
SELECT esac.Element.Id, esac.Identifier
|
|
377
|
+
FROM ecchange.change.InstanceChange ic
|
|
378
|
+
JOIN BisCore.ExternalSourceAspect.Changes(:changesetId, 'BeforeDelete') esac
|
|
379
|
+
ON ic.ChangedInstance.Id=esac.ECInstanceId
|
|
380
|
+
WHERE ic.OpCode=:opcode
|
|
381
|
+
AND ic.Summary.Id=:changesetId
|
|
382
|
+
AND esac.Scope.Id=:targetScopeElementId
|
|
383
|
+
-- not yet documented ecsql feature to check class id
|
|
384
|
+
AND ic.ChangedInstance.ClassId IS (ONLY BisCore.ExternalSourceAspect)
|
|
385
|
+
`, (stmt) => {
|
|
386
|
+
stmt.bindInteger("opcode", core_common_1.ChangeOpCode.Delete);
|
|
387
|
+
stmt.bindInteger("changesetId", changesetId);
|
|
388
|
+
stmt.bindInteger("targetScopeElementId", this.targetScopeElementId);
|
|
389
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
390
|
+
const targetId = stmt.getValue(0).getId();
|
|
391
|
+
const sourceId = stmt.getValue(1).getString(); // BisCore.ExternalSourceAspect.Identifier stores a hex Id64String
|
|
392
|
+
// TODO: maybe delete and don't just remap
|
|
393
|
+
this.context.remapElement(targetId, sourceId);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
734
397
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
SELECT esa.Element.Id
|
|
739
|
-
FROM Bis.ExternalSourceAspect esa
|
|
740
|
-
WHERE esa.Kind=?
|
|
741
|
-
AND esa.Scope.Id=?
|
|
742
|
-
AND esa.Identifier=?
|
|
743
|
-
`, (stmt) => {
|
|
744
|
-
stmt.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
745
|
-
stmt.bindId(2, this.targetScopeElementId);
|
|
746
|
-
stmt.bindString(3, entityInProvenanceSourceId);
|
|
747
|
-
if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
748
|
-
return stmt.getValue(0).getId();
|
|
749
|
-
else
|
|
750
|
-
return undefined;
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
_queryProvenanceForRelationship(entityInProvenanceSourceId, sourceRelInfo) {
|
|
754
|
-
return this.provenanceDb.withPreparedStatement(`
|
|
755
|
-
SELECT
|
|
756
|
-
ECInstanceId,
|
|
757
|
-
JSON_EXTRACT(JsonProperties, '$.targetRelInstanceId'),
|
|
758
|
-
JSON_EXTRACT(JsonProperties, '$.provenanceRelInstanceId')
|
|
759
|
-
FROM Bis.ExternalSourceAspect
|
|
760
|
-
WHERE Kind=?
|
|
761
|
-
AND Scope.Id=?
|
|
762
|
-
AND Identifier=?
|
|
763
|
-
`, (stmt) => {
|
|
764
|
-
stmt.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
765
|
-
stmt.bindId(2, this.targetScopeElementId);
|
|
766
|
-
stmt.bindString(3, entityInProvenanceSourceId);
|
|
767
|
-
if (stmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
768
|
-
return undefined;
|
|
769
|
-
const aspectId = stmt.getValue(0).getId();
|
|
770
|
-
const provenanceRelInstIdVal = stmt.getValue(2);
|
|
771
|
-
const provenanceRelInstanceId = !provenanceRelInstIdVal.isNull
|
|
772
|
-
? provenanceRelInstIdVal.getString()
|
|
773
|
-
: this._queryTargetRelId(sourceRelInfo);
|
|
774
|
-
return {
|
|
775
|
-
aspectId,
|
|
776
|
-
relationshipId: provenanceRelInstanceId,
|
|
777
|
-
};
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
_queryTargetRelId(sourceRelInfo) {
|
|
781
|
-
const targetRelInfo = {
|
|
782
|
-
sourceId: this.context.findTargetElementId(sourceRelInfo.sourceId),
|
|
783
|
-
targetId: this.context.findTargetElementId(sourceRelInfo.targetId),
|
|
784
|
-
};
|
|
785
|
-
if (targetRelInfo.sourceId === undefined || targetRelInfo.targetId === undefined)
|
|
786
|
-
return undefined; // couldn't find an element, rel is invalid or deleted
|
|
787
|
-
return this.targetDb.withPreparedStatement(`
|
|
788
|
-
SELECT ECInstanceId
|
|
789
|
-
FROM bis.ElementRefersToElements
|
|
790
|
-
WHERE SourceECInstanceId=?
|
|
791
|
-
AND TargetECInstanceId=?
|
|
792
|
-
AND ECClassId=?
|
|
793
|
-
`, (stmt) => {
|
|
794
|
-
stmt.bindId(1, targetRelInfo.sourceId);
|
|
795
|
-
stmt.bindId(2, targetRelInfo.targetId);
|
|
796
|
-
stmt.bindId(3, this._targetClassNameToClassId(sourceRelInfo.classFullName));
|
|
797
|
-
if (stmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
798
|
-
return undefined;
|
|
799
|
-
return stmt.getValue(0).getId();
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
_targetClassNameToClassId(classFullName) {
|
|
803
|
-
let classId = this._targetClassNameToClassIdCache.get(classFullName);
|
|
804
|
-
if (classId === undefined) {
|
|
805
|
-
classId = this._getRelClassId(this.targetDb, classFullName);
|
|
806
|
-
this._targetClassNameToClassIdCache.set(classFullName, classId);
|
|
398
|
+
finally {
|
|
399
|
+
if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
|
|
400
|
+
core_backend_1.ChangeSummaryManager.detachChangeCache(this.sourceDb);
|
|
807
401
|
}
|
|
808
|
-
return classId;
|
|
809
|
-
}
|
|
810
|
-
// NOTE: this doesn't handle remapped element classes,
|
|
811
|
-
// but is only used for relationships rn
|
|
812
|
-
_getRelClassId(db, classFullName) {
|
|
813
|
-
return db.withPreparedStatement(`
|
|
814
|
-
SELECT c.ECInstanceId
|
|
815
|
-
FROM ECDbMeta.ECClassDef c
|
|
816
|
-
JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
|
|
817
|
-
WHERE s.Name=? AND c.Name=?
|
|
818
|
-
`, (stmt) => {
|
|
819
|
-
const [schemaName, className] = classFullName.split(".");
|
|
820
|
-
stmt.bindString(1, schemaName);
|
|
821
|
-
stmt.bindString(2, className);
|
|
822
|
-
if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
823
|
-
return stmt.getValue(0).getId();
|
|
824
|
-
(0, core_bentley_1.assert)(false, "relationship was not found");
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
_queryElemIdByFedGuid(db, fedGuid) {
|
|
828
|
-
return db.withPreparedStatement("SELECT ECInstanceId FROM Bis.Element WHERE FederationGuid=?", (stmt) => {
|
|
829
|
-
stmt.bindGuid(1, fedGuid);
|
|
830
|
-
if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
831
|
-
return stmt.getValue(0).getId();
|
|
832
|
-
else
|
|
833
|
-
return undefined;
|
|
834
|
-
});
|
|
835
402
|
}
|
|
836
403
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
837
404
|
* @note Not relevant for processChanges when change history is known.
|
|
838
405
|
*/
|
|
839
406
|
shouldDetectDeletes() {
|
|
840
|
-
// FIXME: all synchronizations should mark this as false
|
|
841
407
|
if (this._isFirstSynchronization)
|
|
842
408
|
return false; // not necessary the first time since there are no deletes to detect
|
|
843
409
|
if (this._options.isReverseSynchronization)
|
|
844
410
|
return false; // not possible for a reverse synchronization since provenance will be deleted when element is deleted
|
|
845
411
|
return true;
|
|
846
412
|
}
|
|
847
|
-
/**
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* @deprecated in 0.1.x. This method is only called during [[processAll]] when the option
|
|
851
|
-
* [[IModelTransformerOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
|
|
852
|
-
* necessary when using [[processChanges]] since changeset information is sufficient.
|
|
853
|
-
* @note you do not need to call this directly unless processing a subset of an iModel.
|
|
413
|
+
/** Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements in the source iModel.
|
|
414
|
+
* @see processChanges
|
|
415
|
+
* @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.
|
|
854
416
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
855
417
|
*/
|
|
856
418
|
async detectElementDeletes() {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
if (
|
|
876
|
-
|
|
419
|
+
if (this._options.isReverseSynchronization) {
|
|
420
|
+
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
|
|
421
|
+
}
|
|
422
|
+
const targetElementsToDelete = [];
|
|
423
|
+
this.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
424
|
+
if (undefined === this.sourceDb.elements.tryGetElementProps(sourceElementId)) {
|
|
425
|
+
// if the sourceElement is not found, then it must have been deleted, so propagate the delete to the target iModel
|
|
426
|
+
targetElementsToDelete.push(targetElementId);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
targetElementsToDelete.forEach((targetElementId) => {
|
|
430
|
+
try {
|
|
431
|
+
// TODO: make it possible to delete more elements at once to prevent redundant expensive
|
|
432
|
+
// element reference scanning
|
|
433
|
+
this.importer.deleteElement(targetElementId);
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
// ignore not found elements, iterative element tree deletion might have already deleted them
|
|
437
|
+
if (err.name !== "Not Found")
|
|
438
|
+
throw err;
|
|
877
439
|
}
|
|
878
440
|
});
|
|
879
441
|
}
|
|
@@ -900,53 +462,24 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
900
462
|
}
|
|
901
463
|
return targetElementProps;
|
|
902
464
|
}
|
|
903
|
-
// FIXME: this is a PoC, see if we minimize memory usage
|
|
904
|
-
_cacheSourceChanges() {
|
|
905
|
-
nodeAssert(this._changeSummaryIds && this._changeSummaryIds.length > 0, "should have changeset data by now");
|
|
906
|
-
this._hasElementChangedCache = new Set();
|
|
907
|
-
const query = `
|
|
908
|
-
SELECT
|
|
909
|
-
ic.ChangedInstance.Id AS InstId
|
|
910
|
-
FROM ecchange.change.InstanceChange ic
|
|
911
|
-
JOIN iModelChange.Changeset imc ON ic.Summary.Id=imc.Summary.Id
|
|
912
|
-
-- FIXME: do relationship entities also need this cache optimization?
|
|
913
|
-
WHERE ic.ChangedInstance.ClassId IS (BisCore.Element)
|
|
914
|
-
AND InVirtualSet(:changeSummaryIds, ic.Summary.Id)
|
|
915
|
-
-- ignore deleted, we take care of those in remapDeletedSourceEntities
|
|
916
|
-
-- include inserted since inserted code-colliding elements should be considered
|
|
917
|
-
-- a change so that the colliding element is exported to the target
|
|
918
|
-
AND ic.OpCode<>:opDelete
|
|
919
|
-
`;
|
|
920
|
-
// there is a single mega-query multi-join+coalescing hack that I used originally to get around
|
|
921
|
-
// only being able to run table.Changes() on one changeset at once, but sqlite only supports up to 64
|
|
922
|
-
// tables in a join. Need to talk to core about .Changes being able to take a set of changesets
|
|
923
|
-
// You can find this version in the `federation-guid-optimization-megaquery` branch
|
|
924
|
-
// I wouldn't use it unless we prove via profiling that it speeds things up significantly
|
|
925
|
-
// And even then let's first try scanning the raw changesets instead of applying them as these queries
|
|
926
|
-
// require
|
|
927
|
-
this.sourceDb.withPreparedStatement(query, (stmt) => {
|
|
928
|
-
stmt.bindInteger("opDelete", core_common_1.ChangeOpCode.Delete);
|
|
929
|
-
stmt.bindIdSet("changeSummaryIds", this._changeSummaryIds);
|
|
930
|
-
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
931
|
-
const instId = stmt.getValue(0).getId();
|
|
932
|
-
this._hasElementChangedCache.add(instId);
|
|
933
|
-
}
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
465
|
/** Returns true if a change within sourceElement is detected.
|
|
937
466
|
* @param sourceElement The Element from the source iModel
|
|
938
467
|
* @param targetElementId The Element from the target iModel to compare against.
|
|
939
468
|
* @note A subclass can override this method to provide custom change detection behavior.
|
|
940
469
|
*/
|
|
941
|
-
hasElementChanged(sourceElement,
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
470
|
+
hasElementChanged(sourceElement, targetElementId) {
|
|
471
|
+
const sourceAspects = this.targetDb.elements.getAspects(targetElementId, core_backend_1.ExternalSourceAspect.classFullName);
|
|
472
|
+
for (const sourceAspect of sourceAspects) {
|
|
473
|
+
if (sourceAspect.scope === undefined) // if the scope was lost, we can't correlate so assume it changed
|
|
474
|
+
return true;
|
|
475
|
+
if (sourceAspect.identifier === sourceElement.id &&
|
|
476
|
+
sourceAspect.scope.id === this.targetScopeElementId &&
|
|
477
|
+
sourceAspect.kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
|
|
478
|
+
const lastModifiedTime = sourceElement.iModel.elements.queryLastModifiedTime(sourceElement.id);
|
|
479
|
+
return lastModifiedTime !== sourceAspect.version;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return true;
|
|
950
483
|
}
|
|
951
484
|
static transformCallbackFor(transformer, entity) {
|
|
952
485
|
if (entity instanceof core_backend_1.Element)
|
|
@@ -1136,68 +669,51 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1136
669
|
targetElementId = this.context.findTargetElementId(sourceElement.id);
|
|
1137
670
|
targetElementProps = this.onTransformElement(sourceElement);
|
|
1138
671
|
}
|
|
1139
|
-
// if an existing remapping was not yet found, check by FederationGuid
|
|
1140
|
-
if (this.context.isBetweenIModels && !core_bentley_1.Id64.isValid(targetElementId) && sourceElement.federationGuid !== undefined) {
|
|
1141
|
-
targetElementId = this._queryElemIdByFedGuid(this.targetDb, sourceElement.federationGuid) ?? core_bentley_1.Id64.invalid;
|
|
1142
|
-
if (core_bentley_1.Id64.isValid(targetElementId))
|
|
1143
|
-
this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found
|
|
1144
|
-
}
|
|
1145
672
|
// if an existing remapping was not yet found, check by Code as long as the CodeScope is valid (invalid means a missing reference so not worth checking)
|
|
1146
|
-
if (!core_bentley_1.Id64.
|
|
1147
|
-
// respond the same way to undefined code value as the @see Code class, but don't use that class because
|
|
673
|
+
if (!core_bentley_1.Id64.isValidId64(targetElementId) && core_bentley_1.Id64.isValidId64(targetElementProps.code.scope)) {
|
|
674
|
+
// respond the same way to undefined code value as the @see Code class, but don't use that class because it trims
|
|
1148
675
|
// whitespace from the value, and there are iModels out there with untrimmed whitespace that we ought not to trim
|
|
1149
676
|
targetElementProps.code.value = targetElementProps.code.value ?? "";
|
|
1150
|
-
|
|
1151
|
-
if (undefined !==
|
|
1152
|
-
const
|
|
1153
|
-
if (
|
|
1154
|
-
targetElementId = maybeTargetElementId;
|
|
677
|
+
targetElementId = this.targetDb.elements.queryElementIdByCode(targetElementProps.code);
|
|
678
|
+
if (undefined !== targetElementId) {
|
|
679
|
+
const targetElement = this.targetDb.elements.getElement(targetElementId);
|
|
680
|
+
if (targetElement.classFullName === targetElementProps.classFullName) { // ensure code remapping doesn't change the target class
|
|
1155
681
|
this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found by Code
|
|
1156
682
|
}
|
|
1157
683
|
else {
|
|
684
|
+
targetElementId = undefined;
|
|
1158
685
|
targetElementProps.code = core_common_1.Code.createEmpty(); // clear out invalid code
|
|
1159
686
|
}
|
|
1160
687
|
}
|
|
1161
688
|
}
|
|
1162
|
-
if (
|
|
1163
|
-
|
|
689
|
+
if (undefined !== targetElementId && core_bentley_1.Id64.isValidId64(targetElementId)) {
|
|
690
|
+
// compare LastMod of sourceElement to ExternalSourceAspect of targetElement to see there are changes to import
|
|
691
|
+
if (!this.hasElementChanged(sourceElement, targetElementId)) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
1164
695
|
this.collectUnmappedReferences(sourceElement);
|
|
1165
|
-
//
|
|
1166
|
-
|
|
1167
|
-
=
|
|
1168
|
-
|
|
1169
|
-
: undefined;
|
|
696
|
+
// TODO: untangle targetElementId state...
|
|
697
|
+
if (targetElementId === core_bentley_1.Id64.invalid)
|
|
698
|
+
targetElementId = undefined;
|
|
699
|
+
targetElementProps.id = targetElementId; // targetElementId will be valid (indicating update) or undefined (indicating insert)
|
|
1170
700
|
if (!this._options.wasSourceIModelCopiedToTarget) {
|
|
1171
701
|
this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
|
|
1172
702
|
}
|
|
1173
703
|
this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
|
|
1174
704
|
// now that we've mapped this elem we can fix unmapped references to it
|
|
1175
705
|
this.resolvePendingReferences(sourceElement);
|
|
1176
|
-
// the transformer does not currently 'split' or 'join' any elements, therefore, it does not
|
|
1177
|
-
// insert external source aspects because federation guids are sufficient for this.
|
|
1178
|
-
// Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
|
|
1179
|
-
// when splitting/joining elements
|
|
1180
|
-
// physical consolidation is an example of a 'joining' transform
|
|
1181
|
-
// FIXME: document this externally!
|
|
1182
|
-
// verify at finalization time that we don't lose provenance on new elements
|
|
1183
|
-
// make public and improve `initElementProvenance` API for usage by consolidators
|
|
1184
706
|
if (!this._options.noProvenance) {
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
|
|
1193
|
-
}
|
|
1194
|
-
else {
|
|
1195
|
-
aspectProps.id = aspectId;
|
|
1196
|
-
this.provenanceDb.elements.updateAspect(aspectProps);
|
|
1197
|
-
}
|
|
1198
|
-
provenance = aspectProps;
|
|
707
|
+
const aspectProps = this.initElementProvenance(sourceElement.id, targetElementProps.id);
|
|
708
|
+
let aspectId = this.queryExternalSourceAspectId(aspectProps);
|
|
709
|
+
if (aspectId === undefined) {
|
|
710
|
+
aspectId = this.provenanceDb.elements.insertAspect(aspectProps);
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
this.provenanceDb.elements.updateAspect(aspectProps);
|
|
1199
714
|
}
|
|
1200
|
-
|
|
715
|
+
aspectProps.id = aspectId;
|
|
716
|
+
this.markLastProvenance(aspectProps, { isRelationship: false });
|
|
1201
717
|
}
|
|
1202
718
|
}
|
|
1203
719
|
resolvePendingReferences(entity) {
|
|
@@ -1235,11 +751,50 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1235
751
|
onDeleteModel(sourceModelId) {
|
|
1236
752
|
// It is possible and apparently occasionally sensical to delete a model without deleting its underlying element.
|
|
1237
753
|
// - If only the model is deleted, [[initFromExternalSourceAspects]] will have already remapped the underlying element since it still exists.
|
|
1238
|
-
// - If both were deleted, [[
|
|
754
|
+
// - If both were deleted, [[remapDeletedSourceElements]] will find and remap the deleted element making this operation valid
|
|
1239
755
|
const targetModelId = this.context.findTargetElementId(sourceModelId);
|
|
1240
|
-
if (core_bentley_1.Id64.isValidId64(targetModelId))
|
|
756
|
+
if (!core_bentley_1.Id64.isValidId64(targetModelId))
|
|
757
|
+
return;
|
|
758
|
+
if (this.exporter.sourceDbChanges?.element.deleteIds.has(sourceModelId)) {
|
|
759
|
+
const isDefinitionPartition = this.targetDb.withPreparedStatement(`
|
|
760
|
+
SELECT 1
|
|
761
|
+
FROM bis.DefinitionPartition
|
|
762
|
+
WHERE ECInstanceId=?
|
|
763
|
+
`, (stmt) => {
|
|
764
|
+
stmt.bindId(1, targetModelId);
|
|
765
|
+
const val = stmt.step();
|
|
766
|
+
switch (val) {
|
|
767
|
+
case core_bentley_1.DbResult.BE_SQLITE_ROW: return true;
|
|
768
|
+
case core_bentley_1.DbResult.BE_SQLITE_DONE: return false;
|
|
769
|
+
default: (0, core_bentley_1.assert)(false, `unexpected db result: '${stmt}'`);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
if (isDefinitionPartition) {
|
|
773
|
+
// Skipping model deletion because model's partition will also be deleted.
|
|
774
|
+
// It expects that model will be present and will fail if it's missing.
|
|
775
|
+
// Model will be deleted when its partition will be deleted.
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
try {
|
|
1241
780
|
this.importer.deleteModel(targetModelId);
|
|
1242
781
|
}
|
|
782
|
+
catch (error) {
|
|
783
|
+
const isDeletionProhibitedErr = error instanceof core_common_1.IModelError && (error.errorNumber === core_bentley_1.IModelStatus.DeletionProhibited || error.errorNumber === core_bentley_1.IModelStatus.ForeignKeyConstraint);
|
|
784
|
+
if (!isDeletionProhibitedErr)
|
|
785
|
+
throw error;
|
|
786
|
+
// Transformer tries to delete models before it deletes elements. Definition models cannot be deleted unless all of their modeled elements are deleted first.
|
|
787
|
+
// In case a definition model needs to be deleted we need to skip it for now and register its modeled partition for deletion.
|
|
788
|
+
// The `OnDeleteElement` calls `DeleteElementTree` Which deletes the model together with its partition after deleting all of the modeled elements.
|
|
789
|
+
this.scheduleModeledPartitionDeletion(sourceModelId);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/** Schedule modeled partition deletion */
|
|
793
|
+
scheduleModeledPartitionDeletion(sourceModelId) {
|
|
794
|
+
const deletedElements = this.exporter.sourceDbChanges?.element.deleteIds;
|
|
795
|
+
if (!deletedElements.has(sourceModelId)) {
|
|
796
|
+
deletedElements.add(sourceModelId);
|
|
797
|
+
}
|
|
1243
798
|
}
|
|
1244
799
|
/** Cause the model container, contents, and sub-models to be exported from the source iModel and imported into the target iModel.
|
|
1245
800
|
* @param sourceModeledElementId Import this [Model]($backend) from the source IModelDb.
|
|
@@ -1311,68 +866,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1311
866
|
* @deprecated in 3.x. This method is no longer necessary since the transformer no longer needs to defer elements
|
|
1312
867
|
*/
|
|
1313
868
|
async processDeferredElements(_numRetries = 3) { }
|
|
1314
|
-
/** called at the end ([[finalizeTransformation]]) of a transformation,
|
|
1315
|
-
* updates the target scope element to say that transformation up through the
|
|
1316
|
-
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1317
|
-
* during the transformation as "pending synchronization changeset indices"
|
|
1318
|
-
*
|
|
1319
|
-
* You generally should not call this function yourself and use [[processChanges]] instead.
|
|
1320
|
-
* It is public for unsupported use cases of custom synchronization transforms.
|
|
1321
|
-
* @note if you are not running processChanges in this transformation, this will fail
|
|
1322
|
-
* without setting the `force` option to `true`
|
|
1323
|
-
*/
|
|
1324
|
-
updateSynchronizationVersion({ force = false } = {}) {
|
|
1325
|
-
if (!force && (this._sourceChangeDataState !== "has-changes" && !this._isFirstSynchronization))
|
|
1326
|
-
return;
|
|
1327
|
-
nodeAssert(this._targetScopeProvenanceProps);
|
|
1328
|
-
const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
|
|
1329
|
-
const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
|
|
1330
|
-
if (this._isFirstSynchronization) {
|
|
1331
|
-
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1332
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion = targetVersion;
|
|
1333
|
-
}
|
|
1334
|
-
else if (this._options.isReverseSynchronization) {
|
|
1335
|
-
const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
|
|
1336
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
|
|
1337
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion = sourceVersion;
|
|
1338
|
-
}
|
|
1339
|
-
else if (!this._options.isReverseSynchronization) {
|
|
1340
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
|
|
1341
|
-
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1342
|
-
}
|
|
1343
|
-
if (this._isSynchronization) {
|
|
1344
|
-
(0, core_bentley_1.assert)(this.targetDb.changeset.index !== undefined && this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
|
|
1345
|
-
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
1346
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1347
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1348
|
-
const [syncChangesetsToClear, syncChangesetsToUpdate] = this._isReverseSynchronization
|
|
1349
|
-
? [jsonProps.pendingReverseSyncChangesetIndices, jsonProps.pendingSyncChangesetIndices]
|
|
1350
|
-
: [jsonProps.pendingSyncChangesetIndices, jsonProps.pendingReverseSyncChangesetIndices];
|
|
1351
|
-
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1352
|
-
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1353
|
-
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1354
|
-
// stuff to it which would break future synchronizations
|
|
1355
|
-
// FIXME: force save for the user to prevent that
|
|
1356
|
-
for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1357
|
-
syncChangesetsToUpdate.push(i);
|
|
1358
|
-
syncChangesetsToClear.length = 0;
|
|
1359
|
-
// if reverse sync then we may have received provenance changes which should be marked as sync changes
|
|
1360
|
-
if (this._isReverseSynchronization) {
|
|
1361
|
-
nodeAssert(this.sourceDb.changeset.index, "changeset didn't exist");
|
|
1362
|
-
for (let i = this._startingChangesetIndices.source + 1; i <= this.sourceDb.changeset.index + 1; i++)
|
|
1363
|
-
jsonProps.pendingReverseSyncChangesetIndices.push(i);
|
|
1364
|
-
}
|
|
1365
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `new pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1366
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `new pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1367
|
-
}
|
|
1368
|
-
this.provenanceDb.elements.updateAspect({
|
|
1369
|
-
...this._targetScopeProvenanceProps,
|
|
1370
|
-
jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1373
|
-
// FIXME: is this necessary when manually using lowlevel transform APIs?
|
|
1374
869
|
finalizeTransformation() {
|
|
1375
|
-
this.updateSynchronizationVersion();
|
|
1376
870
|
if (this._partiallyCommittedEntities.size > 0) {
|
|
1377
871
|
core_bentley_1.Logger.logWarning(loggerCategory, [
|
|
1378
872
|
"The following elements were never fully resolved:",
|
|
@@ -1384,11 +878,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1384
878
|
partiallyCommittedElem.forceComplete();
|
|
1385
879
|
}
|
|
1386
880
|
}
|
|
1387
|
-
//
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
881
|
+
// this internal is guaranteed stable for just transformer usage
|
|
882
|
+
/* eslint-disable @itwin/no-internal */
|
|
883
|
+
if ("codeValueBehavior" in this.sourceDb) {
|
|
884
|
+
this.sourceDb.codeValueBehavior = "trim-unicode-whitespace";
|
|
885
|
+
this.targetDb.codeValueBehavior = "trim-unicode-whitespace";
|
|
1391
886
|
}
|
|
887
|
+
/* eslint-enable @itwin/no-internal */
|
|
1392
888
|
}
|
|
1393
889
|
/** Imports all relationships that subclass from the specified base class.
|
|
1394
890
|
* @param baseRelClassFullName The specified base relationship class.
|
|
@@ -1406,52 +902,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1406
902
|
* This override calls [[onTransformRelationship]] and then [IModelImporter.importRelationship]($transformer) to update the target iModel.
|
|
1407
903
|
*/
|
|
1408
904
|
onExportRelationship(sourceRelationship) {
|
|
1409
|
-
const sourceFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.sourceId);
|
|
1410
|
-
const targetFedGuid = queryElemFedGuid(this.sourceDb, sourceRelationship.targetId);
|
|
1411
905
|
const targetRelationshipProps = this.onTransformRelationship(sourceRelationship);
|
|
1412
906
|
const targetRelationshipInstanceId = this.importer.importRelationship(targetRelationshipProps);
|
|
1413
|
-
if (!this._options.noProvenance && core_bentley_1.Id64.
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
if (!provenance) {
|
|
1418
|
-
const aspectProps = this.initRelationshipProvenance(sourceRelationship, targetRelationshipInstanceId);
|
|
1419
|
-
aspectProps.id = this.queryScopeExternalSource(aspectProps).aspectId;
|
|
1420
|
-
if (undefined === aspectProps.id) {
|
|
1421
|
-
aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
|
|
1422
|
-
}
|
|
1423
|
-
provenance = aspectProps;
|
|
907
|
+
if (!this._options.noProvenance && core_bentley_1.Id64.isValidId64(targetRelationshipInstanceId)) {
|
|
908
|
+
const aspectProps = this.initRelationshipProvenance(sourceRelationship, targetRelationshipInstanceId);
|
|
909
|
+
if (undefined === aspectProps.id) {
|
|
910
|
+
aspectProps.id = this.provenanceDb.elements.insertAspect(aspectProps);
|
|
1424
911
|
}
|
|
1425
|
-
|
|
912
|
+
(0, core_bentley_1.assert)(aspectProps.id !== undefined);
|
|
913
|
+
this.markLastProvenance(aspectProps, { isRelationship: true });
|
|
1426
914
|
}
|
|
1427
915
|
}
|
|
1428
916
|
/** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
|
|
1429
917
|
* This override propagates the delete to the target iModel via [IModelImporter.deleteRelationship]($transformer).
|
|
1430
918
|
*/
|
|
1431
919
|
onDeleteRelationship(sourceRelInstanceId) {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1449
|
-
if (deletedRelData.provenanceAspectId) {
|
|
1450
|
-
this.provenanceDb.elements.deleteAspect(deletedRelData.provenanceAspectId);
|
|
1451
|
-
}
|
|
920
|
+
const sql = `SELECT ECInstanceId,JsonProperties FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect` +
|
|
921
|
+
` WHERE aspect.Scope.Id=:scopeId AND aspect.Kind=:kind AND aspect.Identifier=:identifier LIMIT 1`;
|
|
922
|
+
this.targetDb.withPreparedStatement(sql, (statement) => {
|
|
923
|
+
statement.bindId("scopeId", this.targetScopeElementId);
|
|
924
|
+
statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
925
|
+
statement.bindString("identifier", sourceRelInstanceId);
|
|
926
|
+
if (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
927
|
+
const json = JSON.parse(statement.getValue(1).getString());
|
|
928
|
+
if (undefined !== json.targetRelInstanceId) {
|
|
929
|
+
const targetRelationship = this.targetDb.relationships.tryGetInstance(core_backend_1.ElementRefersToElements.classFullName, json.targetRelInstanceId);
|
|
930
|
+
if (targetRelationship) {
|
|
931
|
+
this.importer.deleteRelationship(targetRelationship.toJSON());
|
|
932
|
+
}
|
|
933
|
+
this.targetDb.elements.deleteAspect(statement.getValue(0).getId());
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
});
|
|
1452
937
|
}
|
|
1453
938
|
/** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
|
|
1454
|
-
* @deprecated
|
|
1455
939
|
* @see processChanges
|
|
1456
940
|
* @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.
|
|
1457
941
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
@@ -1461,20 +945,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1461
945
|
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
|
|
1462
946
|
}
|
|
1463
947
|
const aspectDeleteIds = [];
|
|
1464
|
-
const sql = `
|
|
1465
|
-
SELECT ECInstanceId, Identifier, JsonProperties
|
|
1466
|
-
FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect
|
|
1467
|
-
WHERE aspect.Scope.Id=:scopeId
|
|
1468
|
-
AND aspect.Kind=:kind
|
|
1469
|
-
`;
|
|
948
|
+
const sql = `SELECT ECInstanceId,Identifier,JsonProperties FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect WHERE aspect.Scope.Id=:scopeId AND aspect.Kind=:kind`;
|
|
1470
949
|
await this.targetDb.withPreparedStatement(sql, async (statement) => {
|
|
1471
950
|
statement.bindId("scopeId", this.targetScopeElementId);
|
|
1472
951
|
statement.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
1473
952
|
while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
1474
953
|
const sourceRelInstanceId = core_bentley_1.Id64.fromJSON(statement.getValue(1).getString());
|
|
1475
954
|
if (undefined === this.sourceDb.relationships.tryGetInstanceProps(core_backend_1.ElementRefersToElements.classFullName, sourceRelInstanceId)) {
|
|
1476
|
-
// FIXME: make sure matches new provenance-based method
|
|
1477
|
-
// FIXME: use sql JSON_EXTRACT
|
|
1478
955
|
const json = JSON.parse(statement.getValue(2).getString());
|
|
1479
956
|
if (undefined !== json.targetRelInstanceId) {
|
|
1480
957
|
const targetRelationship = this.targetDb.relationships.getInstance(core_backend_1.ElementRefersToElements.classFullName, json.targetRelInstanceId);
|
|
@@ -1496,7 +973,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1496
973
|
const targetRelationshipProps = sourceRelationship.toJSON();
|
|
1497
974
|
targetRelationshipProps.sourceId = this.context.findTargetElementId(sourceRelationship.sourceId);
|
|
1498
975
|
targetRelationshipProps.targetId = this.context.findTargetElementId(sourceRelationship.targetId);
|
|
1499
|
-
// TODO: move to cloneRelationship in IModelCloneContext
|
|
1500
976
|
sourceRelationship.forEachProperty((propertyName, propertyMetaData) => {
|
|
1501
977
|
if ((core_common_1.PrimitiveTypeCode.Long === propertyMetaData.primitiveType) && ("Id" === propertyMetaData.extendedType)) {
|
|
1502
978
|
targetRelationshipProps[propertyName] = this.context.findTargetElementId(sourceRelationship.asAny[propertyName]);
|
|
@@ -1504,6 +980,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1504
980
|
});
|
|
1505
981
|
return targetRelationshipProps;
|
|
1506
982
|
}
|
|
983
|
+
shouldExportElementAspect(aspect) {
|
|
984
|
+
// This override is needed to ensure that aspects are not exported if their element is not exported.
|
|
985
|
+
// This is needed in case DetachedExportElementAspectsStrategy is used.
|
|
986
|
+
return this.context.findTargetElementId(aspect.element.id) !== core_bentley_1.Id64.invalid;
|
|
987
|
+
}
|
|
1507
988
|
/** Override of [IModelExportHandler.onExportElementUniqueAspect]($transformer) that imports an ElementUniqueAspect into the target iModel when it is exported from the source iModel.
|
|
1508
989
|
* This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
|
|
1509
990
|
*/
|
|
@@ -1658,86 +1139,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1658
1139
|
return this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
1659
1140
|
}
|
|
1660
1141
|
/**
|
|
1661
|
-
* Initialize prerequisites of processing, you must initialize with an [[
|
|
1662
|
-
* are intending
|
|
1663
|
-
*
|
|
1142
|
+
* Initialize prerequisites of processing, you must initialize with an [[InitFromExternalSourceAspectsArgs]] if you
|
|
1143
|
+
* are intending process changes, but prefer using [[processChanges]]
|
|
1144
|
+
* Called by all `process*` functions implicitly.
|
|
1664
1145
|
* Overriders must call `super.initialize()` first
|
|
1665
1146
|
*/
|
|
1666
1147
|
async initialize(args) {
|
|
1667
1148
|
if (this._initialized)
|
|
1668
1149
|
return;
|
|
1669
1150
|
await this.context.initialize();
|
|
1670
|
-
await this._tryInitChangesetData(args);
|
|
1671
|
-
await this.exporter.initialize(this.getExportInitOpts(args ?? {}));
|
|
1672
|
-
// Exporter must be initialized prior to `initFromExternalSourceAspects` in order to handle entity recreations.
|
|
1673
1151
|
// eslint-disable-next-line deprecation/deprecation
|
|
1674
1152
|
await this.initFromExternalSourceAspects(args);
|
|
1675
1153
|
this._initialized = true;
|
|
1676
1154
|
}
|
|
1677
|
-
async _tryInitChangesetData(args) {
|
|
1678
|
-
if (!args || this.sourceDb.iTwinId === undefined || this.sourceDb.changeset.index === undefined) {
|
|
1679
|
-
this._sourceChangeDataState = "unconnected";
|
|
1680
|
-
return;
|
|
1681
|
-
}
|
|
1682
|
-
const noChanges = this._synchronizationVersion.index === this.sourceDb.changeset.index;
|
|
1683
|
-
if (noChanges) {
|
|
1684
|
-
this._sourceChangeDataState = "no-changes";
|
|
1685
|
-
this._changeSummaryIds = [];
|
|
1686
|
-
return;
|
|
1687
|
-
}
|
|
1688
|
-
// NOTE: that we do NOT download the changesummary for the last transformed version, we want
|
|
1689
|
-
// to ignore those already processed changes
|
|
1690
|
-
const startChangesetIndexOrId = args.startChangeset?.index
|
|
1691
|
-
?? args.startChangeset?.id
|
|
1692
|
-
?? this._synchronizationVersion.index + 1;
|
|
1693
|
-
const endChangesetId = this.sourceDb.changeset.id;
|
|
1694
|
-
const [startChangesetIndex, endChangesetIndex] = await Promise.all(([startChangesetIndexOrId, endChangesetId])
|
|
1695
|
-
.map(async (indexOrId) => typeof indexOrId === "number"
|
|
1696
|
-
? indexOrId
|
|
1697
|
-
: core_backend_1.IModelHost.hubAccess
|
|
1698
|
-
.queryChangeset({
|
|
1699
|
-
iModelId: this.sourceDb.iModelId,
|
|
1700
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1701
|
-
changeset: { id: indexOrId },
|
|
1702
|
-
accessToken: args.accessToken,
|
|
1703
|
-
})
|
|
1704
|
-
.then((changeset) => changeset.index)));
|
|
1705
|
-
const missingChangesets = startChangesetIndex > this._synchronizationVersion.index + 1;
|
|
1706
|
-
if (!this._options.ignoreMissingChangesetsInSynchronizations
|
|
1707
|
-
&& startChangesetIndex !== this._synchronizationVersion.index + 1
|
|
1708
|
-
&& this._synchronizationVersion.index !== -1) {
|
|
1709
|
-
throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},`
|
|
1710
|
-
+ " startChangesetId should be"
|
|
1711
|
-
+ " exactly the first changeset *after* the previous synchronization to not miss data."
|
|
1712
|
-
+ ` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}`
|
|
1713
|
-
+ ` but the previous synchronization for this targetScopeElement was '${this._synchronizationVersion.id}'`
|
|
1714
|
-
+ ` which is changeset #${this._synchronizationVersion.index}. The transformer expected`
|
|
1715
|
-
+ ` #${this._synchronizationVersion.index + 1}.`);
|
|
1716
|
-
}
|
|
1717
|
-
nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps should be set by now");
|
|
1718
|
-
const changesetsToSkip = this._isReverseSynchronization
|
|
1719
|
-
? this._targetScopeProvenanceProps.jsonProperties.pendingReverseSyncChangesetIndices
|
|
1720
|
-
: this._targetScopeProvenanceProps.jsonProperties.pendingSyncChangesetIndices;
|
|
1721
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `changesets to skip: ${changesetsToSkip}`);
|
|
1722
|
-
this._changesetRanges = (0, Algo_1.rangesFromRangeAndSkipped)(startChangesetIndex, endChangesetIndex, changesetsToSkip);
|
|
1723
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `ranges: ${this._changesetRanges}`);
|
|
1724
|
-
for (const [first, end] of this._changesetRanges) {
|
|
1725
|
-
this._changeSummaryIds = await core_backend_1.ChangeSummaryManager.createChangeSummaries({
|
|
1726
|
-
accessToken: args.accessToken,
|
|
1727
|
-
iModelId: this.sourceDb.iModelId,
|
|
1728
|
-
iTwinId: this.sourceDb.iTwinId,
|
|
1729
|
-
range: { first, end },
|
|
1730
|
-
});
|
|
1731
|
-
}
|
|
1732
|
-
core_backend_1.ChangeSummaryManager.attachChangeCache(this.sourceDb);
|
|
1733
|
-
this._sourceChangeDataState = "has-changes";
|
|
1734
|
-
}
|
|
1735
1155
|
/** Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
1736
1156
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
1737
1157
|
*/
|
|
1738
1158
|
async processAll() {
|
|
1159
|
+
core_bentley_1.Logger.logTrace(loggerCategory, "processAll()");
|
|
1739
1160
|
this.logSettings();
|
|
1740
|
-
this.
|
|
1161
|
+
this.validateScopeProvenance();
|
|
1741
1162
|
await this.initialize();
|
|
1742
1163
|
await this.exporter.exportCodeSpecs();
|
|
1743
1164
|
await this.exporter.exportFonts();
|
|
@@ -1745,6 +1166,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1745
1166
|
await this.exporter.exportChildElements(core_common_1.IModel.rootSubjectId); // start below the root Subject
|
|
1746
1167
|
await this.exporter.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true); // after the Subject hierarchy, process the other elements of the RepositoryModel
|
|
1747
1168
|
await this.exporter.exportSubModels(core_common_1.IModel.repositoryModelId); // start below the RepositoryModel
|
|
1169
|
+
await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
|
|
1748
1170
|
await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
1749
1171
|
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
1750
1172
|
if (this.shouldDetectDeletes()) {
|
|
@@ -1757,15 +1179,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1757
1179
|
this.finalizeTransformation();
|
|
1758
1180
|
}
|
|
1759
1181
|
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
1760
|
-
this._lastProvenanceEntityInfo
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
aspectVersion: sourceAspect.version ?? "",
|
|
1767
|
-
aspectKind: isRelationship ? core_backend_1.ExternalSourceAspect.Kind.Relationship : core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1768
|
-
};
|
|
1182
|
+
this._lastProvenanceEntityInfo = {
|
|
1183
|
+
entityId: sourceAspect.element.id,
|
|
1184
|
+
aspectId: sourceAspect.id,
|
|
1185
|
+
aspectVersion: sourceAspect.version ?? "",
|
|
1186
|
+
aspectKind: isRelationship ? core_backend_1.ExternalSourceAspect.Kind.Relationship : core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
1187
|
+
};
|
|
1769
1188
|
}
|
|
1770
1189
|
/**
|
|
1771
1190
|
* Load the state of the active transformation from an open SQLiteDb
|
|
@@ -1777,35 +1196,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1777
1196
|
const lastProvenanceEntityInfo = db.withSqliteStatement(`SELECT entityId, aspectId, aspectVersion, aspectKind FROM ${IModelTransformer.lastProvenanceEntityInfoTable}`, (stmt) => {
|
|
1778
1197
|
if (core_bentley_1.DbResult.BE_SQLITE_ROW !== stmt.step())
|
|
1779
1198
|
throw Error("expected row when getting lastProvenanceEntityId from target state table");
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
:
|
|
1785
|
-
|
|
1786
|
-
aspectId: stmt.getValueString(1),
|
|
1787
|
-
aspectVersion: stmt.getValueString(2),
|
|
1788
|
-
aspectKind: stmt.getValueString(3),
|
|
1789
|
-
};
|
|
1199
|
+
return {
|
|
1200
|
+
entityId: stmt.getValueString(0),
|
|
1201
|
+
aspectId: stmt.getValueString(1),
|
|
1202
|
+
aspectVersion: stmt.getValueString(2),
|
|
1203
|
+
aspectKind: stmt.getValueString(3),
|
|
1204
|
+
};
|
|
1790
1205
|
});
|
|
1791
|
-
|
|
1792
|
-
//
|
|
1793
|
-
|
|
1794
|
-
if () {
|
|
1795
|
-
const [sourceFedGuid, targetFedGuid, relClassFullName] = lastProvenanceEntityInfo.split("/");
|
|
1796
|
-
const isRelProvenance = targetFedGuid !== undefined;
|
|
1797
|
-
const instanceId = isRelProvenance
|
|
1798
|
-
? this.targetDb.elements.getElement({federationGuid: sourceFedGuid})
|
|
1799
|
-
: "";
|
|
1800
|
-
//const classId =
|
|
1801
|
-
if (isRelProvenance) {
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
*/
|
|
1805
|
-
const targetHasCorrectLastProvenance = typeof lastProvenanceEntityInfo === "string" ||
|
|
1806
|
-
// ignore provenance check if it's null since we can't bind those ids
|
|
1206
|
+
const targetHasCorrectLastProvenance =
|
|
1207
|
+
// ignore provenance check if it's null since we can't bind those ids
|
|
1208
|
+
!core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.aspectId) ||
|
|
1807
1209
|
!core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.entityId) ||
|
|
1808
|
-
!core_bentley_1.Id64.isValidId64(lastProvenanceEntityInfo.aspectId) ||
|
|
1809
1210
|
this.provenanceDb.withPreparedStatement(`
|
|
1810
1211
|
SELECT Version FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
1811
1212
|
WHERE Scope.Id=:scopeId
|
|
@@ -1846,13 +1247,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1846
1247
|
this.context.loadStateFromDb(db);
|
|
1847
1248
|
this.importer.loadStateFromJson(state.importerState);
|
|
1848
1249
|
this.exporter.loadStateFromJson(state.exporterState);
|
|
1849
|
-
this._elementsWithExplicitlyTrackedProvenance = core_bentley_1.CompressedId64Set.decompressSet(state.explicitlyTrackedElements);
|
|
1850
1250
|
this.loadAdditionalStateJson(state.additionalState);
|
|
1851
1251
|
}
|
|
1852
1252
|
/**
|
|
1853
|
-
* @deprecated in 0.1.x, this is buggy, and it is now equivalently efficient to simply restart the transformation
|
|
1854
|
-
* from the original changeset
|
|
1855
|
-
*
|
|
1856
1253
|
* Return a new transformer instance with the same remappings state as saved from a previous [[IModelTransformer.saveStateToFile]] call.
|
|
1857
1254
|
* This allows you to "resume" an iModel transformation, you will have to call [[IModelTransformer.processChanges]]/[[IModelTransformer.processAll]]
|
|
1858
1255
|
* again but the remapping state will cause already mapped elements to be skipped.
|
|
@@ -1897,7 +1294,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1897
1294
|
const jsonState = {
|
|
1898
1295
|
transformerClass: this.constructor.name,
|
|
1899
1296
|
options: this._options,
|
|
1900
|
-
explicitlyTrackedElements: core_bentley_1.CompressedId64Set.compressSet(this._elementsWithExplicitlyTrackedProvenance),
|
|
1901
1297
|
importerState: this.importer.saveStateToJson(),
|
|
1902
1298
|
exporterState: this.exporter.saveStateToJson(),
|
|
1903
1299
|
additionalState: this.getAdditionalStateJson(),
|
|
@@ -1907,9 +1303,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1907
1303
|
throw Error("Failed to create the js state table in the state database");
|
|
1908
1304
|
if (core_bentley_1.DbResult.BE_SQLITE_DONE !== db.executeSQL(`
|
|
1909
1305
|
CREATE TABLE ${IModelTransformer.lastProvenanceEntityInfoTable} (
|
|
1910
|
-
--
|
|
1306
|
+
-- because we cannot bind the invalid id which we use for our null state, we actually store the id as a hex string
|
|
1911
1307
|
entityId TEXT,
|
|
1912
|
-
-- the following are only valid if the above entityId is a hex id representation
|
|
1913
1308
|
aspectId TEXT,
|
|
1914
1309
|
aspectVersion TEXT,
|
|
1915
1310
|
aspectKind TEXT
|
|
@@ -1923,20 +1318,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1923
1318
|
throw Error("Failed to insert options into the state database");
|
|
1924
1319
|
});
|
|
1925
1320
|
db.withSqliteStatement(`INSERT INTO ${IModelTransformer.lastProvenanceEntityInfoTable} (entityId, aspectId, aspectVersion, aspectKind) VALUES (?,?,?,?)`, (stmt) => {
|
|
1926
|
-
|
|
1927
|
-
stmt.bindString(
|
|
1928
|
-
stmt.bindString(
|
|
1929
|
-
stmt.bindString(
|
|
1930
|
-
stmt.bindString(4, lastProvenanceEntityInfo?.aspectKind ?? "");
|
|
1321
|
+
stmt.bindString(1, this._lastProvenanceEntityInfo.entityId);
|
|
1322
|
+
stmt.bindString(2, this._lastProvenanceEntityInfo.aspectId);
|
|
1323
|
+
stmt.bindString(3, this._lastProvenanceEntityInfo.aspectVersion);
|
|
1324
|
+
stmt.bindString(4, this._lastProvenanceEntityInfo.aspectKind);
|
|
1931
1325
|
if (core_bentley_1.DbResult.BE_SQLITE_DONE !== stmt.step())
|
|
1932
1326
|
throw Error("Failed to insert options into the state database");
|
|
1933
1327
|
});
|
|
1934
1328
|
db.saveChanges();
|
|
1935
1329
|
}
|
|
1936
1330
|
/**
|
|
1937
|
-
* @deprecated in 0.1.x, this is buggy, and it is now equivalently efficient to simply restart the transformation
|
|
1938
|
-
* from the original changeset
|
|
1939
|
-
*
|
|
1940
1331
|
* Save the state of the active transformation to a file path, if a file at the path already exists, it will be overwritten
|
|
1941
1332
|
* This state can be used by [[IModelTransformer.resumeTransformation]] to resume a transformation from this point.
|
|
1942
1333
|
* The serialization format is a custom sqlite database.
|
|
@@ -1958,54 +1349,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1958
1349
|
db.closeDb();
|
|
1959
1350
|
}
|
|
1960
1351
|
}
|
|
1961
|
-
async processChanges(
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
this.
|
|
1965
|
-
const
|
|
1352
|
+
async processChanges(accessTokenOrArgs, startChangesetId) {
|
|
1353
|
+
core_bentley_1.Logger.logTrace(loggerCategory, "processChanges()");
|
|
1354
|
+
this.logSettings();
|
|
1355
|
+
this.validateScopeProvenance();
|
|
1356
|
+
const options = typeof accessTokenOrArgs === "string"
|
|
1966
1357
|
? {
|
|
1967
|
-
accessToken:
|
|
1968
|
-
startChangeset: startChangesetId
|
|
1969
|
-
|
|
1970
|
-
: { index: this._synchronizationVersion.index + 1 },
|
|
1358
|
+
accessToken: accessTokenOrArgs,
|
|
1359
|
+
startChangeset: startChangesetId ? { id: startChangesetId } : this.sourceDb.changeset,
|
|
1360
|
+
changedInstanceIds: undefined,
|
|
1971
1361
|
}
|
|
1972
|
-
:
|
|
1973
|
-
this.
|
|
1974
|
-
await this.
|
|
1975
|
-
// must wait for initialization of synchronization provenance data
|
|
1976
|
-
await this.exporter.exportChanges(this.getExportInitOpts(args));
|
|
1362
|
+
: accessTokenOrArgs;
|
|
1363
|
+
await this.initialize(options);
|
|
1364
|
+
await this.exporter.exportChanges(options);
|
|
1977
1365
|
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
1366
|
+
await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
|
|
1978
1367
|
if (this._options.optimizeGeometry)
|
|
1979
1368
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
1980
1369
|
this.importer.computeProjectExtents();
|
|
1981
1370
|
this.finalizeTransformation();
|
|
1982
1371
|
}
|
|
1983
|
-
/** Changeset data must be initialized in order to build correct changeOptions.
|
|
1984
|
-
* Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
|
|
1985
|
-
*/
|
|
1986
|
-
getExportInitOpts(opts) {
|
|
1987
|
-
if (!this._isSynchronization)
|
|
1988
|
-
return {};
|
|
1989
|
-
return {
|
|
1990
|
-
accessToken: opts.accessToken,
|
|
1991
|
-
...this._changesetRanges
|
|
1992
|
-
? { changesetRanges: this._changesetRanges }
|
|
1993
|
-
: opts.startChangeset
|
|
1994
|
-
? { startChangeset: opts.startChangeset }
|
|
1995
|
-
: { startChangeset: { index: this._synchronizationVersion.index + 1 } },
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
/** Combine an array of source elements into a single target element.
|
|
1999
|
-
* All source and target elements must be created before calling this method.
|
|
2000
|
-
* The "combine" operation is a remap and no properties from the source elements will be exported into the target
|
|
2001
|
-
* and provenance will be explicitly tracked by ExternalSourceAspects
|
|
2002
|
-
*/
|
|
2003
|
-
combineElements(sourceElementIds, targetElementId) {
|
|
2004
|
-
for (const elementId of sourceElementIds) {
|
|
2005
|
-
this.context.remapElement(elementId, targetElementId);
|
|
2006
|
-
this._elementsWithExplicitlyTrackedProvenance.add(elementId);
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
1372
|
}
|
|
2010
1373
|
exports.IModelTransformer = IModelTransformer;
|
|
2011
1374
|
/** @internal the name of the table where javascript state of the transformer is serialized in transformer state dumps */
|
|
@@ -2036,7 +1399,6 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2036
1399
|
* @returns The mapping of sourceElementIds from the template model to the instantiated targetElementIds in the targetDb in case further processing is required.
|
|
2037
1400
|
*/
|
|
2038
1401
|
async placeTemplate3d(sourceTemplateModelId, targetModelId, placement) {
|
|
2039
|
-
await this.initialize();
|
|
2040
1402
|
this.context.remapElement(sourceTemplateModelId, targetModelId);
|
|
2041
1403
|
this._transform3d = core_geometry_1.Transform.createOriginAndMatrix(placement.origin, placement.angles.toMatrix3d());
|
|
2042
1404
|
this._sourceIdToTargetIdMap = new Map();
|
|
@@ -2057,7 +1419,6 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2057
1419
|
* @returns The mapping of sourceElementIds from the template model to the instantiated targetElementIds in the targetDb in case further processing is required.
|
|
2058
1420
|
*/
|
|
2059
1421
|
async placeTemplate2d(sourceTemplateModelId, targetModelId, placement) {
|
|
2060
|
-
await this.initialize();
|
|
2061
1422
|
this.context.remapElement(sourceTemplateModelId, targetModelId);
|
|
2062
1423
|
this._transform3d = core_geometry_1.Transform.createOriginAndMatrix(core_geometry_1.Point3d.createFrom(placement.origin), placement.rotation);
|
|
2063
1424
|
this._sourceIdToTargetIdMap = new Map();
|
|
@@ -2094,12 +1455,16 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2094
1455
|
const targetElementProps = super.onTransformElement(sourceElement);
|
|
2095
1456
|
targetElementProps.federationGuid = core_bentley_1.Guid.createValue(); // clone from template should create a new federationGuid
|
|
2096
1457
|
targetElementProps.code = core_common_1.Code.createEmpty(); // clone from template should not maintain codes
|
|
2097
|
-
if (sourceElement instanceof core_backend_1.
|
|
2098
|
-
const
|
|
2099
|
-
|
|
2100
|
-
|
|
1458
|
+
if (sourceElement instanceof core_backend_1.GeometricElement3d) {
|
|
1459
|
+
const placement = core_common_1.Placement3d.fromJSON(targetElementProps.placement);
|
|
1460
|
+
if (placement.isValid) {
|
|
1461
|
+
placement.multiplyTransform(this._transform3d);
|
|
1462
|
+
targetElementProps.placement = placement;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
else if (sourceElement instanceof core_backend_1.GeometricElement2d) {
|
|
1466
|
+
const placement = core_common_1.Placement2d.fromJSON(targetElementProps.placement);
|
|
2101
1467
|
if (placement.isValid) {
|
|
2102
|
-
nodeAssert(this._transform3d);
|
|
2103
1468
|
placement.multiplyTransform(this._transform3d);
|
|
2104
1469
|
targetElementProps.placement = placement;
|
|
2105
1470
|
}
|
|
@@ -2109,17 +1474,4 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2109
1474
|
}
|
|
2110
1475
|
}
|
|
2111
1476
|
exports.TemplateModelCloner = TemplateModelCloner;
|
|
2112
|
-
function queryElemFedGuid(db, elemId) {
|
|
2113
|
-
return db.withPreparedStatement(`
|
|
2114
|
-
SELECT FederationGuid
|
|
2115
|
-
FROM bis.Element
|
|
2116
|
-
WHERE ECInstanceId=?
|
|
2117
|
-
`, (stmt) => {
|
|
2118
|
-
stmt.bindId(1, elemId);
|
|
2119
|
-
(0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW);
|
|
2120
|
-
const result = stmt.getValue(0).getGuid();
|
|
2121
|
-
(0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_DONE);
|
|
2122
|
-
return result;
|
|
2123
|
-
});
|
|
2124
|
-
}
|
|
2125
1477
|
//# sourceMappingURL=IModelTransformer.js.map
|