@itwin/imodel-transformer 2.0.0-dev.4 → 2.0.0-dev.40
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/README.md +12 -26
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +35 -30
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/ChangesetScanner.d.ts +60 -0
- package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
- package/lib/cjs/ChangesetScanner.js +101 -0
- package/lib/cjs/ChangesetScanner.js.map +1 -0
- package/lib/cjs/ECReferenceTypesCache.d.ts +0 -3
- package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
- package/lib/cjs/ECReferenceTypesCache.js +61 -103
- package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
- package/lib/cjs/ElementAspectCleanup.d.ts +16 -0
- package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
- package/lib/cjs/ElementAspectCleanup.js +83 -0
- package/lib/cjs/ElementAspectCleanup.js.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts +44 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.js +148 -0
- package/lib/cjs/ElementAspectExportCoordinator.js.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts +55 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.js +186 -0
- package/lib/cjs/ElementAspectExportProcessor.js.map +1 -0
- package/lib/cjs/ElementCascadingDeleter.d.ts +3 -3
- package/lib/cjs/ElementCascadingDeleter.d.ts.map +1 -1
- package/lib/cjs/ElementCascadingDeleter.js +12 -12
- package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
- package/lib/cjs/EntityUnifier.d.ts +4 -8
- package/lib/cjs/EntityUnifier.d.ts.map +1 -1
- package/lib/cjs/EntityUnifier.js +6 -24
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts +6 -5
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +74 -57
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +80 -32
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +563 -188
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +38 -24
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +210 -129
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformContext.d.ts +54 -0
- package/lib/cjs/IModelTransformContext.d.ts.map +1 -0
- package/lib/cjs/IModelTransformContext.js +3 -0
- package/lib/cjs/IModelTransformContext.js.map +1 -0
- package/lib/cjs/IModelTransformer.d.ts +101 -189
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +558 -1104
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/IModelTransformerError.d.ts +76 -0
- package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
- package/lib/cjs/IModelTransformerError.js +85 -0
- package/lib/cjs/IModelTransformerError.js.map +1 -0
- package/lib/cjs/ProvenanceManager.d.ts +170 -0
- package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
- package/lib/cjs/ProvenanceManager.js +735 -0
- package/lib/cjs/ProvenanceManager.js.map +1 -0
- package/lib/cjs/SyncTypeResolver.d.ts +34 -0
- package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
- package/lib/cjs/SyncTypeResolver.js +98 -0
- package/lib/cjs/SyncTypeResolver.js.map +1 -0
- package/lib/cjs/TransformerLoggerCategory.d.ts +1 -1
- package/lib/cjs/TransformerLoggerCategory.js +1 -1
- package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
- package/lib/cjs/TransformerPackageMetadata.d.ts +8 -0
- package/lib/cjs/TransformerPackageMetadata.d.ts.map +1 -0
- package/lib/cjs/TransformerPackageMetadata.js +14 -0
- package/lib/cjs/TransformerPackageMetadata.js.map +1 -0
- package/lib/cjs/imodel-transformer.d.ts +18 -0
- package/lib/cjs/imodel-transformer.d.ts.map +1 -1
- package/lib/cjs/imodel-transformer.js +29 -33
- package/lib/cjs/imodel-transformer.js.map +1 -1
- package/package.json +27 -39
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +0 -16
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +0 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.js +0 -106
- package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +0 -1
- package/lib/cjs/ExportElementAspectsStrategy.d.ts +0 -35
- package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +0 -1
- package/lib/cjs/ExportElementAspectsStrategy.js +0 -52
- package/lib/cjs/ExportElementAspectsStrategy.js.map +0 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +0 -12
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +0 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +0 -47
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +0 -1
|
@@ -13,8 +13,11 @@ const core_bentley_1 = require("@itwin/core-bentley");
|
|
|
13
13
|
const core_common_1 = require("@itwin/core-common");
|
|
14
14
|
const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
|
|
15
15
|
const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const node_assert_1 = require("node:assert");
|
|
17
|
+
const ElementAspectExportProcessor_1 = require("./ElementAspectExportProcessor");
|
|
18
|
+
const ElementAspectExportCoordinator_1 = require("./ElementAspectExportCoordinator");
|
|
19
|
+
const IModelTransformerError_1 = require("./IModelTransformerError");
|
|
20
|
+
const ChangesetScanner_1 = require("./ChangesetScanner");
|
|
18
21
|
const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelExporter;
|
|
19
22
|
/** Handles the events generated by IModelExporter.
|
|
20
23
|
* @note Change information is available when `IModelExportHandler` methods are invoked via [IModelExporter.exportChanges]($transformer), but not available when invoked via [IModelExporter.exportAll]($transformer).
|
|
@@ -26,7 +29,7 @@ class IModelExportHandler {
|
|
|
26
29
|
/** If `true` is returned, then the CodeSpec will be exported.
|
|
27
30
|
* @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
|
|
28
31
|
*/
|
|
29
|
-
shouldExportCodeSpec(_codeSpec) {
|
|
32
|
+
async shouldExportCodeSpec(_codeSpec) {
|
|
30
33
|
return true;
|
|
31
34
|
}
|
|
32
35
|
/** Called when a CodeSpec should be exported.
|
|
@@ -34,37 +37,37 @@ class IModelExportHandler {
|
|
|
34
37
|
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
35
38
|
* @note This should be overridden to actually do the export.
|
|
36
39
|
*/
|
|
37
|
-
onExportCodeSpec(_codeSpec, _isUpdate) { }
|
|
40
|
+
async onExportCodeSpec(_codeSpec, _isUpdate) { }
|
|
38
41
|
/** Called when a font should be exported.
|
|
39
42
|
* @param font The font to export
|
|
40
43
|
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
41
44
|
* @note This should be overridden to actually do the export.
|
|
42
45
|
*/
|
|
43
|
-
onExportFont(_font, _isUpdate) { }
|
|
46
|
+
async onExportFont(_font, _isUpdate) { }
|
|
44
47
|
/** Called when a model should be exported.
|
|
45
48
|
* @param model The model to export
|
|
46
49
|
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
47
50
|
* @note This should be overridden to actually do the export.
|
|
48
51
|
*/
|
|
49
|
-
onExportModel(_model, _isUpdate) { }
|
|
52
|
+
async onExportModel(_model, _isUpdate) { }
|
|
50
53
|
/** Called when a model should be deleted. */
|
|
51
|
-
onDeleteModel(_modelId) { }
|
|
54
|
+
async onDeleteModel(_modelId) { }
|
|
52
55
|
/** If `true` is returned, then the element will be exported.
|
|
53
56
|
* @note This method can optionally be overridden to exclude an individual Element (and its children and ElementAspects) from the export. The base implementation always returns `true`.
|
|
54
57
|
*/
|
|
55
|
-
shouldExportElement(_element) {
|
|
58
|
+
async shouldExportElement(_element) {
|
|
56
59
|
return true;
|
|
57
60
|
}
|
|
58
61
|
/** Called when element is skipped instead of exported.
|
|
59
62
|
* @note When an element is skipped, exporter will not export any of its child elements. Because of this, [[onSkipElement]] will not be invoked for any children of a "skipped" element.
|
|
60
63
|
*/
|
|
61
|
-
onSkipElement(_elementId) { }
|
|
64
|
+
async onSkipElement(_elementId) { }
|
|
62
65
|
/** Called when an element should be exported.
|
|
63
66
|
* @param element The element to export
|
|
64
67
|
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
65
68
|
* @note This should be overridden to actually do the export.
|
|
66
69
|
*/
|
|
67
|
-
onExportElement(_element, _isUpdate) { }
|
|
70
|
+
async onExportElement(_element, _isUpdate) { }
|
|
68
71
|
/**
|
|
69
72
|
* Do any asynchronous actions before exporting an element
|
|
70
73
|
* @note Do not implement this handler manually, it is internal, it will be removed.
|
|
@@ -73,11 +76,11 @@ class IModelExportHandler {
|
|
|
73
76
|
*/
|
|
74
77
|
async preExportElement(_element) { }
|
|
75
78
|
/** Called when an element should be deleted. */
|
|
76
|
-
onDeleteElement(_elementId) { }
|
|
79
|
+
async onDeleteElement(_elementId) { }
|
|
77
80
|
/** If `true` is returned, then the ElementAspect will be exported.
|
|
78
81
|
* @note This method can optionally be overridden to exclude an individual ElementAspect from the export. The base implementation always returns `true`.
|
|
79
82
|
*/
|
|
80
|
-
shouldExportElementAspect(_aspect) {
|
|
83
|
+
async shouldExportElementAspect(_aspect) {
|
|
81
84
|
return true;
|
|
82
85
|
}
|
|
83
86
|
/** Called when an ElementUniqueAspect should be exported.
|
|
@@ -85,15 +88,15 @@ class IModelExportHandler {
|
|
|
85
88
|
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
86
89
|
* @note This should be overridden to actually do the export.
|
|
87
90
|
*/
|
|
88
|
-
onExportElementUniqueAspect(_aspect, _isUpdate) { }
|
|
91
|
+
async onExportElementUniqueAspect(_aspect, _isUpdate) { }
|
|
89
92
|
/** Called when ElementMultiAspects should be exported.
|
|
90
93
|
* @note This should be overridden to actually do the export.
|
|
91
94
|
*/
|
|
92
|
-
onExportElementMultiAspects(_aspects) { }
|
|
95
|
+
async onExportElementMultiAspects(_aspects) { }
|
|
93
96
|
/** If `true` is returned, then the relationship will be exported.
|
|
94
97
|
* @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
|
|
95
98
|
*/
|
|
96
|
-
shouldExportRelationship(_relationship) {
|
|
99
|
+
async shouldExportRelationship(_relationship) {
|
|
97
100
|
return true;
|
|
98
101
|
}
|
|
99
102
|
/** Called when a Relationship should be exported.
|
|
@@ -101,13 +104,13 @@ class IModelExportHandler {
|
|
|
101
104
|
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
102
105
|
* @note This should be overridden to actually do the export.
|
|
103
106
|
*/
|
|
104
|
-
onExportRelationship(_relationship, _isUpdate) { }
|
|
107
|
+
async onExportRelationship(_relationship, _isUpdate) { }
|
|
105
108
|
/** Called when a relationship should be deleted. */
|
|
106
|
-
onDeleteRelationship(_relInstanceId) { }
|
|
109
|
+
async onDeleteRelationship(_relInstanceId) { }
|
|
107
110
|
/** If `true` is returned, then the schema will be exported.
|
|
108
111
|
* @note This method can optionally be overridden to exclude an individual schema from the export. The base implementation always returns `true`.
|
|
109
112
|
*/
|
|
110
|
-
shouldExportSchema(_schemaKey) {
|
|
113
|
+
async shouldExportSchema(_schemaKey) {
|
|
111
114
|
return true;
|
|
112
115
|
}
|
|
113
116
|
/** Called when a schema should be exported.
|
|
@@ -126,10 +129,14 @@ class IModelExportHandler {
|
|
|
126
129
|
exports.IModelExportHandler = IModelExportHandler;
|
|
127
130
|
/** Base class for exporting data from an iModel.
|
|
128
131
|
* @note Most uses cases will not require a custom subclass of `IModelExporter`. Instead, it is more typical to subclass/customize [IModelExportHandler]($transformer).
|
|
132
|
+
* ElementAspects are exported separately from element callbacks in bounded,
|
|
133
|
+
* owner-scoped groups. Aspect callbacks are not guaranteed to be adjacent to
|
|
134
|
+
* callbacks for their owners.
|
|
129
135
|
* @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md), [[registerHandler]], [IModelTransformer]($transformer), [IModelImporter]($transformer)
|
|
130
136
|
* @beta
|
|
131
137
|
*/
|
|
132
138
|
class IModelExporter {
|
|
139
|
+
static _elementAspectOwnerBatchSize = 1000;
|
|
133
140
|
/** The read-only source iModel. */
|
|
134
141
|
sourceDb;
|
|
135
142
|
/** A flag that indicates whether element GeometryStreams are loaded or not.
|
|
@@ -175,7 +182,13 @@ class IModelExporter {
|
|
|
175
182
|
/** The handler called by this IModelExporter. */
|
|
176
183
|
get handler() {
|
|
177
184
|
if (undefined === this._handler) {
|
|
178
|
-
|
|
185
|
+
core_bentley_1.ITwinError.throwError({
|
|
186
|
+
iTwinErrorId: {
|
|
187
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
188
|
+
key: IModelTransformerError_1.IModelTransformerError.ExportHandlerNotRegistered,
|
|
189
|
+
},
|
|
190
|
+
message: "IModelExportHandler not registered",
|
|
191
|
+
});
|
|
179
192
|
}
|
|
180
193
|
return this._handler;
|
|
181
194
|
}
|
|
@@ -189,20 +202,37 @@ class IModelExporter {
|
|
|
189
202
|
_excludedElementClasses = new Set();
|
|
190
203
|
/** The set of classes of Relationships that will be excluded (polymorphically) from transformation to the target iModel. */
|
|
191
204
|
_excludedRelationshipClasses = new Set();
|
|
192
|
-
/**
|
|
193
|
-
|
|
205
|
+
/** Exports ElementAspects for accepted owner groups. */
|
|
206
|
+
_elementAspectExportProcessor;
|
|
207
|
+
/** Coordinates accepted-owner scopes and bounded group processing. */
|
|
208
|
+
_elementAspectExportCoordinator;
|
|
209
|
+
/** Coordinates accepted ElementAspect owners and bounded group processing.
|
|
210
|
+
* @internal
|
|
211
|
+
*/
|
|
212
|
+
get elementAspectExportCoordinator() {
|
|
213
|
+
return this._elementAspectExportCoordinator;
|
|
214
|
+
}
|
|
215
|
+
/** Whether change traversal skips root-owned entities. */
|
|
216
|
+
_skipPropagateChangesToRootElements = false;
|
|
217
|
+
_rootElementIds = new Set([
|
|
218
|
+
core_common_1.IModel.rootSubjectId,
|
|
219
|
+
core_common_1.IModel.repositoryModelId,
|
|
220
|
+
core_common_1.IModel.dictionaryId,
|
|
221
|
+
"0xe", // The reality-data-sources modelId, skipped with root propagation.
|
|
222
|
+
]);
|
|
194
223
|
/** Construct a new IModelExporter
|
|
195
224
|
* @param sourceDb The source IModelDb
|
|
196
225
|
* @see registerHandler
|
|
197
226
|
*/
|
|
198
|
-
constructor(sourceDb
|
|
227
|
+
constructor(sourceDb) {
|
|
199
228
|
this.sourceDb = sourceDb;
|
|
200
|
-
this.
|
|
201
|
-
onExportElementMultiAspects: (aspects) => this.handler.onExportElementMultiAspects(aspects),
|
|
202
|
-
onExportElementUniqueAspect: (aspect, isUpdate) => this.handler.onExportElementUniqueAspect(aspect, isUpdate),
|
|
203
|
-
shouldExportElementAspect: (aspect) => this.handler.shouldExportElementAspect(aspect),
|
|
229
|
+
this._elementAspectExportProcessor = new ElementAspectExportProcessor_1.ElementAspectExportProcessor(this.sourceDb, {
|
|
230
|
+
onExportElementMultiAspects: async (aspects) => this.handler.onExportElementMultiAspects(aspects),
|
|
231
|
+
onExportElementUniqueAspect: async (aspect, isUpdate) => this.handler.onExportElementUniqueAspect(aspect, isUpdate),
|
|
232
|
+
shouldExportElementAspect: async (aspect) => this.handler.shouldExportElementAspect(aspect),
|
|
204
233
|
trackProgress: async () => this.trackProgress(),
|
|
205
234
|
});
|
|
235
|
+
this._elementAspectExportCoordinator = new ElementAspectExportCoordinator_1.ElementAspectExportCoordinator(IModelExporter._elementAspectOwnerBatchSize, () => this._elementAspectExportProcessor.excludedElementAspectClassFullNames, async (elementIds) => this._elementAspectExportProcessor.exportAllElementAspects(elementIds), () => this._elementAspectExportProcessor.resetCaches());
|
|
206
236
|
}
|
|
207
237
|
/**
|
|
208
238
|
* Initialize prerequisites of exporting. This is implicitly done by any `export*` calls that need initialization
|
|
@@ -220,7 +250,7 @@ class IModelExporter {
|
|
|
220
250
|
});
|
|
221
251
|
if (this._sourceDbChanges === undefined)
|
|
222
252
|
return;
|
|
223
|
-
this.
|
|
253
|
+
this._elementAspectExportProcessor.setAspectChanges(this._sourceDbChanges.aspect);
|
|
224
254
|
}
|
|
225
255
|
/** Register the handler that will be called by IModelExporter. */
|
|
226
256
|
registerHandler(handler) {
|
|
@@ -244,7 +274,7 @@ class IModelExporter {
|
|
|
244
274
|
}
|
|
245
275
|
/** Add a rule to exclude all ElementAspects of a specified class. */
|
|
246
276
|
excludeElementAspectClass(classFullName) {
|
|
247
|
-
this.
|
|
277
|
+
this._elementAspectExportProcessor.excludeElementAspectClass(classFullName);
|
|
248
278
|
}
|
|
249
279
|
/** Add a rule to exclude all Relationships of a specified class. */
|
|
250
280
|
excludeRelationshipClass(classFullName) {
|
|
@@ -257,11 +287,12 @@ class IModelExporter {
|
|
|
257
287
|
await this.initialize({});
|
|
258
288
|
await this.exportCodeSpecs();
|
|
259
289
|
await this.exportFonts();
|
|
260
|
-
await this.exportModel(core_common_1.IModel.repositoryModelId);
|
|
290
|
+
await this._elementAspectExportCoordinator.run(async () => this.exportModel(core_common_1.IModel.repositoryModelId));
|
|
261
291
|
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
262
292
|
}
|
|
263
293
|
/** Export changes from the source iModel.
|
|
264
294
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
295
|
+
* @throws [[ITwinError]] with scope `@itwin/imodel-transformer` and key `no-changesets` if the source iModel has no changesets and no custom changes. Call [[exportAll]] to export all content.
|
|
265
296
|
* @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired
|
|
266
297
|
* range and open the source iModel as of the end (inclusive) of the desired range.
|
|
267
298
|
* @note the changedInstanceIds are just for this call to exportChanges, so you must continue to pass it in
|
|
@@ -269,35 +300,68 @@ class IModelExporter {
|
|
|
269
300
|
*/
|
|
270
301
|
async exportChanges(args) {
|
|
271
302
|
if (!this.sourceDb.isBriefcaseDb())
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
303
|
+
core_bentley_1.ITwinError.throwError({
|
|
304
|
+
iTwinErrorId: {
|
|
305
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
306
|
+
key: IModelTransformerError_1.IModelTransformerError.ExportChangesRequiresBriefcase,
|
|
307
|
+
},
|
|
308
|
+
message: "Must be a briefcase to export changes",
|
|
309
|
+
});
|
|
310
|
+
let initOpts = args ?? {};
|
|
311
|
+
const hasExplicitChangeSource = "csFileProps" in initOpts ||
|
|
312
|
+
"changedInstanceIds" in initOpts ||
|
|
313
|
+
"changesetRanges" in initOpts ||
|
|
314
|
+
"startChangeset" in initOpts;
|
|
315
|
+
const currentChangesetId = this.sourceDb.changeset.id;
|
|
316
|
+
if (!hasExplicitChangeSource && currentChangesetId !== "") {
|
|
317
|
+
initOpts = {
|
|
318
|
+
...initOpts,
|
|
319
|
+
startChangeset: { id: currentChangesetId },
|
|
320
|
+
};
|
|
277
321
|
}
|
|
278
|
-
const startChangeset = args && "startChangeset" in args ? args.startChangeset : undefined;
|
|
279
|
-
const initOpts = {
|
|
280
|
-
startChangeset: { id: startChangeset?.id },
|
|
281
|
-
};
|
|
282
322
|
await this.initialize(initOpts);
|
|
323
|
+
if (currentChangesetId === "" && !this.sourceDbChanges?.hasChanges) {
|
|
324
|
+
core_bentley_1.ITwinError.throwError({
|
|
325
|
+
iTwinErrorId: {
|
|
326
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
327
|
+
key: IModelTransformerError_1.IModelTransformerError.NoChangesets,
|
|
328
|
+
},
|
|
329
|
+
message: "Cannot export changes because the source iModel has no changesets or custom changes. Call exportAll() to export all content.",
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
this._skipPropagateChangesToRootElements =
|
|
333
|
+
initOpts.skipPropagateChangesToRootElements ?? false;
|
|
283
334
|
// _sourceDbChanges are initialized in this.initialize
|
|
284
|
-
|
|
335
|
+
(0, node_assert_1.strict)(this._sourceDbChanges !== undefined, "sourceDbChanges must be initialized.");
|
|
285
336
|
await this.exportCodeSpecs();
|
|
286
337
|
await this.exportFonts();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
338
|
+
await this._elementAspectExportCoordinator.run(async () => {
|
|
339
|
+
if (initOpts.skipPropagateChangesToRootElements) {
|
|
340
|
+
// The root Subject is in the RepositoryModel. Traverse its children
|
|
341
|
+
// separately, then export other top-level repository elements while
|
|
342
|
+
// excluding the root so no element is visited twice.
|
|
343
|
+
await this.exportChildElements(core_common_1.IModel.rootSubjectId);
|
|
344
|
+
await this.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true);
|
|
345
|
+
await this.exportSubModels(core_common_1.IModel.repositoryModelId);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
await this.exportModel(core_common_1.IModel.repositoryModelId);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
const aspectOnlyOwnerElementIds = new Set(this._sourceDbChanges.aspectOwnerElementIds);
|
|
352
|
+
for (const elementId of this._sourceDbChanges.element.insertIds) {
|
|
353
|
+
aspectOnlyOwnerElementIds.delete(elementId);
|
|
290
354
|
}
|
|
291
|
-
|
|
292
|
-
|
|
355
|
+
for (const elementId of this._sourceDbChanges.element.updateIds) {
|
|
356
|
+
aspectOnlyOwnerElementIds.delete(elementId);
|
|
293
357
|
}
|
|
294
|
-
await this.
|
|
358
|
+
await this.exportAspectsForOwners(await this.filterOwnerElementIdsForAspectExport(aspectOnlyOwnerElementIds));
|
|
295
359
|
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
296
360
|
// handle deletes
|
|
297
361
|
if (this.visitElements) {
|
|
298
362
|
// must delete models first since they have a constraint on the submodeling element which may also be deleted
|
|
299
363
|
for (const modelId of this._sourceDbChanges.model.deleteIds) {
|
|
300
|
-
this.handler.onDeleteModel(modelId);
|
|
364
|
+
await this.handler.onDeleteModel(modelId);
|
|
301
365
|
}
|
|
302
366
|
for (const elementId of this._sourceDbChanges.element.deleteIds) {
|
|
303
367
|
// We don't know how the handler wants to handle deletions, and we don't have enough information
|
|
@@ -307,7 +371,7 @@ class IModelExporter {
|
|
|
307
371
|
// In the future, the handler may be responsible for doing the work of finding out which elements were cascade deleted,
|
|
308
372
|
// and returning them for the exporter to use to avoid double-deleting with error ignoring
|
|
309
373
|
try {
|
|
310
|
-
this.handler.onDeleteElement(elementId);
|
|
374
|
+
await this.handler.onDeleteElement(elementId);
|
|
311
375
|
}
|
|
312
376
|
catch (err) {
|
|
313
377
|
const isMissingErr = err instanceof core_common_1.IModelError &&
|
|
@@ -320,15 +384,17 @@ class IModelExporter {
|
|
|
320
384
|
if (this.visitRelationships) {
|
|
321
385
|
for (const relInstanceId of this._sourceDbChanges.relationship
|
|
322
386
|
.deleteIds) {
|
|
323
|
-
this.handler.onDeleteRelationship(relInstanceId);
|
|
387
|
+
await this.handler.onDeleteRelationship(relInstanceId);
|
|
324
388
|
}
|
|
325
389
|
}
|
|
326
390
|
// Enable consecutive exportChanges runs without the need to re-instantiate the exporter.
|
|
327
391
|
// You can counteract the obvious impact of losing this expensive data by always calling
|
|
328
392
|
// exportChanges with the [[ExportChangesOptions.changedInstanceIds]] option set to
|
|
329
393
|
// whatever you want
|
|
330
|
-
if (this._resetChangeDataOnExport)
|
|
394
|
+
if (this._resetChangeDataOnExport) {
|
|
331
395
|
this._sourceDbChanges = undefined;
|
|
396
|
+
this._elementAspectExportProcessor.setAspectChanges(undefined);
|
|
397
|
+
}
|
|
332
398
|
}
|
|
333
399
|
_resetChangeDataOnExport = true;
|
|
334
400
|
/** Export schemas from the source iModel.
|
|
@@ -345,26 +411,33 @@ class IModelExporter {
|
|
|
345
411
|
`}
|
|
346
412
|
ORDER BY ECInstanceId
|
|
347
413
|
`;
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
414
|
+
const schemaKeysToExport = [];
|
|
415
|
+
for await (const row of this.sourceDb.createQueryReader(sql, undefined, {
|
|
416
|
+
usePrimaryConn: true,
|
|
417
|
+
})) {
|
|
418
|
+
const schemaName = row[0];
|
|
419
|
+
const versionMajor = row[1];
|
|
420
|
+
const versionWrite = row[2];
|
|
421
|
+
const versionMinor = row[3];
|
|
422
|
+
const schemaKey = new ecschema_metadata_1.SchemaKey(schemaName, new ecschema_metadata_1.ECVersion(versionMajor, versionWrite, versionMinor));
|
|
423
|
+
if (await this.handler.shouldExportSchema(schemaKey)) {
|
|
424
|
+
schemaKeysToExport.push(schemaKey);
|
|
360
425
|
}
|
|
361
|
-
}
|
|
362
|
-
if (
|
|
426
|
+
}
|
|
427
|
+
if (schemaKeysToExport.length === 0)
|
|
363
428
|
return;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
429
|
+
await Promise.all(schemaKeysToExport.map(async (schemaKey) => {
|
|
430
|
+
const schema = await this.sourceDb.schemaContext.getSchema(schemaKey);
|
|
431
|
+
if (!schema) {
|
|
432
|
+
core_bentley_1.ITwinError.throwError({
|
|
433
|
+
iTwinErrorId: {
|
|
434
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
435
|
+
key: IModelTransformerError_1.IModelTransformerError.SchemaLoadFailed,
|
|
436
|
+
},
|
|
437
|
+
message: `Failed to load schema: ${schemaKey.name}`,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportSchema(${schemaKey.name})`);
|
|
368
441
|
return this.handler.onExportSchema(schema);
|
|
369
442
|
}));
|
|
370
443
|
}
|
|
@@ -378,15 +451,11 @@ class IModelExporter {
|
|
|
378
451
|
async exportCodeSpecs() {
|
|
379
452
|
core_bentley_1.Logger.logTrace(loggerCategory, "exportCodeSpecs()");
|
|
380
453
|
const sql = "SELECT Name FROM BisCore:CodeSpec ORDER BY ECInstanceId";
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const codeSpecName = statement.getValue(0).getString();
|
|
387
|
-
await this.exportCodeSpecByName(codeSpecName);
|
|
388
|
-
}
|
|
389
|
-
});
|
|
454
|
+
for await (const row of this.sourceDb.createQueryReader(sql, undefined, {
|
|
455
|
+
usePrimaryConn: true,
|
|
456
|
+
})) {
|
|
457
|
+
await this.exportCodeSpecByName(row[0]);
|
|
458
|
+
}
|
|
390
459
|
}
|
|
391
460
|
/** Export a single CodeSpec from the source iModel.
|
|
392
461
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
@@ -412,9 +481,9 @@ class IModelExporter {
|
|
|
412
481
|
return;
|
|
413
482
|
}
|
|
414
483
|
// CodeSpec has passed standard exclusion rules, now give handler a chance to accept/reject export
|
|
415
|
-
if (this.handler.shouldExportCodeSpec(codeSpec)) {
|
|
484
|
+
if (await this.handler.shouldExportCodeSpec(codeSpec)) {
|
|
416
485
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportCodeSpec(${codeSpecName})${this.getChangeOpSuffix(isUpdate)}`);
|
|
417
|
-
this.handler.onExportCodeSpec(codeSpec, isUpdate);
|
|
486
|
+
await this.handler.onExportCodeSpec(codeSpec, isUpdate);
|
|
418
487
|
return this.trackProgress();
|
|
419
488
|
}
|
|
420
489
|
}
|
|
@@ -430,41 +499,63 @@ class IModelExporter {
|
|
|
430
499
|
*/
|
|
431
500
|
async exportFonts() {
|
|
432
501
|
core_bentley_1.Logger.logTrace(loggerCategory, "exportFonts()");
|
|
433
|
-
for (const font of this.sourceDb.
|
|
434
|
-
await this.
|
|
502
|
+
for (const font of this.sourceDb.fonts.queryMappedFamilies()) {
|
|
503
|
+
await this.exportFontByFontProps(font);
|
|
435
504
|
}
|
|
436
505
|
}
|
|
437
506
|
/** Export a single font from the source iModel.
|
|
438
|
-
* @note
|
|
507
|
+
* @note multiple fonts can have the same font name, if a font with a specific type is needed use exportFontByFontFamilyDescriptor.
|
|
508
|
+
* If not this will only export the first font with this type in the db.
|
|
439
509
|
*/
|
|
440
510
|
async exportFontByName(fontName) {
|
|
441
511
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByName(${fontName})`);
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
512
|
+
const fontId = this.sourceDb.fonts.findId({
|
|
513
|
+
name: fontName,
|
|
514
|
+
});
|
|
515
|
+
if (undefined !== fontId) {
|
|
516
|
+
await this.exportFontByNumber(fontId);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/** Export a single font from the source iModel.
|
|
520
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
521
|
+
*/
|
|
522
|
+
async exportFontByFontProps(fontProps) {
|
|
523
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByFamily(${fontProps.name}, ${fontProps.type})`);
|
|
524
|
+
await this.handler.onExportFont(fontProps, true);
|
|
525
|
+
return this.trackProgress();
|
|
526
|
+
}
|
|
527
|
+
/** Export a single font from the source iModel.
|
|
528
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
529
|
+
*/
|
|
530
|
+
async exportFontByFontFamilyDescriptor(fontFamily) {
|
|
531
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByFamilyDescriptor(${fontFamily.name}, ${fontFamily.type})`);
|
|
532
|
+
const fontId = await this.sourceDb.fonts.acquireId(fontFamily);
|
|
533
|
+
if (undefined !== fontId) {
|
|
534
|
+
await this.exportFontByFontProps({ ...fontFamily, id: fontId });
|
|
445
535
|
}
|
|
446
536
|
}
|
|
447
537
|
/** Export a single font from the source iModel.
|
|
448
538
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
449
539
|
*/
|
|
450
540
|
async exportFontByNumber(fontNumber) {
|
|
451
|
-
/**
|
|
452
|
-
*
|
|
541
|
+
/** ChangedInstanceIds contains EC-mapped changes, but the font table is not EC-mapped.
|
|
542
|
+
* Always export fonts because their changes are therefore absent from sourceDbChanges.
|
|
453
543
|
* It is very rare and even problematic for the font table to reach a large size, so it is not a bottleneck in transforming changes.
|
|
454
544
|
* See https://github.com/iTwin/imodel-transformer/pull/135 for removed code.
|
|
455
545
|
*/
|
|
456
|
-
const isUpdate = true;
|
|
457
546
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontById(${fontNumber})`);
|
|
458
|
-
const font = this.sourceDb.
|
|
547
|
+
const font = this.sourceDb.fonts.findDescriptor(fontNumber);
|
|
459
548
|
if (undefined !== font) {
|
|
460
|
-
this.
|
|
461
|
-
return this.trackProgress();
|
|
549
|
+
await this.exportFontByFontFamilyDescriptor(font);
|
|
462
550
|
}
|
|
463
551
|
}
|
|
464
552
|
/** Export the model container, contents, and sub-models from the source iModel.
|
|
465
553
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
466
554
|
*/
|
|
467
555
|
async exportModel(modeledElementId) {
|
|
556
|
+
return this.runScopedElementExport(async () => this.exportModelImpl(modeledElementId));
|
|
557
|
+
}
|
|
558
|
+
async exportModelImpl(modeledElementId) {
|
|
468
559
|
const model = this.sourceDb.models.getModel(modeledElementId);
|
|
469
560
|
if (model.isTemplate && !this.wantTemplateModels) {
|
|
470
561
|
return;
|
|
@@ -475,7 +566,7 @@ class IModelExporter {
|
|
|
475
566
|
wantBRepData: this.wantGeometry,
|
|
476
567
|
});
|
|
477
568
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportModel(${modeledElementId})`);
|
|
478
|
-
if (this.shouldExportElement(modeledElement)) {
|
|
569
|
+
if (await this.shouldExportElement(modeledElement)) {
|
|
479
570
|
await this.exportModelContainer(model);
|
|
480
571
|
if (this.visitElements) {
|
|
481
572
|
await this.exportModelContents(modeledElementId);
|
|
@@ -498,7 +589,7 @@ class IModelExporter {
|
|
|
498
589
|
return; // not in changeset, don't export
|
|
499
590
|
}
|
|
500
591
|
}
|
|
501
|
-
this.handler.onExportModel(model, isUpdate);
|
|
592
|
+
await this.handler.onExportModel(model, isUpdate);
|
|
502
593
|
return this.trackProgress();
|
|
503
594
|
}
|
|
504
595
|
_yieldManager = new core_bentley_1.YieldManager();
|
|
@@ -509,6 +600,9 @@ class IModelExporter {
|
|
|
509
600
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
510
601
|
*/
|
|
511
602
|
async exportModelContents(modelId, elementClassFullName = core_backend_1.Element.classFullName, skipRootSubject) {
|
|
603
|
+
return this.runScopedElementExport(async () => this.exportModelContentsImpl(modelId, elementClassFullName, skipRootSubject));
|
|
604
|
+
}
|
|
605
|
+
async exportModelContentsImpl(modelId, elementClassFullName, skipRootSubject) {
|
|
512
606
|
if (skipRootSubject) {
|
|
513
607
|
// NOTE: IModelTransformer.processAll should skip the root Subject since it is specific to the individual iModel and is not part of the changes that need to be synchronized
|
|
514
608
|
// NOTE: IModelExporter.exportAll should not skip the root Subject since the goal is to export everything
|
|
@@ -533,44 +627,41 @@ class IModelExporter {
|
|
|
533
627
|
else {
|
|
534
628
|
sql = `SELECT ECInstanceId FROM ${elementClassFullName} WHERE Parent.Id IS NULL AND Model.Id=:modelId ORDER BY ECInstanceId`;
|
|
535
629
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
await this._yieldManager.allowYield();
|
|
547
|
-
}
|
|
548
|
-
});
|
|
630
|
+
const params = new core_common_1.QueryBinder().bindId("modelId", modelId);
|
|
631
|
+
if (skipRootSubject) {
|
|
632
|
+
params.bindId("rootSubjectId", core_common_1.IModel.rootSubjectId);
|
|
633
|
+
}
|
|
634
|
+
for await (const row of this.sourceDb.createQueryReader(sql, params, {
|
|
635
|
+
usePrimaryConn: true,
|
|
636
|
+
})) {
|
|
637
|
+
await this.exportElement(row.id);
|
|
638
|
+
await this._yieldManager.allowYield();
|
|
639
|
+
}
|
|
549
640
|
}
|
|
550
641
|
/** Export the sub-models directly below the specified model.
|
|
551
642
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
552
643
|
*/
|
|
553
644
|
async exportSubModels(parentModelId) {
|
|
645
|
+
return this.runScopedElementExport(async () => this.exportSubModelsImpl(parentModelId));
|
|
646
|
+
}
|
|
647
|
+
async exportSubModelsImpl(parentModelId) {
|
|
554
648
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportSubModels(${parentModelId})`);
|
|
555
649
|
const definitionModelIds = [];
|
|
556
650
|
const otherModelIds = [];
|
|
557
651
|
const sql = `SELECT ECInstanceId FROM ${core_backend_1.Model.classFullName} WHERE ParentModel.Id=:parentModelId ORDER BY ECInstanceId`;
|
|
558
|
-
|
|
559
|
-
this.sourceDb.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (model instanceof core_backend_1.DefinitionModel) {
|
|
567
|
-
definitionModelIds.push(modelId);
|
|
568
|
-
}
|
|
569
|
-
else {
|
|
570
|
-
otherModelIds.push(modelId);
|
|
571
|
-
}
|
|
652
|
+
const params = new core_common_1.QueryBinder().bindId("parentModelId", parentModelId);
|
|
653
|
+
for await (const row of this.sourceDb.createQueryReader(sql, params, {
|
|
654
|
+
usePrimaryConn: true,
|
|
655
|
+
})) {
|
|
656
|
+
const modelId = row.id;
|
|
657
|
+
const model = this.sourceDb.models.getModel(modelId);
|
|
658
|
+
if (model instanceof core_backend_1.DefinitionModel) {
|
|
659
|
+
definitionModelIds.push(modelId);
|
|
572
660
|
}
|
|
573
|
-
|
|
661
|
+
else {
|
|
662
|
+
otherModelIds.push(modelId);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
574
665
|
// export DefinitionModels before other types of Models
|
|
575
666
|
for (const definitionModelId of definitionModelIds) {
|
|
576
667
|
await this.exportModel(definitionModelId);
|
|
@@ -583,16 +674,15 @@ class IModelExporter {
|
|
|
583
674
|
* This considers the standard IModelExporter exclusion rules plus calls [IModelExportHandler.shouldExportElement]($transformer) for any custom exclusion rules.
|
|
584
675
|
* @note This method is called from within [[exportChanges]] and [[exportAll]], so usually does not need to be called directly.
|
|
585
676
|
*/
|
|
586
|
-
shouldExportElement(element) {
|
|
677
|
+
async shouldExportElement(element) {
|
|
587
678
|
if (this._excludedElementIds.has(element.id)) {
|
|
588
679
|
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Id`);
|
|
589
680
|
return false;
|
|
590
681
|
}
|
|
591
|
-
if (element instanceof core_backend_1.GeometricElement
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
682
|
+
if (element instanceof core_backend_1.GeometricElement &&
|
|
683
|
+
this._excludedElementCategoryIds.has(element.category)) {
|
|
684
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Category`);
|
|
685
|
+
return false;
|
|
596
686
|
}
|
|
597
687
|
if (!this.wantTemplateModels &&
|
|
598
688
|
element instanceof core_backend_1.RecipeDefinitionElement) {
|
|
@@ -606,12 +696,19 @@ class IModelExporter {
|
|
|
606
696
|
}
|
|
607
697
|
}
|
|
608
698
|
// element has passed standard exclusion rules, now give handler a chance to accept/reject
|
|
609
|
-
|
|
699
|
+
if (!(await this.handler.shouldExportElement(element))) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
return true;
|
|
610
703
|
}
|
|
611
|
-
/** Export the specified element
|
|
704
|
+
/** Export the specified element and its child elements (if applicable).
|
|
612
705
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
706
|
+
* @note ElementAspects are exported separately in owner-scoped groups, so their callbacks are not guaranteed to follow this element callback.
|
|
613
707
|
*/
|
|
614
708
|
async exportElement(elementId) {
|
|
709
|
+
return this.runScopedElementExport(async () => this.exportElementImpl(elementId));
|
|
710
|
+
}
|
|
711
|
+
async exportElementImpl(elementId) {
|
|
615
712
|
if (!this.visitElements) {
|
|
616
713
|
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportElement(${elementId})`);
|
|
617
714
|
return;
|
|
@@ -619,7 +716,7 @@ class IModelExporter {
|
|
|
619
716
|
// Return early if the elementId is already in the excludedElementIds, that way we don't need to load the element from the db.
|
|
620
717
|
if (this._excludedElementIds.has(elementId)) {
|
|
621
718
|
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementId} by Id`);
|
|
622
|
-
this.handler.onSkipElement(elementId);
|
|
719
|
+
await this.handler.onSkipElement(elementId);
|
|
623
720
|
return;
|
|
624
721
|
}
|
|
625
722
|
// are we processing changes?
|
|
@@ -628,6 +725,10 @@ class IModelExporter {
|
|
|
628
725
|
: this._sourceDbChanges?.element.updateIds.has(elementId)
|
|
629
726
|
? true
|
|
630
727
|
: undefined;
|
|
728
|
+
// Short-circuit: element is not in the changeset, skip its own export but still visit children
|
|
729
|
+
if (undefined !== this._sourceDbChanges && undefined === isUpdate) {
|
|
730
|
+
return this.exportChildElements(elementId);
|
|
731
|
+
}
|
|
631
732
|
const element = this.sourceDb.elements.getElement({
|
|
632
733
|
id: elementId,
|
|
633
734
|
wantGeometry: this.wantGeometry,
|
|
@@ -635,21 +736,24 @@ class IModelExporter {
|
|
|
635
736
|
});
|
|
636
737
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportElement(${element.id}, "${element.getDisplayLabel()}")${this.getChangeOpSuffix(isUpdate)}`);
|
|
637
738
|
// the order and `await`ing of calls beyond here is depended upon by the IModelTransformer for a current bug workaround
|
|
638
|
-
if (this.shouldExportElement(element)) {
|
|
739
|
+
if (await this.shouldExportElement(element)) {
|
|
639
740
|
await this.handler.preExportElement(element);
|
|
640
|
-
this.handler.onExportElement(element, isUpdate);
|
|
741
|
+
await this.handler.onExportElement(element, isUpdate);
|
|
641
742
|
await this.trackProgress();
|
|
642
|
-
await this.
|
|
743
|
+
await this._elementAspectExportCoordinator.addAcceptedOwner(elementId);
|
|
643
744
|
return this.exportChildElements(elementId);
|
|
644
745
|
}
|
|
645
746
|
else {
|
|
646
|
-
this.handler.onSkipElement(element.id);
|
|
747
|
+
await this.handler.onSkipElement(element.id);
|
|
647
748
|
}
|
|
648
749
|
}
|
|
649
750
|
/** Export the child elements of the specified element from the source iModel.
|
|
650
751
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
651
752
|
*/
|
|
652
753
|
async exportChildElements(elementId) {
|
|
754
|
+
return this.runScopedElementExport(async () => this.exportChildElementsImpl(elementId));
|
|
755
|
+
}
|
|
756
|
+
async exportChildElementsImpl(elementId) {
|
|
653
757
|
if (!this.visitElements) {
|
|
654
758
|
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportChildElements(${elementId})`);
|
|
655
759
|
return;
|
|
@@ -662,10 +766,212 @@ class IModelExporter {
|
|
|
662
766
|
}
|
|
663
767
|
}
|
|
664
768
|
}
|
|
665
|
-
/** Exports all aspects
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
769
|
+
/** Exports all aspects owned by the supplied elements. */
|
|
770
|
+
async exportAspectsForOwners(ownerElementIds) {
|
|
771
|
+
return this._elementAspectExportCoordinator.exportOwners(ownerElementIds);
|
|
772
|
+
}
|
|
773
|
+
async filterOwnerElementIdsForAspectExport(ownerElementIds) {
|
|
774
|
+
if (ownerElementIds.size === 0)
|
|
775
|
+
return new Set();
|
|
776
|
+
const elementFacts = await this.queryAspectElementHierarchyFacts(ownerElementIds);
|
|
777
|
+
const modelFacts = await this.queryAspectModelHierarchyFacts(new Set([...elementFacts.values()].map((elementFact) => elementFact.modelId)));
|
|
778
|
+
await this.queryAspectElementFacts(new Set(modelFacts.keys()), elementFacts);
|
|
779
|
+
const elementIds = new Set();
|
|
780
|
+
for (const elementId of ownerElementIds) {
|
|
781
|
+
if (this._skipPropagateChangesToRootElements &&
|
|
782
|
+
this._rootElementIds.has(elementId)) {
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (await this.shouldExportElementHierarchyForAspect(elementId, elementFacts, modelFacts)) {
|
|
786
|
+
elementIds.add(elementId);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return elementIds;
|
|
790
|
+
}
|
|
791
|
+
async shouldExportElementHierarchyForAspect(elementId, elementFacts, modelFacts) {
|
|
792
|
+
let elementFact = elementFacts.get(elementId);
|
|
793
|
+
while (elementFact !== undefined) {
|
|
794
|
+
if (!(await this.shouldExportElementForAspect(elementFact.id, elementFact)))
|
|
795
|
+
return false;
|
|
796
|
+
if (!(await this.shouldExportModelForAspect(elementFact.modelId, elementFacts, modelFacts))) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
const parentId = elementFact.parentId;
|
|
800
|
+
if (parentId === undefined ||
|
|
801
|
+
(this._skipPropagateChangesToRootElements &&
|
|
802
|
+
parentId === core_common_1.IModel.rootSubjectId)) {
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
elementFact = elementFacts.get(parentId);
|
|
806
|
+
}
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
async shouldExportModelForAspect(modelId, elementFacts, modelFacts) {
|
|
810
|
+
const modelFact = modelFacts.get(modelId);
|
|
811
|
+
if (modelFact === undefined) {
|
|
812
|
+
this.sourceDb.models.getModel(modelId);
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
if (modelFact.isTemplate && !this.wantTemplateModels)
|
|
816
|
+
return false;
|
|
817
|
+
if (modelFact.id !== core_common_1.IModel.repositoryModelId &&
|
|
818
|
+
modelFact.id !== core_common_1.IModel.dictionaryId &&
|
|
819
|
+
modelFact.id !== "0xe" &&
|
|
820
|
+
!(await this.shouldExportElementForAspect(modelFact.id, elementFacts.get(modelFact.id)))) {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
if (modelFact.parentModelId === undefined ||
|
|
824
|
+
modelFact.parentModelId === modelFact.id) {
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
return this.shouldExportModelForAspect(modelFact.parentModelId, elementFacts, modelFacts);
|
|
828
|
+
}
|
|
829
|
+
async runScopedElementExport(exportElements) {
|
|
830
|
+
await this._elementAspectExportCoordinator.run(exportElements);
|
|
831
|
+
}
|
|
832
|
+
/** Apply the element export filter when deciding whether to process an aspect owner. @internal */
|
|
833
|
+
async shouldExportElementForAspect(elementId, elementFact) {
|
|
834
|
+
if (!this.visitElements)
|
|
835
|
+
return false;
|
|
836
|
+
if (this.shouldExportElement ===
|
|
837
|
+
IModelExporter.prototype.shouldExportElement &&
|
|
838
|
+
elementFact !== undefined &&
|
|
839
|
+
!this.shouldExportElementFact(elementFact)) {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
const element = this.sourceDb.elements.getElement({
|
|
843
|
+
id: elementId,
|
|
844
|
+
wantGeometry: false,
|
|
845
|
+
wantBRepData: false,
|
|
846
|
+
});
|
|
847
|
+
return this.shouldExportElement(element);
|
|
848
|
+
}
|
|
849
|
+
shouldExportElementFact(elementFact) {
|
|
850
|
+
if (this._excludedElementIds.has(elementFact.id)) {
|
|
851
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by Id`);
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
const elementClass = this.sourceDb.getJsClass(elementFact.classFullName);
|
|
855
|
+
if ((elementFact.classFullName === core_backend_1.GeometricElement.classFullName ||
|
|
856
|
+
elementClass.prototype instanceof core_backend_1.GeometricElement) &&
|
|
857
|
+
elementFact.categoryId !== undefined &&
|
|
858
|
+
this._excludedElementCategoryIds.has(elementFact.categoryId)) {
|
|
859
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by Category`);
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
if (!this.wantTemplateModels &&
|
|
863
|
+
(elementFact.classFullName === core_backend_1.RecipeDefinitionElement.classFullName ||
|
|
864
|
+
elementClass.prototype instanceof core_backend_1.RecipeDefinitionElement)) {
|
|
865
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded RecipeDefinitionElement ${elementFact.id} because wantTemplate=false`);
|
|
866
|
+
return false;
|
|
867
|
+
}
|
|
868
|
+
for (const excludedElementClass of this._excludedElementClasses) {
|
|
869
|
+
if (elementFact.classFullName === excludedElementClass.classFullName ||
|
|
870
|
+
elementClass.prototype instanceof excludedElementClass) {
|
|
871
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by class: ${excludedElementClass.classFullName}`);
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
async queryAspectElementHierarchyFacts(elementIds) {
|
|
878
|
+
const elementFacts = new Map();
|
|
879
|
+
const queriedElementIds = new Set();
|
|
880
|
+
let pendingElementIds = new Set(elementIds);
|
|
881
|
+
while (pendingElementIds.size > 0) {
|
|
882
|
+
const currentElementIds = pendingElementIds;
|
|
883
|
+
pendingElementIds = new Set();
|
|
884
|
+
await this.queryAspectElementFacts(currentElementIds, elementFacts);
|
|
885
|
+
for (const elementId of currentElementIds)
|
|
886
|
+
queriedElementIds.add(elementId);
|
|
887
|
+
for (const elementId of currentElementIds) {
|
|
888
|
+
const parentId = elementFacts.get(elementId)?.parentId;
|
|
889
|
+
if (parentId !== undefined &&
|
|
890
|
+
!queriedElementIds.has(parentId) &&
|
|
891
|
+
!elementFacts.has(parentId)) {
|
|
892
|
+
pendingElementIds.add(parentId);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return elementFacts;
|
|
897
|
+
}
|
|
898
|
+
async queryAspectElementFacts(elementIds, elementFacts) {
|
|
899
|
+
const unqueriedElementIds = new Set([...elementIds].filter((elementId) => !elementFacts.has(elementId)));
|
|
900
|
+
if (unqueriedElementIds.size === 0)
|
|
901
|
+
return;
|
|
902
|
+
for (const elementIdBatch of this.getAspectOwnerIdBatches(unqueriedElementIds)) {
|
|
903
|
+
const queryParams = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIdBatch);
|
|
904
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT e.ECInstanceId id, e.Parent.Id parentId, e.Model.Id modelId,
|
|
905
|
+
ec_className(e.ECClassId, 's') schemaName,
|
|
906
|
+
ec_className(e.ECClassId, 'c') className
|
|
907
|
+
FROM bis.Element e
|
|
908
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = e.ECInstanceId
|
|
909
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, queryParams, { usePrimaryConn: true })) {
|
|
910
|
+
elementFacts.set(row.id, {
|
|
911
|
+
id: row.id,
|
|
912
|
+
parentId: row.parentId ?? undefined,
|
|
913
|
+
modelId: row.modelId,
|
|
914
|
+
classFullName: `${row.schemaName}:${row.className}`,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
if (this._excludedElementCategoryIds.size > 0) {
|
|
918
|
+
const categoryQueryParams = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIdBatch);
|
|
919
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT g.ECInstanceId id, g.Category.Id categoryId
|
|
920
|
+
FROM bis.GeometricElement g
|
|
921
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = g.ECInstanceId
|
|
922
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, categoryQueryParams, { usePrimaryConn: true })) {
|
|
923
|
+
const elementFact = elementFacts.get(row.id);
|
|
924
|
+
if (elementFact !== undefined) {
|
|
925
|
+
elementFact.categoryId = row.categoryId;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
async queryAspectModelHierarchyFacts(modelIds) {
|
|
932
|
+
const modelFacts = new Map();
|
|
933
|
+
const queriedModelIds = new Set();
|
|
934
|
+
let pendingModelIds = new Set(modelIds);
|
|
935
|
+
while (pendingModelIds.size > 0) {
|
|
936
|
+
const currentModelIds = pendingModelIds;
|
|
937
|
+
pendingModelIds = new Set();
|
|
938
|
+
for (const modelIdBatch of this.getAspectOwnerIdBatches(currentModelIds)) {
|
|
939
|
+
const queryParams = new core_common_1.QueryBinder().bindIdSet("modelIds", modelIdBatch);
|
|
940
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT model.ECInstanceId id, model.ParentModel.Id parentModelId, model.IsTemplate isTemplate
|
|
941
|
+
FROM bis.Model model
|
|
942
|
+
INNER JOIN IdSet(:modelIds) ids ON ids.id = model.ECInstanceId
|
|
943
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, queryParams, { usePrimaryConn: true })) {
|
|
944
|
+
modelFacts.set(row.id, {
|
|
945
|
+
id: row.id,
|
|
946
|
+
parentModelId: row.parentModelId ?? undefined,
|
|
947
|
+
isTemplate: row.isTemplate === true || row.isTemplate === 1,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
for (const modelId of currentModelIds)
|
|
952
|
+
queriedModelIds.add(modelId);
|
|
953
|
+
for (const modelId of currentModelIds) {
|
|
954
|
+
const parentModelId = modelFacts.get(modelId)?.parentModelId;
|
|
955
|
+
if (parentModelId !== undefined &&
|
|
956
|
+
!queriedModelIds.has(parentModelId) &&
|
|
957
|
+
!modelFacts.has(parentModelId)) {
|
|
958
|
+
pendingModelIds.add(parentModelId);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return modelFacts;
|
|
963
|
+
}
|
|
964
|
+
*getAspectOwnerIdBatches(ids) {
|
|
965
|
+
let batch = new Set();
|
|
966
|
+
for (const id of ids) {
|
|
967
|
+
batch.add(id);
|
|
968
|
+
if (batch.size === IModelExporter._elementAspectOwnerBatchSize) {
|
|
969
|
+
yield batch;
|
|
970
|
+
batch = new Set();
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (batch.size > 0)
|
|
974
|
+
yield batch;
|
|
669
975
|
}
|
|
670
976
|
/** Exports all relationships that subclass from the specified base class.
|
|
671
977
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
@@ -676,23 +982,18 @@ class IModelExporter {
|
|
|
676
982
|
return;
|
|
677
983
|
}
|
|
678
984
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationships(${baseRelClassFullName})`);
|
|
679
|
-
const sql = `SELECT r.ECInstanceId, r.ECClassId FROM ${baseRelClassFullName} r
|
|
985
|
+
const sql = `SELECT r.ECInstanceId, ec_className(r.ECClassId, 's.c') as className FROM ${baseRelClassFullName} r
|
|
680
986
|
JOIN bis.Element s ON s.ECInstanceId = r.SourceECInstanceId
|
|
681
987
|
JOIN bis.Element t ON t.ECInstanceId = r.TargetECInstanceId
|
|
682
988
|
WHERE s.ECInstanceId IS NOT NULL AND t.ECInstanceId IS NOT NULL`;
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
.getClassNameForClassId();
|
|
692
|
-
await this.exportRelationship(relationshipClass, relationshipId); // must call exportRelationship using the actual classFullName, not baseRelClassFullName
|
|
693
|
-
await this._yieldManager.allowYield();
|
|
694
|
-
}
|
|
695
|
-
});
|
|
989
|
+
for await (const row of this.sourceDb.createQueryReader(sql, undefined, {
|
|
990
|
+
usePrimaryConn: true,
|
|
991
|
+
})) {
|
|
992
|
+
const relationshipId = row.id;
|
|
993
|
+
const relationshipClass = row.className;
|
|
994
|
+
await this.exportRelationship(relationshipClass, relationshipId); // must call exportRelationship using the actual classFullName, not baseRelClassFullName
|
|
995
|
+
await this._yieldManager.allowYield();
|
|
996
|
+
}
|
|
696
997
|
}
|
|
697
998
|
/** Export a relationship from the source iModel. */
|
|
698
999
|
async exportRelationship(relClassFullName, relInstanceId) {
|
|
@@ -723,8 +1024,8 @@ class IModelExporter {
|
|
|
723
1024
|
}
|
|
724
1025
|
}
|
|
725
1026
|
// relationship has passed standard exclusion rules, now give handler a chance to accept/reject export
|
|
726
|
-
if (this.handler.shouldExportRelationship(relationship)) {
|
|
727
|
-
this.handler.onExportRelationship(relationship, isUpdate);
|
|
1027
|
+
if (await this.handler.shouldExportRelationship(relationship)) {
|
|
1028
|
+
await this.handler.onExportRelationship(relationship, isUpdate);
|
|
728
1029
|
await this.trackProgress();
|
|
729
1030
|
}
|
|
730
1031
|
}
|
|
@@ -783,6 +1084,13 @@ class ChangedInstanceIds {
|
|
|
783
1084
|
_aspectSubclassIds;
|
|
784
1085
|
_relationshipSubclassIds;
|
|
785
1086
|
_relationshipSubclassIdsToSkip;
|
|
1087
|
+
_aspectOwnerElementIds = new Set();
|
|
1088
|
+
/** Element IDs that own the aspects represented by `aspect` changes.
|
|
1089
|
+
* @internal
|
|
1090
|
+
*/
|
|
1091
|
+
get aspectOwnerElementIds() {
|
|
1092
|
+
return this._aspectOwnerElementIds;
|
|
1093
|
+
}
|
|
786
1094
|
_db;
|
|
787
1095
|
constructor(db) {
|
|
788
1096
|
this._db = db;
|
|
@@ -795,7 +1103,7 @@ class ChangedInstanceIds {
|
|
|
795
1103
|
this._relationshipSubclassIds = new Set();
|
|
796
1104
|
this._relationshipSubclassIdsToSkip = new Set();
|
|
797
1105
|
const addECClassIdsToSet = async (setToModify, baseClass) => {
|
|
798
|
-
for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})
|
|
1106
|
+
for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})`, undefined, { usePrimaryConn: true })) {
|
|
799
1107
|
setToModify.add(row.ECInstanceId);
|
|
800
1108
|
}
|
|
801
1109
|
};
|
|
@@ -845,33 +1153,63 @@ class ChangedInstanceIds {
|
|
|
845
1153
|
!this.font.isEmpty);
|
|
846
1154
|
}
|
|
847
1155
|
/**
|
|
848
|
-
* Adds the provided [[
|
|
1156
|
+
* Adds the provided [[ChangeInstance]] to the appropriate set of changes by class type (codeSpec, model, element, aspect, or relationship) maintained by this instance of ChangedInstanceIds.
|
|
849
1157
|
* If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
|
|
850
1158
|
* from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
|
|
851
|
-
* @param change
|
|
1159
|
+
* @param change Changed EC instance with the ID, operation, and EC class ID of the changed entity.
|
|
852
1160
|
*/
|
|
853
1161
|
async addChange(change) {
|
|
854
1162
|
if (!this._ecClassIdsInitialized)
|
|
855
1163
|
await this.setupECClassIds();
|
|
856
|
-
const ecClassId = change.ECClassId
|
|
1164
|
+
const ecClassId = change.ECClassId;
|
|
857
1165
|
if (ecClassId === undefined)
|
|
858
|
-
|
|
859
|
-
|
|
1166
|
+
core_bentley_1.ITwinError.throwError({
|
|
1167
|
+
iTwinErrorId: {
|
|
1168
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1169
|
+
key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
|
|
1170
|
+
},
|
|
1171
|
+
message: `ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change.$meta.tables}`,
|
|
1172
|
+
});
|
|
1173
|
+
const changeType = change.$meta.op;
|
|
860
1174
|
if (changeType === undefined)
|
|
861
|
-
|
|
1175
|
+
core_bentley_1.ITwinError.throwError({
|
|
1176
|
+
iTwinErrorId: {
|
|
1177
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1178
|
+
key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
|
|
1179
|
+
},
|
|
1180
|
+
message: `ChangeType was undefined for id: ${change.ECInstanceId}.`,
|
|
1181
|
+
});
|
|
862
1182
|
if (this._relationshipSubclassIdsToSkip?.has(ecClassId))
|
|
863
1183
|
return;
|
|
864
1184
|
if (this.isRelationship(ecClassId))
|
|
865
1185
|
this.handleChange(this.relationship, changeType, change.ECInstanceId);
|
|
866
1186
|
else if (this.isCodeSpec(ecClassId))
|
|
867
1187
|
this.handleChange(this.codeSpec, changeType, change.ECInstanceId);
|
|
868
|
-
else if (this.isAspect(ecClassId))
|
|
1188
|
+
else if (this.isAspect(ecClassId)) {
|
|
1189
|
+
const ownerElementId = change.Element?.Id ??
|
|
1190
|
+
this.tryGetAspectOwnerElementId(change.ECInstanceId);
|
|
1191
|
+
if (ownerElementId !== undefined) {
|
|
1192
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1193
|
+
}
|
|
869
1194
|
this.handleChange(this.aspect, changeType, change.ECInstanceId);
|
|
1195
|
+
}
|
|
870
1196
|
else if (this.isModel(ecClassId))
|
|
871
1197
|
this.handleChange(this.model, changeType, change.ECInstanceId);
|
|
872
1198
|
else if (this.isElement(ecClassId))
|
|
873
1199
|
this.handleChange(this.element, changeType, change.ECInstanceId);
|
|
874
1200
|
}
|
|
1201
|
+
tryGetAspectOwnerElementId(aspectId) {
|
|
1202
|
+
try {
|
|
1203
|
+
return this._db.elements.getAspect(aspectId).element.id;
|
|
1204
|
+
}
|
|
1205
|
+
catch (error) {
|
|
1206
|
+
if (error instanceof core_common_1.IModelError &&
|
|
1207
|
+
error.errorNumber === core_bentley_1.IModelStatus.NotFound) {
|
|
1208
|
+
return undefined;
|
|
1209
|
+
}
|
|
1210
|
+
throw error;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
875
1213
|
/**
|
|
876
1214
|
* This method should only be called inside [[IModelTransformer.addCustomChanges]].
|
|
877
1215
|
* It adds the provided change to the element changes maintained by this instance of ChangedInstanceIds.
|
|
@@ -933,14 +1271,47 @@ class ChangedInstanceIds {
|
|
|
933
1271
|
* If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
|
|
934
1272
|
* from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
|
|
935
1273
|
* @note It is the responsibility of the caller to ensure that the provided id is, in fact an aspect.
|
|
1274
|
+
* @param elementIds Owning element IDs. Required when `changeType` is
|
|
1275
|
+
* `Deleted` or the source aspect row is unavailable.
|
|
936
1276
|
* @note In most cases, this method does not need to be called. Its only for consumers to mimic changes as if they were found in a changeset, which should only be useful in certain cases such as the changing of filter criteria for a preexisting master branch relationship.
|
|
937
1277
|
* @beta
|
|
938
1278
|
*/
|
|
939
|
-
addCustomAspectChange(changeType, ids) {
|
|
1279
|
+
addCustomAspectChange(changeType, ids, elementIds) {
|
|
1280
|
+
if (elementIds !== undefined) {
|
|
1281
|
+
for (const elementId of core_bentley_1.Id64.iterable(elementIds)) {
|
|
1282
|
+
this._aspectOwnerElementIds.add(elementId);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
940
1285
|
for (const id of core_bentley_1.Id64.iterable(ids)) {
|
|
1286
|
+
if (elementIds === undefined && changeType === "Deleted") {
|
|
1287
|
+
core_bentley_1.ITwinError.throwError({
|
|
1288
|
+
iTwinErrorId: {
|
|
1289
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1290
|
+
key: IModelTransformerError_1.IModelTransformerError.AspectOwnerRequired,
|
|
1291
|
+
},
|
|
1292
|
+
message: "Custom deleted ElementAspect changes require the owning element ID.",
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
if (elementIds === undefined && changeType !== "Deleted") {
|
|
1296
|
+
const ownerElementId = this.tryGetAspectOwnerElementId(id);
|
|
1297
|
+
if (ownerElementId === undefined) {
|
|
1298
|
+
core_bentley_1.ITwinError.throwError({
|
|
1299
|
+
iTwinErrorId: {
|
|
1300
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1301
|
+
key: IModelTransformerError_1.IModelTransformerError.AspectOwnerRequired,
|
|
1302
|
+
},
|
|
1303
|
+
message: "Custom ElementAspect changes require the owning element ID when the source aspect is unavailable.",
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1307
|
+
}
|
|
941
1308
|
this.handleChange(this.aspect, changeType, id);
|
|
942
1309
|
}
|
|
943
1310
|
}
|
|
1311
|
+
recordCustomAspectChange(changeType, aspectId, ownerElementId) {
|
|
1312
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1313
|
+
this.handleChange(this.aspect, changeType, aspectId);
|
|
1314
|
+
}
|
|
944
1315
|
/**
|
|
945
1316
|
* There is an optimization in [IModelExporter.exportModelContents] which doesn't try to export elements within a model unless the model itself is marked as `Updated` or 'Inserted' in sourceDbChanges. This method is used in [[addCustomElementChange]] and [[addCustomModelChange]] to add the parent model hierarchy to the 'updatedIds' so that the custom element changes are exported.
|
|
946
1317
|
* Transformer will insert 'Updated' model to target if it does not exist there already. To handle such case, modeled elements of parent models are also marked as updated. This is done, because model can not be inserted without it's modeled element.
|
|
@@ -949,16 +1320,21 @@ class ChangedInstanceIds {
|
|
|
949
1320
|
const params = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
950
1321
|
const ecQuery = `
|
|
951
1322
|
WITH RECURSIVE hierarchy (parentId) AS (
|
|
952
|
-
SELECT Model.Id
|
|
1323
|
+
SELECT element.Model.Id
|
|
1324
|
+
FROM bis.Element element
|
|
1325
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
953
1326
|
UNION
|
|
954
1327
|
SELECT ParentModel.id
|
|
955
1328
|
FROM bis.Model e
|
|
956
1329
|
INNER JOIN hierarchy h ON h.parentId = e.ECInstanceId
|
|
957
1330
|
)
|
|
958
1331
|
SELECT parentId FROM hierarchy where parentId is not null
|
|
1332
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES
|
|
959
1333
|
`;
|
|
960
1334
|
const parentModelIds = new Set();
|
|
961
|
-
for await (const row of this._db.createQueryReader(ecQuery, params
|
|
1335
|
+
for await (const row of this._db.createQueryReader(ecQuery, params, {
|
|
1336
|
+
usePrimaryConn: true,
|
|
1337
|
+
})) {
|
|
962
1338
|
// Transformer handles update as insert when element does not exist in target.
|
|
963
1339
|
// Which means that in scenario where child and parent model are filtered out from target,
|
|
964
1340
|
// and child element is inserted trough custom change, its parent model will be marked as updated.
|
|
@@ -972,11 +1348,20 @@ class ChangedInstanceIds {
|
|
|
972
1348
|
return parentModelIds;
|
|
973
1349
|
}
|
|
974
1350
|
async markElementRelationshipsAsInserted(relationshipClassName, elementIds) {
|
|
975
|
-
const ecQuery = `SELECT ECInstanceId
|
|
976
|
-
|
|
977
|
-
|
|
1351
|
+
const ecQuery = `SELECT relationship.ECInstanceId
|
|
1352
|
+
FROM ${relationshipClassName} relationship
|
|
1353
|
+
INNER JOIN IdSet(:elementIds) ids
|
|
1354
|
+
ON ids.id = relationship.TargetECInstanceId
|
|
1355
|
+
UNION
|
|
1356
|
+
SELECT relationship.ECInstanceId
|
|
1357
|
+
FROM ${relationshipClassName} relationship
|
|
1358
|
+
INNER JOIN IdSet(:elementIds) ids
|
|
1359
|
+
ON ids.id = relationship.SourceECInstanceId
|
|
1360
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`;
|
|
978
1361
|
const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
979
|
-
const queryReader = this._db.createQueryReader(ecQuery, queryBinder
|
|
1362
|
+
const queryReader = this._db.createQueryReader(ecQuery, queryBinder, {
|
|
1363
|
+
usePrimaryConn: true,
|
|
1364
|
+
});
|
|
980
1365
|
for await (const row of queryReader) {
|
|
981
1366
|
this.handleChange(this.relationship, "Inserted", row.ECInstanceId);
|
|
982
1367
|
}
|
|
@@ -986,11 +1371,17 @@ class ChangedInstanceIds {
|
|
|
986
1371
|
core_backend_1.ElementUniqueAspect.classFullName,
|
|
987
1372
|
core_backend_1.ElementMultiAspect.classFullName,
|
|
988
1373
|
]) {
|
|
989
|
-
const ecQuery = `
|
|
1374
|
+
const ecQuery = `SELECT aspect.ECInstanceId, aspect.Element.Id
|
|
1375
|
+
FROM ${aspectClassName} aspect
|
|
1376
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = aspect.Element.Id
|
|
1377
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`;
|
|
990
1378
|
const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
991
|
-
const queryReader = this._db.createQueryReader(ecQuery, queryBinder
|
|
1379
|
+
const queryReader = this._db.createQueryReader(ecQuery, queryBinder, {
|
|
1380
|
+
usePrimaryConn: true,
|
|
1381
|
+
});
|
|
992
1382
|
for await (const row of queryReader) {
|
|
993
|
-
|
|
1383
|
+
const [aspectId, elementId] = row.toArray();
|
|
1384
|
+
this.recordCustomAspectChange("Inserted", aspectId, elementId);
|
|
994
1385
|
}
|
|
995
1386
|
}
|
|
996
1387
|
}
|
|
@@ -1057,23 +1448,7 @@ class ChangedInstanceIds {
|
|
|
1057
1448
|
if (csFileProps === undefined)
|
|
1058
1449
|
return undefined;
|
|
1059
1450
|
const changedInstanceIds = new ChangedInstanceIds(opts.iModel);
|
|
1060
|
-
|
|
1061
|
-
const csReader = core_backend_1.SqliteChangesetReader.openFile({
|
|
1062
|
-
fileName: csFile.pathname,
|
|
1063
|
-
db: opts.iModel,
|
|
1064
|
-
disableSchemaCheck: true,
|
|
1065
|
-
});
|
|
1066
|
-
const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
|
|
1067
|
-
const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier(opts.iModel);
|
|
1068
|
-
while (csAdaptor.step()) {
|
|
1069
|
-
ecChangeUnifier.appendFrom(csAdaptor);
|
|
1070
|
-
}
|
|
1071
|
-
const changes = [...ecChangeUnifier.instances];
|
|
1072
|
-
for (const change of changes) {
|
|
1073
|
-
await changedInstanceIds.addChange(change);
|
|
1074
|
-
}
|
|
1075
|
-
csReader.close();
|
|
1076
|
-
}
|
|
1451
|
+
await ChangesetScanner_1.ChangesetScanner.scan(opts.iModel, csFileProps, changedInstanceIds);
|
|
1077
1452
|
return changedInstanceIds;
|
|
1078
1453
|
}
|
|
1079
1454
|
}
|