@itwin/imodel-transformer 2.0.0-dev.5 → 2.0.0-dev.50
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 +13 -27
- package/lib/cjs/Algo.js.map +1 -1
- package/lib/cjs/BigMap.d.ts.map +1 -1
- package/lib/cjs/BigMap.js.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +34 -36
- 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/ECSqlReaderAsyncIterableIteratorAdapter.d.ts.map +1 -1
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.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 +43 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.js +167 -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/EntityExistenceCache.d.ts +43 -0
- package/lib/cjs/EntityExistenceCache.d.ts.map +1 -0
- package/lib/cjs/EntityExistenceCache.js +91 -0
- package/lib/cjs/EntityExistenceCache.js.map +1 -0
- package/lib/cjs/EntityMap.d.ts.map +1 -1
- package/lib/cjs/EntityMap.js.map +1 -1
- package/lib/cjs/EntityUnifier.d.ts +8 -8
- package/lib/cjs/EntityUnifier.d.ts.map +1 -1
- package/lib/cjs/EntityUnifier.js +55 -32
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts +11 -5
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +94 -58
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +108 -34
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +702 -208
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +50 -25
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +231 -130
- 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 +125 -203
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +646 -1190
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/IModelTransformerError.d.ts +82 -0
- package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
- package/lib/cjs/IModelTransformerError.js +91 -0
- package/lib/cjs/IModelTransformerError.js.map +1 -0
- package/lib/cjs/ProvenanceManager.d.ts +182 -0
- package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
- package/lib/cjs/ProvenanceManager.js +743 -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 +47 -0
- package/lib/cjs/TransformerPackageMetadata.js.map +1 -0
- package/lib/cjs/imodel-transformer.d.ts +19 -0
- package/lib/cjs/imodel-transformer.d.ts.map +1 -1
- package/lib/cjs/imodel-transformer.js +53 -34
- package/lib/cjs/imodel-transformer.js.map +1 -1
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts +29 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts.map +1 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js +212 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts +25 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js +196 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts +59 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.js +28 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.js.map +1 -0
- package/lib/cjs/schema-processing.d.ts +7 -0
- package/lib/cjs/schema-processing.d.ts.map +1 -0
- package/lib/cjs/schema-processing.js +27 -0
- package/lib/cjs/schema-processing.js.map +1 -0
- package/package.json +65 -43
- 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,29 +88,31 @@ 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.
|
|
100
103
|
* @param relationship The Relationship to export
|
|
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.
|
|
105
|
+
* @param sourceFedGuid The FederationGuid of the relationship's source endpoint, if available.
|
|
106
|
+
* @param targetFedGuid The FederationGuid of the relationship's target endpoint, if available.
|
|
102
107
|
* @note This should be overridden to actually do the export.
|
|
103
108
|
*/
|
|
104
|
-
onExportRelationship(_relationship, _isUpdate) { }
|
|
109
|
+
async onExportRelationship(_relationship, _isUpdate, _sourceFedGuid, _targetFedGuid) { }
|
|
105
110
|
/** Called when a relationship should be deleted. */
|
|
106
|
-
onDeleteRelationship(_relInstanceId) { }
|
|
111
|
+
async onDeleteRelationship(_relInstanceId) { }
|
|
107
112
|
/** If `true` is returned, then the schema will be exported.
|
|
108
113
|
* @note This method can optionally be overridden to exclude an individual schema from the export. The base implementation always returns `true`.
|
|
109
114
|
*/
|
|
110
|
-
shouldExportSchema(_schemaKey) {
|
|
115
|
+
async shouldExportSchema(_schemaKey) {
|
|
111
116
|
return true;
|
|
112
117
|
}
|
|
113
118
|
/** Called when a schema should be exported.
|
|
@@ -126,10 +131,14 @@ class IModelExportHandler {
|
|
|
126
131
|
exports.IModelExportHandler = IModelExportHandler;
|
|
127
132
|
/** Base class for exporting data from an iModel.
|
|
128
133
|
* @note Most uses cases will not require a custom subclass of `IModelExporter`. Instead, it is more typical to subclass/customize [IModelExportHandler]($transformer).
|
|
134
|
+
* ElementAspects are exported separately from element callbacks in bounded,
|
|
135
|
+
* owner-scoped groups. Aspect callbacks are not guaranteed to be adjacent to
|
|
136
|
+
* callbacks for their owners.
|
|
129
137
|
* @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md), [[registerHandler]], [IModelTransformer]($transformer), [IModelImporter]($transformer)
|
|
130
138
|
* @beta
|
|
131
139
|
*/
|
|
132
140
|
class IModelExporter {
|
|
141
|
+
static _elementAspectOwnerBatchSize = 1000;
|
|
133
142
|
/** The read-only source iModel. */
|
|
134
143
|
sourceDb;
|
|
135
144
|
/** A flag that indicates whether element GeometryStreams are loaded or not.
|
|
@@ -175,7 +184,13 @@ class IModelExporter {
|
|
|
175
184
|
/** The handler called by this IModelExporter. */
|
|
176
185
|
get handler() {
|
|
177
186
|
if (undefined === this._handler) {
|
|
178
|
-
|
|
187
|
+
core_bentley_1.ITwinError.throwError({
|
|
188
|
+
iTwinErrorId: {
|
|
189
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
190
|
+
key: IModelTransformerError_1.IModelTransformerError.ExportHandlerNotRegistered,
|
|
191
|
+
},
|
|
192
|
+
message: "IModelExportHandler not registered",
|
|
193
|
+
});
|
|
179
194
|
}
|
|
180
195
|
return this._handler;
|
|
181
196
|
}
|
|
@@ -189,20 +204,37 @@ class IModelExporter {
|
|
|
189
204
|
_excludedElementClasses = new Set();
|
|
190
205
|
/** The set of classes of Relationships that will be excluded (polymorphically) from transformation to the target iModel. */
|
|
191
206
|
_excludedRelationshipClasses = new Set();
|
|
192
|
-
/**
|
|
193
|
-
|
|
207
|
+
/** Exports ElementAspects for accepted owner groups. */
|
|
208
|
+
_elementAspectExportProcessor;
|
|
209
|
+
/** Coordinates accepted-owner scopes and bounded group processing. */
|
|
210
|
+
_elementAspectExportCoordinator;
|
|
211
|
+
/** Coordinates accepted ElementAspect owners and bounded group processing.
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
get elementAspectExportCoordinator() {
|
|
215
|
+
return this._elementAspectExportCoordinator;
|
|
216
|
+
}
|
|
217
|
+
/** Whether change traversal skips root-owned entities. */
|
|
218
|
+
_skipPropagateChangesToRootElements = false;
|
|
219
|
+
_rootElementIds = new Set([
|
|
220
|
+
core_common_1.IModel.rootSubjectId,
|
|
221
|
+
core_common_1.IModel.repositoryModelId,
|
|
222
|
+
core_common_1.IModel.dictionaryId,
|
|
223
|
+
"0xe", // The reality-data-sources modelId, skipped with root propagation.
|
|
224
|
+
]);
|
|
194
225
|
/** Construct a new IModelExporter
|
|
195
226
|
* @param sourceDb The source IModelDb
|
|
196
227
|
* @see registerHandler
|
|
197
228
|
*/
|
|
198
|
-
constructor(sourceDb
|
|
229
|
+
constructor(sourceDb) {
|
|
199
230
|
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),
|
|
231
|
+
this._elementAspectExportProcessor = new ElementAspectExportProcessor_1.ElementAspectExportProcessor(this.sourceDb, {
|
|
232
|
+
onExportElementMultiAspects: async (aspects) => this.handler.onExportElementMultiAspects(aspects),
|
|
233
|
+
onExportElementUniqueAspect: async (aspect, isUpdate) => this.handler.onExportElementUniqueAspect(aspect, isUpdate),
|
|
234
|
+
shouldExportElementAspect: async (aspect) => this.handler.shouldExportElementAspect(aspect),
|
|
204
235
|
trackProgress: async () => this.trackProgress(),
|
|
205
236
|
});
|
|
237
|
+
this._elementAspectExportCoordinator = new ElementAspectExportCoordinator_1.ElementAspectExportCoordinator(IModelExporter._elementAspectOwnerBatchSize, () => this._elementAspectExportProcessor.excludedElementAspectClassFullNames, async (elementIds) => this._elementAspectExportProcessor.exportAllElementAspects(elementIds), () => this._elementAspectExportProcessor.resetCaches());
|
|
206
238
|
}
|
|
207
239
|
/**
|
|
208
240
|
* Initialize prerequisites of exporting. This is implicitly done by any `export*` calls that need initialization
|
|
@@ -220,7 +252,7 @@ class IModelExporter {
|
|
|
220
252
|
});
|
|
221
253
|
if (this._sourceDbChanges === undefined)
|
|
222
254
|
return;
|
|
223
|
-
this.
|
|
255
|
+
this._elementAspectExportProcessor.setAspectChanges(this._sourceDbChanges.aspect);
|
|
224
256
|
}
|
|
225
257
|
/** Register the handler that will be called by IModelExporter. */
|
|
226
258
|
registerHandler(handler) {
|
|
@@ -244,7 +276,7 @@ class IModelExporter {
|
|
|
244
276
|
}
|
|
245
277
|
/** Add a rule to exclude all ElementAspects of a specified class. */
|
|
246
278
|
excludeElementAspectClass(classFullName) {
|
|
247
|
-
this.
|
|
279
|
+
this._elementAspectExportProcessor.excludeElementAspectClass(classFullName);
|
|
248
280
|
}
|
|
249
281
|
/** Add a rule to exclude all Relationships of a specified class. */
|
|
250
282
|
excludeRelationshipClass(classFullName) {
|
|
@@ -257,11 +289,12 @@ class IModelExporter {
|
|
|
257
289
|
await this.initialize({});
|
|
258
290
|
await this.exportCodeSpecs();
|
|
259
291
|
await this.exportFonts();
|
|
260
|
-
await this.exportModel(core_common_1.IModel.repositoryModelId);
|
|
292
|
+
await this._elementAspectExportCoordinator.run(async () => this.exportModel(core_common_1.IModel.repositoryModelId));
|
|
261
293
|
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
262
294
|
}
|
|
263
295
|
/** Export changes from the source iModel.
|
|
264
296
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
297
|
+
* @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
298
|
* @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired
|
|
266
299
|
* range and open the source iModel as of the end (inclusive) of the desired range.
|
|
267
300
|
* @note the changedInstanceIds are just for this call to exportChanges, so you must continue to pass it in
|
|
@@ -269,35 +302,68 @@ class IModelExporter {
|
|
|
269
302
|
*/
|
|
270
303
|
async exportChanges(args) {
|
|
271
304
|
if (!this.sourceDb.isBriefcaseDb())
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
305
|
+
core_bentley_1.ITwinError.throwError({
|
|
306
|
+
iTwinErrorId: {
|
|
307
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
308
|
+
key: IModelTransformerError_1.IModelTransformerError.ExportChangesRequiresBriefcase,
|
|
309
|
+
},
|
|
310
|
+
message: "Must be a briefcase to export changes",
|
|
311
|
+
});
|
|
312
|
+
let initOpts = args ?? {};
|
|
313
|
+
const hasExplicitChangeSource = "csFileProps" in initOpts ||
|
|
314
|
+
"changedInstanceIds" in initOpts ||
|
|
315
|
+
"changesetRanges" in initOpts ||
|
|
316
|
+
"startChangeset" in initOpts;
|
|
317
|
+
const currentChangesetId = this.sourceDb.changeset.id;
|
|
318
|
+
if (!hasExplicitChangeSource && currentChangesetId !== "") {
|
|
319
|
+
initOpts = {
|
|
320
|
+
...initOpts,
|
|
321
|
+
startChangeset: { id: currentChangesetId },
|
|
322
|
+
};
|
|
277
323
|
}
|
|
278
|
-
const startChangeset = args && "startChangeset" in args ? args.startChangeset : undefined;
|
|
279
|
-
const initOpts = {
|
|
280
|
-
startChangeset: { id: startChangeset?.id },
|
|
281
|
-
};
|
|
282
324
|
await this.initialize(initOpts);
|
|
325
|
+
if (currentChangesetId === "" && !this.sourceDbChanges?.hasChanges) {
|
|
326
|
+
core_bentley_1.ITwinError.throwError({
|
|
327
|
+
iTwinErrorId: {
|
|
328
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
329
|
+
key: IModelTransformerError_1.IModelTransformerError.NoChangesets,
|
|
330
|
+
},
|
|
331
|
+
message: "Cannot export changes because the source iModel has no changesets or custom changes. Call exportAll() to export all content.",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
this._skipPropagateChangesToRootElements =
|
|
335
|
+
initOpts.skipPropagateChangesToRootElements ?? false;
|
|
283
336
|
// _sourceDbChanges are initialized in this.initialize
|
|
284
|
-
|
|
337
|
+
(0, node_assert_1.strict)(this._sourceDbChanges !== undefined, "sourceDbChanges must be initialized.");
|
|
285
338
|
await this.exportCodeSpecs();
|
|
286
339
|
await this.exportFonts();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
340
|
+
await this._elementAspectExportCoordinator.run(async () => {
|
|
341
|
+
if (initOpts.skipPropagateChangesToRootElements) {
|
|
342
|
+
// The root Subject is in the RepositoryModel. Traverse its children
|
|
343
|
+
// separately, then export other top-level repository elements while
|
|
344
|
+
// excluding the root so no element is visited twice.
|
|
345
|
+
await this.exportChildElements(core_common_1.IModel.rootSubjectId);
|
|
346
|
+
await this.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true);
|
|
347
|
+
await this.exportSubModels(core_common_1.IModel.repositoryModelId);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
await this.exportModel(core_common_1.IModel.repositoryModelId);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
const aspectOnlyOwnerElementIds = new Set(this._sourceDbChanges.aspectOwnerElementIds);
|
|
354
|
+
for (const elementId of this._sourceDbChanges.element.insertIds) {
|
|
355
|
+
aspectOnlyOwnerElementIds.delete(elementId);
|
|
290
356
|
}
|
|
291
|
-
|
|
292
|
-
|
|
357
|
+
for (const elementId of this._sourceDbChanges.element.updateIds) {
|
|
358
|
+
aspectOnlyOwnerElementIds.delete(elementId);
|
|
293
359
|
}
|
|
294
|
-
await this.
|
|
360
|
+
await this.exportAspectsForOwners(await this.filterOwnerElementIdsForAspectExport(aspectOnlyOwnerElementIds));
|
|
295
361
|
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
296
362
|
// handle deletes
|
|
297
363
|
if (this.visitElements) {
|
|
298
364
|
// must delete models first since they have a constraint on the submodeling element which may also be deleted
|
|
299
365
|
for (const modelId of this._sourceDbChanges.model.deleteIds) {
|
|
300
|
-
this.handler.onDeleteModel(modelId);
|
|
366
|
+
await this.handler.onDeleteModel(modelId);
|
|
301
367
|
}
|
|
302
368
|
for (const elementId of this._sourceDbChanges.element.deleteIds) {
|
|
303
369
|
// We don't know how the handler wants to handle deletions, and we don't have enough information
|
|
@@ -307,7 +373,7 @@ class IModelExporter {
|
|
|
307
373
|
// In the future, the handler may be responsible for doing the work of finding out which elements were cascade deleted,
|
|
308
374
|
// and returning them for the exporter to use to avoid double-deleting with error ignoring
|
|
309
375
|
try {
|
|
310
|
-
this.handler.onDeleteElement(elementId);
|
|
376
|
+
await this.handler.onDeleteElement(elementId);
|
|
311
377
|
}
|
|
312
378
|
catch (err) {
|
|
313
379
|
const isMissingErr = err instanceof core_common_1.IModelError &&
|
|
@@ -320,21 +386,24 @@ class IModelExporter {
|
|
|
320
386
|
if (this.visitRelationships) {
|
|
321
387
|
for (const relInstanceId of this._sourceDbChanges.relationship
|
|
322
388
|
.deleteIds) {
|
|
323
|
-
this.handler.onDeleteRelationship(relInstanceId);
|
|
389
|
+
await this.handler.onDeleteRelationship(relInstanceId);
|
|
324
390
|
}
|
|
325
391
|
}
|
|
326
392
|
// Enable consecutive exportChanges runs without the need to re-instantiate the exporter.
|
|
327
393
|
// You can counteract the obvious impact of losing this expensive data by always calling
|
|
328
394
|
// exportChanges with the [[ExportChangesOptions.changedInstanceIds]] option set to
|
|
329
395
|
// whatever you want
|
|
330
|
-
if (this._resetChangeDataOnExport)
|
|
396
|
+
if (this._resetChangeDataOnExport) {
|
|
331
397
|
this._sourceDbChanges = undefined;
|
|
398
|
+
this._elementAspectExportProcessor.setAspectChanges(undefined);
|
|
399
|
+
}
|
|
332
400
|
}
|
|
333
401
|
_resetChangeDataOnExport = true;
|
|
334
|
-
/**
|
|
335
|
-
* @note
|
|
402
|
+
/** Enumerate schemas from the source iModel in database order.
|
|
403
|
+
* @note Override this method to customize schema discovery for both direct exports and transformer schema processing.
|
|
404
|
+
* @beta
|
|
336
405
|
*/
|
|
337
|
-
async
|
|
406
|
+
async *enumerateSchemas() {
|
|
338
407
|
const sql = `
|
|
339
408
|
SELECT s.Name, s.VersionMajor, s.VersionWrite, s.VersionMinor
|
|
340
409
|
FROM ECDbMeta.ECSchemaDef s
|
|
@@ -345,26 +414,35 @@ class IModelExporter {
|
|
|
345
414
|
`}
|
|
346
415
|
ORDER BY ECInstanceId
|
|
347
416
|
`;
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
417
|
+
for await (const row of this.sourceDb.createQueryReader(sql, undefined, {
|
|
418
|
+
usePrimaryConn: true,
|
|
419
|
+
})) {
|
|
420
|
+
const schemaKey = new ecschema_metadata_1.SchemaKey(row[0], new ecschema_metadata_1.ECVersion(row[1], row[2], row[3]));
|
|
421
|
+
const schema = await this.sourceDb.schemaContext.getSchema(schemaKey);
|
|
422
|
+
if (!schema) {
|
|
423
|
+
core_bentley_1.ITwinError.throwError({
|
|
424
|
+
iTwinErrorId: {
|
|
425
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
426
|
+
key: IModelTransformerError_1.IModelTransformerError.SchemaLoadFailed,
|
|
427
|
+
},
|
|
428
|
+
message: `Failed to load schema: ${schemaKey.name}`,
|
|
429
|
+
});
|
|
360
430
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
431
|
+
yield schema;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/** Export schemas from the source iModel.
|
|
435
|
+
* @note This must be called separately from [[exportAll]] or [[exportChanges]].
|
|
436
|
+
*/
|
|
437
|
+
async exportSchemas() {
|
|
438
|
+
const schemasToExport = [];
|
|
439
|
+
for await (const schema of this.enumerateSchemas()) {
|
|
440
|
+
if (await this.handler.shouldExportSchema(schema.schemaKey)) {
|
|
441
|
+
schemasToExport.push(schema);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
await Promise.all(schemasToExport.map(async (schema) => {
|
|
445
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportSchema(${schema.name})`);
|
|
368
446
|
return this.handler.onExportSchema(schema);
|
|
369
447
|
}));
|
|
370
448
|
}
|
|
@@ -378,15 +456,11 @@ class IModelExporter {
|
|
|
378
456
|
async exportCodeSpecs() {
|
|
379
457
|
core_bentley_1.Logger.logTrace(loggerCategory, "exportCodeSpecs()");
|
|
380
458
|
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
|
-
});
|
|
459
|
+
for await (const row of this.sourceDb.createQueryReader(sql, undefined, {
|
|
460
|
+
usePrimaryConn: true,
|
|
461
|
+
})) {
|
|
462
|
+
await this.exportCodeSpecByName(row[0]);
|
|
463
|
+
}
|
|
390
464
|
}
|
|
391
465
|
/** Export a single CodeSpec from the source iModel.
|
|
392
466
|
* @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 +486,9 @@ class IModelExporter {
|
|
|
412
486
|
return;
|
|
413
487
|
}
|
|
414
488
|
// CodeSpec has passed standard exclusion rules, now give handler a chance to accept/reject export
|
|
415
|
-
if (this.handler.shouldExportCodeSpec(codeSpec)) {
|
|
489
|
+
if (await this.handler.shouldExportCodeSpec(codeSpec)) {
|
|
416
490
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportCodeSpec(${codeSpecName})${this.getChangeOpSuffix(isUpdate)}`);
|
|
417
|
-
this.handler.onExportCodeSpec(codeSpec, isUpdate);
|
|
491
|
+
await this.handler.onExportCodeSpec(codeSpec, isUpdate);
|
|
418
492
|
return this.trackProgress();
|
|
419
493
|
}
|
|
420
494
|
}
|
|
@@ -430,41 +504,63 @@ class IModelExporter {
|
|
|
430
504
|
*/
|
|
431
505
|
async exportFonts() {
|
|
432
506
|
core_bentley_1.Logger.logTrace(loggerCategory, "exportFonts()");
|
|
433
|
-
for (const font of this.sourceDb.
|
|
434
|
-
await this.
|
|
507
|
+
for (const font of this.sourceDb.fonts.queryMappedFamilies()) {
|
|
508
|
+
await this.exportFontByFontProps(font);
|
|
435
509
|
}
|
|
436
510
|
}
|
|
437
511
|
/** Export a single font from the source iModel.
|
|
438
|
-
* @note
|
|
512
|
+
* @note multiple fonts can have the same font name, if a font with a specific type is needed use exportFontByFontFamilyDescriptor.
|
|
513
|
+
* If not this will only export the first font with this type in the db.
|
|
439
514
|
*/
|
|
440
515
|
async exportFontByName(fontName) {
|
|
441
516
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByName(${fontName})`);
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
517
|
+
const fontId = this.sourceDb.fonts.findId({
|
|
518
|
+
name: fontName,
|
|
519
|
+
});
|
|
520
|
+
if (undefined !== fontId) {
|
|
521
|
+
await this.exportFontByNumber(fontId);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/** Export a single font from the source iModel.
|
|
525
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
526
|
+
*/
|
|
527
|
+
async exportFontByFontProps(fontProps) {
|
|
528
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByFamily(${fontProps.name}, ${fontProps.type})`);
|
|
529
|
+
await this.handler.onExportFont(fontProps, true);
|
|
530
|
+
return this.trackProgress();
|
|
531
|
+
}
|
|
532
|
+
/** Export a single font from the source iModel.
|
|
533
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
534
|
+
*/
|
|
535
|
+
async exportFontByFontFamilyDescriptor(fontFamily) {
|
|
536
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByFamilyDescriptor(${fontFamily.name}, ${fontFamily.type})`);
|
|
537
|
+
const fontId = await this.sourceDb.fonts.acquireId(fontFamily);
|
|
538
|
+
if (undefined !== fontId) {
|
|
539
|
+
await this.exportFontByFontProps({ ...fontFamily, id: fontId });
|
|
445
540
|
}
|
|
446
541
|
}
|
|
447
542
|
/** Export a single font from the source iModel.
|
|
448
543
|
* @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
544
|
*/
|
|
450
545
|
async exportFontByNumber(fontNumber) {
|
|
451
|
-
/**
|
|
452
|
-
*
|
|
546
|
+
/** ChangedInstanceIds contains EC-mapped changes, but the font table is not EC-mapped.
|
|
547
|
+
* Always export fonts because their changes are therefore absent from sourceDbChanges.
|
|
453
548
|
* 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
549
|
* See https://github.com/iTwin/imodel-transformer/pull/135 for removed code.
|
|
455
550
|
*/
|
|
456
|
-
const isUpdate = true;
|
|
457
551
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontById(${fontNumber})`);
|
|
458
|
-
const font = this.sourceDb.
|
|
552
|
+
const font = this.sourceDb.fonts.findDescriptor(fontNumber);
|
|
459
553
|
if (undefined !== font) {
|
|
460
|
-
this.
|
|
461
|
-
return this.trackProgress();
|
|
554
|
+
await this.exportFontByFontFamilyDescriptor(font);
|
|
462
555
|
}
|
|
463
556
|
}
|
|
464
557
|
/** Export the model container, contents, and sub-models from the source iModel.
|
|
465
558
|
* @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
559
|
*/
|
|
467
560
|
async exportModel(modeledElementId) {
|
|
561
|
+
return this.runScopedElementExport(async () => this.exportModelImpl(modeledElementId));
|
|
562
|
+
}
|
|
563
|
+
async exportModelImpl(modeledElementId) {
|
|
468
564
|
const model = this.sourceDb.models.getModel(modeledElementId);
|
|
469
565
|
if (model.isTemplate && !this.wantTemplateModels) {
|
|
470
566
|
return;
|
|
@@ -475,7 +571,7 @@ class IModelExporter {
|
|
|
475
571
|
wantBRepData: this.wantGeometry,
|
|
476
572
|
});
|
|
477
573
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportModel(${modeledElementId})`);
|
|
478
|
-
if (this.shouldExportElement(modeledElement)) {
|
|
574
|
+
if (await this.shouldExportElement(modeledElement)) {
|
|
479
575
|
await this.exportModelContainer(model);
|
|
480
576
|
if (this.visitElements) {
|
|
481
577
|
await this.exportModelContents(modeledElementId);
|
|
@@ -498,7 +594,7 @@ class IModelExporter {
|
|
|
498
594
|
return; // not in changeset, don't export
|
|
499
595
|
}
|
|
500
596
|
}
|
|
501
|
-
this.handler.onExportModel(model, isUpdate);
|
|
597
|
+
await this.handler.onExportModel(model, isUpdate);
|
|
502
598
|
return this.trackProgress();
|
|
503
599
|
}
|
|
504
600
|
_yieldManager = new core_bentley_1.YieldManager();
|
|
@@ -509,6 +605,9 @@ class IModelExporter {
|
|
|
509
605
|
* @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
606
|
*/
|
|
511
607
|
async exportModelContents(modelId, elementClassFullName = core_backend_1.Element.classFullName, skipRootSubject) {
|
|
608
|
+
return this.runScopedElementExport(async () => this.exportModelContentsImpl(modelId, elementClassFullName, skipRootSubject));
|
|
609
|
+
}
|
|
610
|
+
async exportModelContentsImpl(modelId, elementClassFullName, skipRootSubject) {
|
|
512
611
|
if (skipRootSubject) {
|
|
513
612
|
// 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
613
|
// NOTE: IModelExporter.exportAll should not skip the root Subject since the goal is to export everything
|
|
@@ -533,44 +632,41 @@ class IModelExporter {
|
|
|
533
632
|
else {
|
|
534
633
|
sql = `SELECT ECInstanceId FROM ${elementClassFullName} WHERE Parent.Id IS NULL AND Model.Id=:modelId ORDER BY ECInstanceId`;
|
|
535
634
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
await this._yieldManager.allowYield();
|
|
547
|
-
}
|
|
548
|
-
});
|
|
635
|
+
const params = new core_common_1.QueryBinder().bindId("modelId", modelId);
|
|
636
|
+
if (skipRootSubject) {
|
|
637
|
+
params.bindId("rootSubjectId", core_common_1.IModel.rootSubjectId);
|
|
638
|
+
}
|
|
639
|
+
for await (const row of this.sourceDb.createQueryReader(sql, params, {
|
|
640
|
+
usePrimaryConn: true,
|
|
641
|
+
})) {
|
|
642
|
+
await this.exportElement(row.id);
|
|
643
|
+
await this._yieldManager.allowYield();
|
|
644
|
+
}
|
|
549
645
|
}
|
|
550
646
|
/** Export the sub-models directly below the specified model.
|
|
551
647
|
* @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
648
|
*/
|
|
553
649
|
async exportSubModels(parentModelId) {
|
|
650
|
+
return this.runScopedElementExport(async () => this.exportSubModelsImpl(parentModelId));
|
|
651
|
+
}
|
|
652
|
+
async exportSubModelsImpl(parentModelId) {
|
|
554
653
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportSubModels(${parentModelId})`);
|
|
555
654
|
const definitionModelIds = [];
|
|
556
655
|
const otherModelIds = [];
|
|
557
656
|
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
|
-
}
|
|
657
|
+
const params = new core_common_1.QueryBinder().bindId("parentModelId", parentModelId);
|
|
658
|
+
for await (const row of this.sourceDb.createQueryReader(sql, params, {
|
|
659
|
+
usePrimaryConn: true,
|
|
660
|
+
})) {
|
|
661
|
+
const modelId = row.id;
|
|
662
|
+
const model = this.sourceDb.models.getModel(modelId);
|
|
663
|
+
if (model instanceof core_backend_1.DefinitionModel) {
|
|
664
|
+
definitionModelIds.push(modelId);
|
|
572
665
|
}
|
|
573
|
-
|
|
666
|
+
else {
|
|
667
|
+
otherModelIds.push(modelId);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
574
670
|
// export DefinitionModels before other types of Models
|
|
575
671
|
for (const definitionModelId of definitionModelIds) {
|
|
576
672
|
await this.exportModel(definitionModelId);
|
|
@@ -583,16 +679,15 @@ class IModelExporter {
|
|
|
583
679
|
* This considers the standard IModelExporter exclusion rules plus calls [IModelExportHandler.shouldExportElement]($transformer) for any custom exclusion rules.
|
|
584
680
|
* @note This method is called from within [[exportChanges]] and [[exportAll]], so usually does not need to be called directly.
|
|
585
681
|
*/
|
|
586
|
-
shouldExportElement(element) {
|
|
682
|
+
async shouldExportElement(element) {
|
|
587
683
|
if (this._excludedElementIds.has(element.id)) {
|
|
588
684
|
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Id`);
|
|
589
685
|
return false;
|
|
590
686
|
}
|
|
591
|
-
if (element instanceof core_backend_1.GeometricElement
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
687
|
+
if (element instanceof core_backend_1.GeometricElement &&
|
|
688
|
+
this._excludedElementCategoryIds.has(element.category)) {
|
|
689
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Category`);
|
|
690
|
+
return false;
|
|
596
691
|
}
|
|
597
692
|
if (!this.wantTemplateModels &&
|
|
598
693
|
element instanceof core_backend_1.RecipeDefinitionElement) {
|
|
@@ -606,12 +701,19 @@ class IModelExporter {
|
|
|
606
701
|
}
|
|
607
702
|
}
|
|
608
703
|
// element has passed standard exclusion rules, now give handler a chance to accept/reject
|
|
609
|
-
|
|
704
|
+
if (!(await this.handler.shouldExportElement(element))) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
return true;
|
|
610
708
|
}
|
|
611
|
-
/** Export the specified element
|
|
709
|
+
/** Export the specified element and its child elements (if applicable).
|
|
612
710
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
711
|
+
* @note ElementAspects are exported separately in owner-scoped groups, so their callbacks are not guaranteed to follow this element callback.
|
|
613
712
|
*/
|
|
614
713
|
async exportElement(elementId) {
|
|
714
|
+
return this.runScopedElementExport(async () => this.exportElementImpl(elementId));
|
|
715
|
+
}
|
|
716
|
+
async exportElementImpl(elementId) {
|
|
615
717
|
if (!this.visitElements) {
|
|
616
718
|
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportElement(${elementId})`);
|
|
617
719
|
return;
|
|
@@ -619,7 +721,7 @@ class IModelExporter {
|
|
|
619
721
|
// Return early if the elementId is already in the excludedElementIds, that way we don't need to load the element from the db.
|
|
620
722
|
if (this._excludedElementIds.has(elementId)) {
|
|
621
723
|
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementId} by Id`);
|
|
622
|
-
this.handler.onSkipElement(elementId);
|
|
724
|
+
await this.handler.onSkipElement(elementId);
|
|
623
725
|
return;
|
|
624
726
|
}
|
|
625
727
|
// are we processing changes?
|
|
@@ -628,6 +730,10 @@ class IModelExporter {
|
|
|
628
730
|
: this._sourceDbChanges?.element.updateIds.has(elementId)
|
|
629
731
|
? true
|
|
630
732
|
: undefined;
|
|
733
|
+
// Short-circuit: element is not in the changeset, skip its own export but still visit children
|
|
734
|
+
if (undefined !== this._sourceDbChanges && undefined === isUpdate) {
|
|
735
|
+
return this.exportChildElements(elementId);
|
|
736
|
+
}
|
|
631
737
|
const element = this.sourceDb.elements.getElement({
|
|
632
738
|
id: elementId,
|
|
633
739
|
wantGeometry: this.wantGeometry,
|
|
@@ -635,21 +741,24 @@ class IModelExporter {
|
|
|
635
741
|
});
|
|
636
742
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportElement(${element.id}, "${element.getDisplayLabel()}")${this.getChangeOpSuffix(isUpdate)}`);
|
|
637
743
|
// 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)) {
|
|
744
|
+
if (await this.shouldExportElement(element)) {
|
|
639
745
|
await this.handler.preExportElement(element);
|
|
640
|
-
this.handler.onExportElement(element, isUpdate);
|
|
746
|
+
await this.handler.onExportElement(element, isUpdate);
|
|
641
747
|
await this.trackProgress();
|
|
642
|
-
await this.
|
|
748
|
+
await this._elementAspectExportCoordinator.addAcceptedOwner(elementId);
|
|
643
749
|
return this.exportChildElements(elementId);
|
|
644
750
|
}
|
|
645
751
|
else {
|
|
646
|
-
this.handler.onSkipElement(element.id);
|
|
752
|
+
await this.handler.onSkipElement(element.id);
|
|
647
753
|
}
|
|
648
754
|
}
|
|
649
755
|
/** Export the child elements of the specified element from the source iModel.
|
|
650
756
|
* @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
757
|
*/
|
|
652
758
|
async exportChildElements(elementId) {
|
|
759
|
+
return this.runScopedElementExport(async () => this.exportChildElementsImpl(elementId));
|
|
760
|
+
}
|
|
761
|
+
async exportChildElementsImpl(elementId) {
|
|
653
762
|
if (!this.visitElements) {
|
|
654
763
|
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportChildElements(${elementId})`);
|
|
655
764
|
return;
|
|
@@ -662,13 +771,216 @@ class IModelExporter {
|
|
|
662
771
|
}
|
|
663
772
|
}
|
|
664
773
|
}
|
|
665
|
-
/** Exports all aspects
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
774
|
+
/** Exports all aspects owned by the supplied elements. */
|
|
775
|
+
async exportAspectsForOwners(ownerElementIds) {
|
|
776
|
+
return this._elementAspectExportCoordinator.exportOwners(ownerElementIds);
|
|
777
|
+
}
|
|
778
|
+
async filterOwnerElementIdsForAspectExport(ownerElementIds) {
|
|
779
|
+
if (ownerElementIds.size === 0)
|
|
780
|
+
return new Set();
|
|
781
|
+
const elementFacts = await this.queryAspectElementHierarchyFacts(ownerElementIds);
|
|
782
|
+
const modelFacts = await this.queryAspectModelHierarchyFacts(new Set([...elementFacts.values()].map((elementFact) => elementFact.modelId)));
|
|
783
|
+
await this.queryAspectElementFacts(new Set(modelFacts.keys()), elementFacts);
|
|
784
|
+
const elementIds = new Set();
|
|
785
|
+
for (const elementId of ownerElementIds) {
|
|
786
|
+
if (this._skipPropagateChangesToRootElements &&
|
|
787
|
+
this._rootElementIds.has(elementId)) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (await this.shouldExportElementHierarchyForAspect(elementId, elementFacts, modelFacts)) {
|
|
791
|
+
elementIds.add(elementId);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return elementIds;
|
|
795
|
+
}
|
|
796
|
+
async shouldExportElementHierarchyForAspect(elementId, elementFacts, modelFacts) {
|
|
797
|
+
let elementFact = elementFacts.get(elementId);
|
|
798
|
+
while (elementFact !== undefined) {
|
|
799
|
+
if (!(await this.shouldExportElementForAspect(elementFact.id, elementFact)))
|
|
800
|
+
return false;
|
|
801
|
+
if (!(await this.shouldExportModelForAspect(elementFact.modelId, elementFacts, modelFacts))) {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
const parentId = elementFact.parentId;
|
|
805
|
+
if (parentId === undefined ||
|
|
806
|
+
(this._skipPropagateChangesToRootElements &&
|
|
807
|
+
parentId === core_common_1.IModel.rootSubjectId)) {
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
elementFact = elementFacts.get(parentId);
|
|
811
|
+
}
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
async shouldExportModelForAspect(modelId, elementFacts, modelFacts) {
|
|
815
|
+
const modelFact = modelFacts.get(modelId);
|
|
816
|
+
if (modelFact === undefined) {
|
|
817
|
+
this.sourceDb.models.getModel(modelId);
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
if (modelFact.isTemplate && !this.wantTemplateModels)
|
|
821
|
+
return false;
|
|
822
|
+
if (modelFact.id !== core_common_1.IModel.repositoryModelId &&
|
|
823
|
+
modelFact.id !== core_common_1.IModel.dictionaryId &&
|
|
824
|
+
modelFact.id !== "0xe" &&
|
|
825
|
+
!(await this.shouldExportElementForAspect(modelFact.id, elementFacts.get(modelFact.id)))) {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
if (modelFact.parentModelId === undefined ||
|
|
829
|
+
modelFact.parentModelId === modelFact.id) {
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
return this.shouldExportModelForAspect(modelFact.parentModelId, elementFacts, modelFacts);
|
|
833
|
+
}
|
|
834
|
+
async runScopedElementExport(exportElements) {
|
|
835
|
+
await this._elementAspectExportCoordinator.run(exportElements);
|
|
836
|
+
}
|
|
837
|
+
/** Apply the element export filter when deciding whether to process an aspect owner. @internal */
|
|
838
|
+
async shouldExportElementForAspect(elementId, elementFact) {
|
|
839
|
+
if (!this.visitElements)
|
|
840
|
+
return false;
|
|
841
|
+
if (this.shouldExportElement ===
|
|
842
|
+
IModelExporter.prototype.shouldExportElement &&
|
|
843
|
+
elementFact !== undefined &&
|
|
844
|
+
!this.shouldExportElementFact(elementFact)) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
const element = this.sourceDb.elements.getElement({
|
|
848
|
+
id: elementId,
|
|
849
|
+
wantGeometry: false,
|
|
850
|
+
wantBRepData: false,
|
|
851
|
+
});
|
|
852
|
+
return this.shouldExportElement(element);
|
|
853
|
+
}
|
|
854
|
+
shouldExportElementFact(elementFact) {
|
|
855
|
+
if (this._excludedElementIds.has(elementFact.id)) {
|
|
856
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by Id`);
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
const elementClass = this.sourceDb.getJsClass(elementFact.classFullName);
|
|
860
|
+
if ((elementFact.classFullName === core_backend_1.GeometricElement.classFullName ||
|
|
861
|
+
elementClass.prototype instanceof core_backend_1.GeometricElement) &&
|
|
862
|
+
elementFact.categoryId !== undefined &&
|
|
863
|
+
this._excludedElementCategoryIds.has(elementFact.categoryId)) {
|
|
864
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by Category`);
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
if (!this.wantTemplateModels &&
|
|
868
|
+
(elementFact.classFullName === core_backend_1.RecipeDefinitionElement.classFullName ||
|
|
869
|
+
elementClass.prototype instanceof core_backend_1.RecipeDefinitionElement)) {
|
|
870
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded RecipeDefinitionElement ${elementFact.id} because wantTemplate=false`);
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
for (const excludedElementClass of this._excludedElementClasses) {
|
|
874
|
+
if (elementFact.classFullName === excludedElementClass.classFullName ||
|
|
875
|
+
elementClass.prototype instanceof excludedElementClass) {
|
|
876
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by class: ${excludedElementClass.classFullName}`);
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return true;
|
|
881
|
+
}
|
|
882
|
+
async queryAspectElementHierarchyFacts(elementIds) {
|
|
883
|
+
const elementFacts = new Map();
|
|
884
|
+
const queriedElementIds = new Set();
|
|
885
|
+
let pendingElementIds = new Set(elementIds);
|
|
886
|
+
while (pendingElementIds.size > 0) {
|
|
887
|
+
const currentElementIds = pendingElementIds;
|
|
888
|
+
pendingElementIds = new Set();
|
|
889
|
+
await this.queryAspectElementFacts(currentElementIds, elementFacts);
|
|
890
|
+
for (const elementId of currentElementIds)
|
|
891
|
+
queriedElementIds.add(elementId);
|
|
892
|
+
for (const elementId of currentElementIds) {
|
|
893
|
+
const parentId = elementFacts.get(elementId)?.parentId;
|
|
894
|
+
if (parentId !== undefined &&
|
|
895
|
+
!queriedElementIds.has(parentId) &&
|
|
896
|
+
!elementFacts.has(parentId)) {
|
|
897
|
+
pendingElementIds.add(parentId);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return elementFacts;
|
|
902
|
+
}
|
|
903
|
+
async queryAspectElementFacts(elementIds, elementFacts) {
|
|
904
|
+
const unqueriedElementIds = new Set([...elementIds].filter((elementId) => !elementFacts.has(elementId)));
|
|
905
|
+
if (unqueriedElementIds.size === 0)
|
|
906
|
+
return;
|
|
907
|
+
for (const elementIdBatch of this.getAspectOwnerIdBatches(unqueriedElementIds)) {
|
|
908
|
+
const queryParams = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIdBatch);
|
|
909
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT e.ECInstanceId id, e.Parent.Id parentId, e.Model.Id modelId,
|
|
910
|
+
ec_className(e.ECClassId, 's') schemaName,
|
|
911
|
+
ec_className(e.ECClassId, 'c') className
|
|
912
|
+
FROM bis.Element e
|
|
913
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = e.ECInstanceId
|
|
914
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, queryParams, { usePrimaryConn: true })) {
|
|
915
|
+
elementFacts.set(row.id, {
|
|
916
|
+
id: row.id,
|
|
917
|
+
parentId: row.parentId ?? undefined,
|
|
918
|
+
modelId: row.modelId,
|
|
919
|
+
classFullName: `${row.schemaName}:${row.className}`,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
if (this._excludedElementCategoryIds.size > 0) {
|
|
923
|
+
const categoryQueryParams = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIdBatch);
|
|
924
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT g.ECInstanceId id, g.Category.Id categoryId
|
|
925
|
+
FROM bis.GeometricElement g
|
|
926
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = g.ECInstanceId
|
|
927
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, categoryQueryParams, { usePrimaryConn: true })) {
|
|
928
|
+
const elementFact = elementFacts.get(row.id);
|
|
929
|
+
if (elementFact !== undefined) {
|
|
930
|
+
elementFact.categoryId = row.categoryId;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
async queryAspectModelHierarchyFacts(modelIds) {
|
|
937
|
+
const modelFacts = new Map();
|
|
938
|
+
const queriedModelIds = new Set();
|
|
939
|
+
let pendingModelIds = new Set(modelIds);
|
|
940
|
+
while (pendingModelIds.size > 0) {
|
|
941
|
+
const currentModelIds = pendingModelIds;
|
|
942
|
+
pendingModelIds = new Set();
|
|
943
|
+
for (const modelIdBatch of this.getAspectOwnerIdBatches(currentModelIds)) {
|
|
944
|
+
const queryParams = new core_common_1.QueryBinder().bindIdSet("modelIds", modelIdBatch);
|
|
945
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT model.ECInstanceId id, model.ParentModel.Id parentModelId, model.IsTemplate isTemplate
|
|
946
|
+
FROM bis.Model model
|
|
947
|
+
INNER JOIN IdSet(:modelIds) ids ON ids.id = model.ECInstanceId
|
|
948
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, queryParams, { usePrimaryConn: true })) {
|
|
949
|
+
modelFacts.set(row.id, {
|
|
950
|
+
id: row.id,
|
|
951
|
+
parentModelId: row.parentModelId ?? undefined,
|
|
952
|
+
isTemplate: row.isTemplate === true || row.isTemplate === 1,
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
for (const modelId of currentModelIds)
|
|
957
|
+
queriedModelIds.add(modelId);
|
|
958
|
+
for (const modelId of currentModelIds) {
|
|
959
|
+
const parentModelId = modelFacts.get(modelId)?.parentModelId;
|
|
960
|
+
if (parentModelId !== undefined &&
|
|
961
|
+
!queriedModelIds.has(parentModelId) &&
|
|
962
|
+
!modelFacts.has(parentModelId)) {
|
|
963
|
+
pendingModelIds.add(parentModelId);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return modelFacts;
|
|
968
|
+
}
|
|
969
|
+
*getAspectOwnerIdBatches(ids) {
|
|
970
|
+
let batch = new Set();
|
|
971
|
+
for (const id of ids) {
|
|
972
|
+
batch.add(id);
|
|
973
|
+
if (batch.size === IModelExporter._elementAspectOwnerBatchSize) {
|
|
974
|
+
yield batch;
|
|
975
|
+
batch = new Set();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (batch.size > 0)
|
|
979
|
+
yield batch;
|
|
669
980
|
}
|
|
670
981
|
/** Exports all relationships that subclass from the specified base class.
|
|
671
982
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
983
|
+
* @note This is the bulk traversal path. It hydrates relationships with one streaming query and calls [[exportRelationshipInstance]] directly; it does not call [[exportRelationship]] once per row.
|
|
672
984
|
*/
|
|
673
985
|
async exportRelationships(baseRelClassFullName) {
|
|
674
986
|
if (!this.visitRelationships) {
|
|
@@ -676,46 +988,154 @@ class IModelExporter {
|
|
|
676
988
|
return;
|
|
677
989
|
}
|
|
678
990
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationships(${baseRelClassFullName})`);
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
991
|
+
const changedRelationshipIds = this._sourceDbChanges === undefined
|
|
992
|
+
? undefined
|
|
993
|
+
: new Set([
|
|
994
|
+
...this._sourceDbChanges.relationship.insertIds,
|
|
995
|
+
...this._sourceDbChanges.relationship.updateIds,
|
|
996
|
+
]);
|
|
997
|
+
if (changedRelationshipIds?.size === 0) {
|
|
998
|
+
// Deleted relationships are handled by exportChanges after this traversal; they are no
|
|
999
|
+
// longer present in the source iModel and must not cause a full relationship scan here.
|
|
1000
|
+
core_bentley_1.Logger.logTrace(loggerCategory, "No inserted or updated relationships, skipping exportRelationships()");
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const changedRelationshipJoin = changedRelationshipIds === undefined
|
|
1004
|
+
? undefined
|
|
1005
|
+
: "INNER JOIN IdSet(:changedRelationshipIds) changed ON changed.id = r.ECInstanceId";
|
|
1006
|
+
const relationshipWhereClause = "s.ECInstanceId IS NOT NULL AND t.ECInstanceId IS NOT NULL";
|
|
1007
|
+
// Bound the async reader's retained raw rows while preserving batched query throughput.
|
|
1008
|
+
// Keyset pagination avoids rescanning rows returned by earlier batches.
|
|
1009
|
+
const relationshipQueryBatchSize = 1000;
|
|
1010
|
+
let lastRelationshipId;
|
|
1011
|
+
while (true) {
|
|
1012
|
+
let rowsRead = 0;
|
|
1013
|
+
const whereClause = lastRelationshipId === undefined
|
|
1014
|
+
? relationshipWhereClause
|
|
1015
|
+
: `${relationshipWhereClause} AND r.ECInstanceId > :lastRelationshipId`;
|
|
1016
|
+
// QueryBinder parameters are immutable after binding a name. Recreate the binder for each
|
|
1017
|
+
// keyset page so the changing boundary can be bound without redefining a property.
|
|
1018
|
+
const queryParams = changedRelationshipIds === undefined && lastRelationshipId === undefined
|
|
1019
|
+
? undefined
|
|
1020
|
+
: new core_common_1.QueryBinder();
|
|
1021
|
+
if (queryParams !== undefined) {
|
|
1022
|
+
if (changedRelationshipIds !== undefined) {
|
|
1023
|
+
queryParams.bindIdSet("changedRelationshipIds", changedRelationshipIds);
|
|
1024
|
+
}
|
|
1025
|
+
if (lastRelationshipId !== undefined) {
|
|
1026
|
+
queryParams.bindId("lastRelationshipId", lastRelationshipId);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
const reader = this.sourceDb.createQueryReader(this.getRelationshipQuery(baseRelClassFullName, "JOIN", whereClause, changedRelationshipJoin), queryParams, {
|
|
1030
|
+
usePrimaryConn: true,
|
|
1031
|
+
limit: { count: relationshipQueryBatchSize },
|
|
1032
|
+
});
|
|
1033
|
+
for await (const row of reader) {
|
|
1034
|
+
rowsRead++;
|
|
1035
|
+
const relInstanceId = row.relInstanceId;
|
|
1036
|
+
lastRelationshipId = relInstanceId;
|
|
1037
|
+
const changesetFilter = this.checkRelationshipChangesetFilter(relInstanceId);
|
|
1038
|
+
if (changesetFilter.skip) {
|
|
1039
|
+
await this._yieldManager.allowYield();
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
await this.exportHydratedRelationship(row.rawInstance, row.sourceFedGuid, row.targetFedGuid, changesetFilter.isUpdate);
|
|
693
1043
|
await this._yieldManager.allowYield();
|
|
694
1044
|
}
|
|
695
|
-
|
|
1045
|
+
if (rowsRead < relationshipQueryBatchSize)
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/** Builds the raw relationship query used by the bulk and targeted export paths. */
|
|
1050
|
+
getRelationshipQuery(relClassFullName, endpointJoin, whereClause, relationshipJoin) {
|
|
1051
|
+
return `SELECT r.$ AS rawInstance, r.ECInstanceId AS relInstanceId, s.FederationGuid AS sourceFedGuid, t.FederationGuid AS targetFedGuid FROM ${relClassFullName} r
|
|
1052
|
+
${relationshipJoin ?? ""}
|
|
1053
|
+
${endpointJoin} bis.Element s ON s.ECInstanceId = r.SourceECInstanceId
|
|
1054
|
+
${endpointJoin} bis.Element t ON t.ECInstanceId = r.TargetECInstanceId
|
|
1055
|
+
WHERE ${whereClause}
|
|
1056
|
+
ORDER BY r.ECInstanceId
|
|
1057
|
+
OPTIONS USE_JS_PROP_NAMES DO_NOT_TRUNCATE_BLOB`;
|
|
1058
|
+
}
|
|
1059
|
+
/** Applies the changeset filter (when exporting changes) to a relationship instance id.
|
|
1060
|
+
* @returns whether to skip the relationship, and whether the export is an update.
|
|
1061
|
+
*/
|
|
1062
|
+
checkRelationshipChangesetFilter(relInstanceId) {
|
|
1063
|
+
if (undefined === this._sourceDbChanges)
|
|
1064
|
+
return { skip: false, isUpdate: undefined };
|
|
1065
|
+
if (this._sourceDbChanges.relationship.insertIds.has(relInstanceId))
|
|
1066
|
+
return { skip: false, isUpdate: false };
|
|
1067
|
+
if (this._sourceDbChanges.relationship.updateIds.has(relInstanceId))
|
|
1068
|
+
return { skip: false, isUpdate: true };
|
|
1069
|
+
return { skip: true, isUpdate: undefined }; // not in changeset, don't export
|
|
1070
|
+
}
|
|
1071
|
+
/** Converts a raw `$` instance-access row into [RelationshipProps]($common). */
|
|
1072
|
+
getRelationshipProps(rawInstance) {
|
|
1073
|
+
const parsedRow = typeof rawInstance === "string"
|
|
1074
|
+
? JSON.parse(rawInstance, core_common_1.Base64EncodedString.reviver)
|
|
1075
|
+
: rawInstance;
|
|
1076
|
+
if (!core_bentley_1.JsonUtils.isObject(parsedRow))
|
|
1077
|
+
core_bentley_1.ITwinError.throwError({
|
|
1078
|
+
iTwinErrorId: {
|
|
1079
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1080
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidRelationshipData,
|
|
1081
|
+
},
|
|
1082
|
+
message: "Expected a Relationship instance query to return an object",
|
|
1083
|
+
});
|
|
1084
|
+
const row = {};
|
|
1085
|
+
for (const [key, value] of Object.entries(parsedRow)) {
|
|
1086
|
+
const ecPropertyName = key.length === 0 ? key : key[0].toUpperCase() + key.substring(1);
|
|
1087
|
+
row[core_common_1.ECJsNames.toJsName(ecPropertyName)] = value;
|
|
1088
|
+
}
|
|
1089
|
+
const className = row.className;
|
|
1090
|
+
if (typeof className !== "string")
|
|
1091
|
+
core_bentley_1.ITwinError.throwError({
|
|
1092
|
+
iTwinErrorId: {
|
|
1093
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1094
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidRelationshipData,
|
|
1095
|
+
},
|
|
1096
|
+
message: "Expected a Relationship instance query to return a className",
|
|
1097
|
+
});
|
|
1098
|
+
row.classFullName = className.replace(".", ":");
|
|
1099
|
+
delete row.className;
|
|
1100
|
+
delete row.sourceClassName;
|
|
1101
|
+
delete row.targetClassName;
|
|
1102
|
+
return row;
|
|
696
1103
|
}
|
|
697
|
-
/** Export
|
|
1104
|
+
/** Export one specified relationship from the source iModel.
|
|
1105
|
+
* @note This targeted path is separate from [[exportRelationships]], which traverses a class hierarchy. It uses the same raw instance query and passes endpoint FederationGuids to the handler.
|
|
1106
|
+
*/
|
|
698
1107
|
async exportRelationship(relClassFullName, relInstanceId) {
|
|
699
1108
|
if (!this.visitRelationships) {
|
|
700
1109
|
core_bentley_1.Logger.logTrace(loggerCategory, `visitRelationships=false, skipping exportRelationship(${relClassFullName}, ${relInstanceId})`);
|
|
701
1110
|
return;
|
|
702
1111
|
}
|
|
703
|
-
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
else {
|
|
713
|
-
return; // not in changeset, don't export
|
|
714
|
-
}
|
|
1112
|
+
const changesetFilter = this.checkRelationshipChangesetFilter(relInstanceId);
|
|
1113
|
+
if (changesetFilter.skip)
|
|
1114
|
+
return;
|
|
1115
|
+
const queryParams = new core_common_1.QueryBinder().bindId("relInstanceId", relInstanceId);
|
|
1116
|
+
const reader = this.sourceDb.createQueryReader(this.getRelationshipQuery(relClassFullName, "LEFT JOIN", "r.ECInstanceId = :relInstanceId"), queryParams, { usePrimaryConn: true });
|
|
1117
|
+
if (await reader.step()) {
|
|
1118
|
+
const row = reader.current;
|
|
1119
|
+
await this.exportHydratedRelationship(row.rawInstance, row.sourceFedGuid, row.targetFedGuid, changesetFilter.isUpdate);
|
|
1120
|
+
return;
|
|
715
1121
|
}
|
|
716
|
-
//
|
|
717
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationship(${relClassFullName}, ${relInstanceId})`);
|
|
1122
|
+
// Preserve the previous not-found behavior if the targeted query returned no row.
|
|
718
1123
|
const relationship = this.sourceDb.relationships.getInstance(relClassFullName, relInstanceId);
|
|
1124
|
+
await this.exportRelationshipInstance(relationship, changesetFilter.isUpdate, this.sourceDb.elements.getFederationGuidFromId(relationship.sourceId), this.sourceDb.elements.getFederationGuidFromId(relationship.targetId));
|
|
1125
|
+
}
|
|
1126
|
+
/** Hydrates a relationship query row and runs the common export pipeline. */
|
|
1127
|
+
async exportHydratedRelationship(rawInstance, sourceFedGuid, targetFedGuid, isUpdate) {
|
|
1128
|
+
const relationship = this.sourceDb.constructEntity(this.getRelationshipProps(rawInstance));
|
|
1129
|
+
await this.exportRelationshipInstance(relationship, isUpdate, sourceFedGuid ?? undefined, targetFedGuid ?? undefined);
|
|
1130
|
+
}
|
|
1131
|
+
/** Applies exclusion rules and handler callbacks to an already-hydrated relationship instance.
|
|
1132
|
+
* @param sourceFedGuid The FederationGuid of the relationship's source endpoint, if available.
|
|
1133
|
+
* @param targetFedGuid The FederationGuid of the relationship's target endpoint, if available.
|
|
1134
|
+
* @note This is the common protected hook used by both [[exportRelationship]] and [[exportRelationships]]. Most consumers should customize [[IModelExportHandler]] instead.
|
|
1135
|
+
*/
|
|
1136
|
+
async exportRelationshipInstance(relationship, isUpdate, sourceFedGuid, targetFedGuid) {
|
|
1137
|
+
// passed changeset test, now apply standard exclusion rules
|
|
1138
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationship(${relationship.classFullName}, ${relationship.id})`);
|
|
719
1139
|
for (const excludedRelationshipClass of this._excludedRelationshipClasses) {
|
|
720
1140
|
if (relationship instanceof excludedRelationshipClass) {
|
|
721
1141
|
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded relationship by class: ${excludedRelationshipClass.classFullName}`);
|
|
@@ -723,8 +1143,8 @@ class IModelExporter {
|
|
|
723
1143
|
}
|
|
724
1144
|
}
|
|
725
1145
|
// 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);
|
|
1146
|
+
if (await this.handler.shouldExportRelationship(relationship)) {
|
|
1147
|
+
await this.handler.onExportRelationship(relationship, isUpdate, sourceFedGuid, targetFedGuid);
|
|
728
1148
|
await this.trackProgress();
|
|
729
1149
|
}
|
|
730
1150
|
}
|
|
@@ -783,6 +1203,13 @@ class ChangedInstanceIds {
|
|
|
783
1203
|
_aspectSubclassIds;
|
|
784
1204
|
_relationshipSubclassIds;
|
|
785
1205
|
_relationshipSubclassIdsToSkip;
|
|
1206
|
+
_aspectOwnerElementIds = new Set();
|
|
1207
|
+
/** Element IDs that own the aspects represented by `aspect` changes.
|
|
1208
|
+
* @internal
|
|
1209
|
+
*/
|
|
1210
|
+
get aspectOwnerElementIds() {
|
|
1211
|
+
return this._aspectOwnerElementIds;
|
|
1212
|
+
}
|
|
786
1213
|
_db;
|
|
787
1214
|
constructor(db) {
|
|
788
1215
|
this._db = db;
|
|
@@ -795,7 +1222,7 @@ class ChangedInstanceIds {
|
|
|
795
1222
|
this._relationshipSubclassIds = new Set();
|
|
796
1223
|
this._relationshipSubclassIdsToSkip = new Set();
|
|
797
1224
|
const addECClassIdsToSet = async (setToModify, baseClass) => {
|
|
798
|
-
for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})
|
|
1225
|
+
for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})`, undefined, { usePrimaryConn: true })) {
|
|
799
1226
|
setToModify.add(row.ECInstanceId);
|
|
800
1227
|
}
|
|
801
1228
|
};
|
|
@@ -845,33 +1272,63 @@ class ChangedInstanceIds {
|
|
|
845
1272
|
!this.font.isEmpty);
|
|
846
1273
|
}
|
|
847
1274
|
/**
|
|
848
|
-
* Adds the provided [[
|
|
1275
|
+
* 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
1276
|
* 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
1277
|
* from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
|
|
851
|
-
* @param change
|
|
1278
|
+
* @param change Changed EC instance with the ID, operation, and EC class ID of the changed entity.
|
|
852
1279
|
*/
|
|
853
1280
|
async addChange(change) {
|
|
854
1281
|
if (!this._ecClassIdsInitialized)
|
|
855
1282
|
await this.setupECClassIds();
|
|
856
|
-
const ecClassId = change.ECClassId
|
|
1283
|
+
const ecClassId = change.ECClassId;
|
|
857
1284
|
if (ecClassId === undefined)
|
|
858
|
-
|
|
859
|
-
|
|
1285
|
+
core_bentley_1.ITwinError.throwError({
|
|
1286
|
+
iTwinErrorId: {
|
|
1287
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1288
|
+
key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
|
|
1289
|
+
},
|
|
1290
|
+
message: `ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change.$meta.tables}`,
|
|
1291
|
+
});
|
|
1292
|
+
const changeType = change.$meta.op;
|
|
860
1293
|
if (changeType === undefined)
|
|
861
|
-
|
|
1294
|
+
core_bentley_1.ITwinError.throwError({
|
|
1295
|
+
iTwinErrorId: {
|
|
1296
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1297
|
+
key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
|
|
1298
|
+
},
|
|
1299
|
+
message: `ChangeType was undefined for id: ${change.ECInstanceId}.`,
|
|
1300
|
+
});
|
|
862
1301
|
if (this._relationshipSubclassIdsToSkip?.has(ecClassId))
|
|
863
1302
|
return;
|
|
864
1303
|
if (this.isRelationship(ecClassId))
|
|
865
1304
|
this.handleChange(this.relationship, changeType, change.ECInstanceId);
|
|
866
1305
|
else if (this.isCodeSpec(ecClassId))
|
|
867
1306
|
this.handleChange(this.codeSpec, changeType, change.ECInstanceId);
|
|
868
|
-
else if (this.isAspect(ecClassId))
|
|
1307
|
+
else if (this.isAspect(ecClassId)) {
|
|
1308
|
+
const ownerElementId = change.Element?.Id ??
|
|
1309
|
+
this.tryGetAspectOwnerElementId(change.ECInstanceId);
|
|
1310
|
+
if (ownerElementId !== undefined) {
|
|
1311
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1312
|
+
}
|
|
869
1313
|
this.handleChange(this.aspect, changeType, change.ECInstanceId);
|
|
1314
|
+
}
|
|
870
1315
|
else if (this.isModel(ecClassId))
|
|
871
1316
|
this.handleChange(this.model, changeType, change.ECInstanceId);
|
|
872
1317
|
else if (this.isElement(ecClassId))
|
|
873
1318
|
this.handleChange(this.element, changeType, change.ECInstanceId);
|
|
874
1319
|
}
|
|
1320
|
+
tryGetAspectOwnerElementId(aspectId) {
|
|
1321
|
+
try {
|
|
1322
|
+
return this._db.elements.getAspect(aspectId).element.id;
|
|
1323
|
+
}
|
|
1324
|
+
catch (error) {
|
|
1325
|
+
if (error instanceof core_common_1.IModelError &&
|
|
1326
|
+
error.errorNumber === core_bentley_1.IModelStatus.NotFound) {
|
|
1327
|
+
return undefined;
|
|
1328
|
+
}
|
|
1329
|
+
throw error;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
875
1332
|
/**
|
|
876
1333
|
* This method should only be called inside [[IModelTransformer.addCustomChanges]].
|
|
877
1334
|
* It adds the provided change to the element changes maintained by this instance of ChangedInstanceIds.
|
|
@@ -933,14 +1390,47 @@ class ChangedInstanceIds {
|
|
|
933
1390
|
* 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
1391
|
* from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
|
|
935
1392
|
* @note It is the responsibility of the caller to ensure that the provided id is, in fact an aspect.
|
|
1393
|
+
* @param elementIds Owning element IDs. Required when `changeType` is
|
|
1394
|
+
* `Deleted` or the source aspect row is unavailable.
|
|
936
1395
|
* @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
1396
|
* @beta
|
|
938
1397
|
*/
|
|
939
|
-
addCustomAspectChange(changeType, ids) {
|
|
1398
|
+
addCustomAspectChange(changeType, ids, elementIds) {
|
|
1399
|
+
if (elementIds !== undefined) {
|
|
1400
|
+
for (const elementId of core_bentley_1.Id64.iterable(elementIds)) {
|
|
1401
|
+
this._aspectOwnerElementIds.add(elementId);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
940
1404
|
for (const id of core_bentley_1.Id64.iterable(ids)) {
|
|
1405
|
+
if (elementIds === undefined && changeType === "Deleted") {
|
|
1406
|
+
core_bentley_1.ITwinError.throwError({
|
|
1407
|
+
iTwinErrorId: {
|
|
1408
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1409
|
+
key: IModelTransformerError_1.IModelTransformerError.AspectOwnerRequired,
|
|
1410
|
+
},
|
|
1411
|
+
message: "Custom deleted ElementAspect changes require the owning element ID.",
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
if (elementIds === undefined && changeType !== "Deleted") {
|
|
1415
|
+
const ownerElementId = this.tryGetAspectOwnerElementId(id);
|
|
1416
|
+
if (ownerElementId === undefined) {
|
|
1417
|
+
core_bentley_1.ITwinError.throwError({
|
|
1418
|
+
iTwinErrorId: {
|
|
1419
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1420
|
+
key: IModelTransformerError_1.IModelTransformerError.AspectOwnerRequired,
|
|
1421
|
+
},
|
|
1422
|
+
message: "Custom ElementAspect changes require the owning element ID when the source aspect is unavailable.",
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1426
|
+
}
|
|
941
1427
|
this.handleChange(this.aspect, changeType, id);
|
|
942
1428
|
}
|
|
943
1429
|
}
|
|
1430
|
+
recordCustomAspectChange(changeType, aspectId, ownerElementId) {
|
|
1431
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1432
|
+
this.handleChange(this.aspect, changeType, aspectId);
|
|
1433
|
+
}
|
|
944
1434
|
/**
|
|
945
1435
|
* 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
1436
|
* 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 +1439,21 @@ class ChangedInstanceIds {
|
|
|
949
1439
|
const params = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
950
1440
|
const ecQuery = `
|
|
951
1441
|
WITH RECURSIVE hierarchy (parentId) AS (
|
|
952
|
-
SELECT Model.Id
|
|
1442
|
+
SELECT element.Model.Id
|
|
1443
|
+
FROM bis.Element element
|
|
1444
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
953
1445
|
UNION
|
|
954
1446
|
SELECT ParentModel.id
|
|
955
1447
|
FROM bis.Model e
|
|
956
1448
|
INNER JOIN hierarchy h ON h.parentId = e.ECInstanceId
|
|
957
1449
|
)
|
|
958
1450
|
SELECT parentId FROM hierarchy where parentId is not null
|
|
1451
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES
|
|
959
1452
|
`;
|
|
960
1453
|
const parentModelIds = new Set();
|
|
961
|
-
for await (const row of this._db.createQueryReader(ecQuery, params
|
|
1454
|
+
for await (const row of this._db.createQueryReader(ecQuery, params, {
|
|
1455
|
+
usePrimaryConn: true,
|
|
1456
|
+
})) {
|
|
962
1457
|
// Transformer handles update as insert when element does not exist in target.
|
|
963
1458
|
// Which means that in scenario where child and parent model are filtered out from target,
|
|
964
1459
|
// and child element is inserted trough custom change, its parent model will be marked as updated.
|
|
@@ -972,11 +1467,20 @@ class ChangedInstanceIds {
|
|
|
972
1467
|
return parentModelIds;
|
|
973
1468
|
}
|
|
974
1469
|
async markElementRelationshipsAsInserted(relationshipClassName, elementIds) {
|
|
975
|
-
const ecQuery = `SELECT ECInstanceId
|
|
976
|
-
|
|
977
|
-
|
|
1470
|
+
const ecQuery = `SELECT relationship.ECInstanceId
|
|
1471
|
+
FROM ${relationshipClassName} relationship
|
|
1472
|
+
INNER JOIN IdSet(:elementIds) ids
|
|
1473
|
+
ON ids.id = relationship.TargetECInstanceId
|
|
1474
|
+
UNION
|
|
1475
|
+
SELECT relationship.ECInstanceId
|
|
1476
|
+
FROM ${relationshipClassName} relationship
|
|
1477
|
+
INNER JOIN IdSet(:elementIds) ids
|
|
1478
|
+
ON ids.id = relationship.SourceECInstanceId
|
|
1479
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`;
|
|
978
1480
|
const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
979
|
-
const queryReader = this._db.createQueryReader(ecQuery, queryBinder
|
|
1481
|
+
const queryReader = this._db.createQueryReader(ecQuery, queryBinder, {
|
|
1482
|
+
usePrimaryConn: true,
|
|
1483
|
+
});
|
|
980
1484
|
for await (const row of queryReader) {
|
|
981
1485
|
this.handleChange(this.relationship, "Inserted", row.ECInstanceId);
|
|
982
1486
|
}
|
|
@@ -986,11 +1490,17 @@ class ChangedInstanceIds {
|
|
|
986
1490
|
core_backend_1.ElementUniqueAspect.classFullName,
|
|
987
1491
|
core_backend_1.ElementMultiAspect.classFullName,
|
|
988
1492
|
]) {
|
|
989
|
-
const ecQuery = `
|
|
1493
|
+
const ecQuery = `SELECT aspect.ECInstanceId, aspect.Element.Id
|
|
1494
|
+
FROM ${aspectClassName} aspect
|
|
1495
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = aspect.Element.Id
|
|
1496
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`;
|
|
990
1497
|
const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
991
|
-
const queryReader = this._db.createQueryReader(ecQuery, queryBinder
|
|
1498
|
+
const queryReader = this._db.createQueryReader(ecQuery, queryBinder, {
|
|
1499
|
+
usePrimaryConn: true,
|
|
1500
|
+
});
|
|
992
1501
|
for await (const row of queryReader) {
|
|
993
|
-
|
|
1502
|
+
const [aspectId, elementId] = row.toArray();
|
|
1503
|
+
this.recordCustomAspectChange("Inserted", aspectId, elementId);
|
|
994
1504
|
}
|
|
995
1505
|
}
|
|
996
1506
|
}
|
|
@@ -1057,23 +1567,7 @@ class ChangedInstanceIds {
|
|
|
1057
1567
|
if (csFileProps === undefined)
|
|
1058
1568
|
return undefined;
|
|
1059
1569
|
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
|
-
}
|
|
1570
|
+
await ChangesetScanner_1.ChangesetScanner.scan(opts.iModel, csFileProps, changedInstanceIds);
|
|
1077
1571
|
return changedInstanceIds;
|
|
1078
1572
|
}
|
|
1079
1573
|
}
|