@itwin/imodel-transformer 2.0.0-dev.8 → 2.0.0-experimental.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -27
- package/lib/cjs/Algo.d.ts +0 -4
- package/lib/cjs/Algo.d.ts.map +1 -1
- package/lib/cjs/Algo.js +0 -11
- 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 +26 -26
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/ChangedElementForest.d.ts +14 -0
- package/lib/cjs/ChangedElementForest.d.ts.map +1 -0
- package/lib/cjs/ChangedElementForest.js +86 -0
- package/lib/cjs/ChangedElementForest.js.map +1 -0
- package/lib/cjs/ChangesetScanner.d.ts +61 -0
- package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
- package/lib/cjs/ChangesetScanner.js +104 -0
- package/lib/cjs/ChangesetScanner.js.map +1 -0
- package/lib/cjs/ECReferenceTypesCache.d.ts +0 -4
- package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
- package/lib/cjs/ECReferenceTypesCache.js +54 -102
- 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 +56 -0
- package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
- package/lib/cjs/ElementAspectCleanup.js +175 -0
- package/lib/cjs/ElementAspectCleanup.js.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts +47 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.js +160 -0
- package/lib/cjs/ElementAspectExportCoordinator.js.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts +44 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.js +152 -0
- package/lib/cjs/ElementAspectExportProcessor.js.map +1 -0
- package/lib/cjs/ElementBulkDelete.d.ts +30 -0
- package/lib/cjs/ElementBulkDelete.d.ts.map +1 -0
- package/lib/cjs/ElementBulkDelete.js +358 -0
- package/lib/cjs/ElementBulkDelete.js.map +1 -0
- 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/EntityUnifier.d.ts +7 -7
- package/lib/cjs/EntityUnifier.d.ts.map +1 -1
- package/lib/cjs/EntityUnifier.js +55 -24
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts +8 -2
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +42 -12
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +143 -30
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +871 -172
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +87 -31
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +361 -139
- 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 +132 -174
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +679 -1025
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/IModelTransformerError.d.ts +84 -0
- package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
- package/lib/cjs/IModelTransformerError.js +93 -0
- package/lib/cjs/IModelTransformerError.js.map +1 -0
- package/lib/cjs/ProvenanceManager.d.ts +194 -0
- package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
- package/lib/cjs/ProvenanceManager.js +771 -0
- package/lib/cjs/ProvenanceManager.js.map +1 -0
- package/lib/cjs/SyncTypeResolver.d.ts +32 -0
- package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
- package/lib/cjs/SyncTypeResolver.js +92 -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 +27 -0
- package/lib/cjs/imodel-transformer.d.ts.map +1 -1
- package/lib/cjs/imodel-transformer.js +61 -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 +70 -57
- 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/ElementCascadingDeleter.d.ts +0 -22
- package/lib/cjs/ElementCascadingDeleter.d.ts.map +0 -1
- package/lib/cjs/ElementCascadingDeleter.js +0 -57
- package/lib/cjs/ElementCascadingDeleter.js.map +0 -1
- package/lib/cjs/EntityMap.d.ts +0 -26
- package/lib/cjs/EntityMap.d.ts.map +0 -1
- package/lib/cjs/EntityMap.js +0 -54
- package/lib/cjs/EntityMap.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,10 +13,22 @@ const core_bentley_1 = require("@itwin/core-bentley");
|
|
|
13
13
|
const core_common_1 = require("@itwin/core-common");
|
|
14
14
|
const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
|
|
15
15
|
const core_backend_1 = require("@itwin/core-backend");
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const node_assert_1 = require("node:assert");
|
|
17
|
+
const ElementAspectCleanup_1 = require("./ElementAspectCleanup");
|
|
18
18
|
const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
|
|
19
|
+
const IModelTransformerError_1 = require("./IModelTransformerError");
|
|
20
|
+
const ElementBulkDelete_1 = require("./ElementBulkDelete");
|
|
19
21
|
const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter;
|
|
22
|
+
/** Lists the first kept references for a log message. */
|
|
23
|
+
function formatKeptReferences(keptReferences) {
|
|
24
|
+
const maxListed = 10;
|
|
25
|
+
const listed = [...keptReferences]
|
|
26
|
+
.slice(0, maxListed)
|
|
27
|
+
.map(([elementId, referencingId]) => `${elementId} (referenced by ${referencingId})`)
|
|
28
|
+
.join(", ");
|
|
29
|
+
const unlisted = keptReferences.size - maxListed;
|
|
30
|
+
return unlisted > 0 ? `${listed}, and ${unlisted} more` : listed;
|
|
31
|
+
}
|
|
20
32
|
/** Base class for importing data into an iModel.
|
|
21
33
|
* @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md)
|
|
22
34
|
* @see [IModelExporter]($transformer)
|
|
@@ -26,6 +38,23 @@ const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IMo
|
|
|
26
38
|
class IModelImporter {
|
|
27
39
|
/** The read/write target iModel. */
|
|
28
40
|
targetDb;
|
|
41
|
+
/** The [[EditTxn]] used for write operations on the target iModel.
|
|
42
|
+
* @beta
|
|
43
|
+
*/
|
|
44
|
+
_editTxn;
|
|
45
|
+
/** The [[EditTxn]] used for write operations on the target iModel.
|
|
46
|
+
* @beta
|
|
47
|
+
*/
|
|
48
|
+
get editTxn() {
|
|
49
|
+
return this._editTxn;
|
|
50
|
+
}
|
|
51
|
+
_elementAspectCleanup;
|
|
52
|
+
/** Deletes replaceable ElementAspects that an owner batch did not reuse, through this importer's customized deletion callback.
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
get elementAspectCleanup() {
|
|
56
|
+
return this._elementAspectCleanup;
|
|
57
|
+
}
|
|
29
58
|
/** resolved initialization options for the importer
|
|
30
59
|
* @beta
|
|
31
60
|
*/
|
|
@@ -39,6 +68,7 @@ class IModelImporter {
|
|
|
39
68
|
* To resolve code values to their intended values call [[IModelImporter.resolveDuplicateCodeValues]].
|
|
40
69
|
*/
|
|
41
70
|
_duplicateCodeValueMap;
|
|
71
|
+
_entityExistenceCaches = new Set();
|
|
42
72
|
/**
|
|
43
73
|
* A set of elementIds that the transformer adds to while exporting elements to indicate that the element already exists in the target.
|
|
44
74
|
* Defaults to an empty set.
|
|
@@ -52,9 +82,10 @@ class IModelImporter {
|
|
|
52
82
|
* @note This set should stay small, as right after the transformer pushes to it, the importer will remove from the set.
|
|
53
83
|
*/
|
|
54
84
|
_elementsToUpdateDuringPreserveIds = new Set([]);
|
|
55
|
-
/** The set of elements that
|
|
56
|
-
* Defaults to an empty set.
|
|
85
|
+
/** The set of target elements that this importer must not directly update or request for deletion.
|
|
86
|
+
* Defaults to an empty set and is populated by consumers or custom transformers.
|
|
57
87
|
* @note Adding an element to this set is typically necessary when remapping a source element to one that already exists in the target and already has the desired properties.
|
|
88
|
+
* @note Deletion protection applies only when the exact element ID is passed to [[deleteElement]] or [[deleteElements]]. It does not protect the element from deletion through a parent, containing model, or other cascading dependency. Consumers that need to preserve such an element must also add every requested deletion root whose cascade would delete it.
|
|
58
89
|
*/
|
|
59
90
|
doNotUpdateElementIds = new Set([]);
|
|
60
91
|
/** This set is ONLY used for elements that are always present even in an "empty" iModel. We will use this set to filter out changes to root elements if [[IModelTransformOptions.skipPropagateChangesToRootElements]] is false. */
|
|
@@ -72,11 +103,12 @@ class IModelImporter {
|
|
|
72
103
|
"geometryGuid", // cannot compare GeometricModel.GeometryGuid values across iModels
|
|
73
104
|
]);
|
|
74
105
|
/** Construct a new IModelImporter
|
|
75
|
-
* @param
|
|
106
|
+
* @param editTxn The [[EditTxn]] to use for write operations on the target iModel. The target db is derived from `editTxn.iModel`.
|
|
76
107
|
* @param options The options that specify how the import should be done.
|
|
77
108
|
*/
|
|
78
|
-
constructor(
|
|
79
|
-
this.targetDb =
|
|
109
|
+
constructor(editTxn, options) {
|
|
110
|
+
this.targetDb = editTxn.iModel;
|
|
111
|
+
this._editTxn = editTxn;
|
|
80
112
|
this.options = {
|
|
81
113
|
autoExtendProjectExtents: options?.autoExtendProjectExtents ?? true,
|
|
82
114
|
preserveElementIdsForFiltering: options?.preserveElementIdsForFiltering ?? false,
|
|
@@ -84,6 +116,19 @@ class IModelImporter {
|
|
|
84
116
|
skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
|
|
85
117
|
};
|
|
86
118
|
this._duplicateCodeValueMap = new Map();
|
|
119
|
+
this._elementAspectCleanup = new ElementAspectCleanup_1.ElementAspectCleanup(this.targetDb, this._editTxn, async (aspect) => this.onDeleteElementAspect(aspect));
|
|
120
|
+
}
|
|
121
|
+
/** Register a transformation-scoped existence cache for delete invalidation.
|
|
122
|
+
* @internal
|
|
123
|
+
*/
|
|
124
|
+
registerEntityExistenceCache(cache) {
|
|
125
|
+
this._entityExistenceCaches.add(cache);
|
|
126
|
+
}
|
|
127
|
+
/** Stop invalidating a transformation-scoped existence cache.
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
unregisterEntityExistenceCache(cache) {
|
|
131
|
+
this._entityExistenceCaches.delete(cache);
|
|
87
132
|
}
|
|
88
133
|
/**
|
|
89
134
|
* Checks [[IModelImportOptions.skipPropagateChangesToRootElements]], [[IModelImporter.rootElementIds]], and [[IModelImporter.doNotUpdateElementIds]] and returns true for 'do not update', false for do 'update'.
|
|
@@ -106,9 +151,15 @@ class IModelImporter {
|
|
|
106
151
|
this._elementsToUpdateDuringPreserveIds.add(elementId);
|
|
107
152
|
}
|
|
108
153
|
/** Import the specified ModelProps (either as an insert or an update) into the target iModel. */
|
|
109
|
-
importModel(modelProps) {
|
|
154
|
+
async importModel(modelProps) {
|
|
110
155
|
if (undefined === modelProps.id || !core_bentley_1.Id64.isValidId64(modelProps.id))
|
|
111
|
-
|
|
156
|
+
core_bentley_1.ITwinError.throwError({
|
|
157
|
+
iTwinErrorId: {
|
|
158
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
159
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidModelId,
|
|
160
|
+
},
|
|
161
|
+
message: "Model Id not provided, should be the same as the ModeledElementId",
|
|
162
|
+
});
|
|
112
163
|
if (this.doNotUpdateElement(modelProps.id)) {
|
|
113
164
|
core_bentley_1.Logger.logInfo(loggerCategory, `Do not update target model ${modelProps.id}`);
|
|
114
165
|
return;
|
|
@@ -116,14 +167,14 @@ class IModelImporter {
|
|
|
116
167
|
try {
|
|
117
168
|
const model = this.targetDb.models.getModel(modelProps.id); // throws IModelError.NotFound if model does not exist
|
|
118
169
|
if (hasEntityChanged(model, modelProps, this._modelPropertiesToIgnore)) {
|
|
119
|
-
this.onUpdateModel(modelProps);
|
|
170
|
+
await this.onUpdateModel(modelProps);
|
|
120
171
|
}
|
|
121
172
|
}
|
|
122
173
|
catch (error) {
|
|
123
174
|
// catch NotFound error and insertModel
|
|
124
175
|
if (error instanceof core_common_1.IModelError &&
|
|
125
176
|
error.errorNumber === core_bentley_1.IModelStatus.NotFound) {
|
|
126
|
-
this.onInsertModel(modelProps);
|
|
177
|
+
await this.onInsertModel(modelProps);
|
|
127
178
|
return;
|
|
128
179
|
}
|
|
129
180
|
throw error;
|
|
@@ -132,29 +183,35 @@ class IModelImporter {
|
|
|
132
183
|
/** Create a new Model from the specified ModelProps and insert it into the target iModel.
|
|
133
184
|
* @note A subclass may override this method to customize insert behavior but should call `super.onInsertModel`.
|
|
134
185
|
*/
|
|
135
|
-
onInsertModel(modelProps) {
|
|
186
|
+
async onInsertModel(modelProps) {
|
|
136
187
|
try {
|
|
137
|
-
const modelId = this.
|
|
188
|
+
const modelId = this._editTxn.insertModel(modelProps);
|
|
138
189
|
core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatModelForLogger(modelProps)}`);
|
|
139
|
-
this.trackProgress();
|
|
190
|
+
await this.trackProgress();
|
|
140
191
|
return modelId;
|
|
141
192
|
}
|
|
142
193
|
catch (error) {
|
|
143
194
|
if (!this.targetDb.containsClass(modelProps.classFullName)) {
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
195
|
+
// Translate only confirmed missing-class failures; otherwise retain the backend error's identity.
|
|
196
|
+
core_bentley_1.ITwinError.throwError({
|
|
197
|
+
iTwinErrorId: {
|
|
198
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
199
|
+
key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
|
|
200
|
+
},
|
|
201
|
+
message: `Model class "${modelProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
|
|
202
|
+
cause: error,
|
|
203
|
+
});
|
|
147
204
|
}
|
|
148
|
-
throw error;
|
|
205
|
+
throw error;
|
|
149
206
|
}
|
|
150
207
|
}
|
|
151
208
|
/** Update an existing Model in the target iModel from the specified ModelProps.
|
|
152
209
|
* @note A subclass may override this method to customize update behavior but should call `super.onUpdateModel`.
|
|
153
210
|
*/
|
|
154
|
-
onUpdateModel(modelProps) {
|
|
155
|
-
this.
|
|
211
|
+
async onUpdateModel(modelProps) {
|
|
212
|
+
this._editTxn.updateModel(modelProps);
|
|
156
213
|
core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatModelForLogger(modelProps)}`);
|
|
157
|
-
this.trackProgress();
|
|
214
|
+
await this.trackProgress();
|
|
158
215
|
}
|
|
159
216
|
/** Format a Model for the Logger. */
|
|
160
217
|
formatModelForLogger(modelProps) {
|
|
@@ -165,18 +222,18 @@ class IModelImporter {
|
|
|
165
222
|
* Tries to update an element with the specified element properties.
|
|
166
223
|
* If a duplicate code error occurs, it assigns a new unique code value and retries the update
|
|
167
224
|
*/
|
|
168
|
-
tryUpdateElement(elemProps) {
|
|
225
|
+
async tryUpdateElement(elemProps) {
|
|
169
226
|
try {
|
|
170
|
-
this.onUpdateElement(elemProps);
|
|
227
|
+
await this.onUpdateElement(elemProps);
|
|
171
228
|
}
|
|
172
229
|
catch (err) {
|
|
173
230
|
if (err.errorNumber === core_bentley_1.IModelStatus.DuplicateCode) {
|
|
174
|
-
|
|
231
|
+
(0, node_assert_1.strict)(elemProps.code.value !== undefined, "NULL code values are always considered unique and cannot clash");
|
|
175
232
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
176
233
|
this._duplicateCodeValueMap.set(elemProps.id, elemProps.code.value);
|
|
177
234
|
// Using NULL code values as an alternative is not valid because definition elements cannot have NULL code values.
|
|
178
235
|
elemProps.code.value = core_bentley_1.Guid.createValue();
|
|
179
|
-
this.onUpdateElement(elemProps);
|
|
236
|
+
await this.onUpdateElement(elemProps);
|
|
180
237
|
}
|
|
181
238
|
else {
|
|
182
239
|
throw err;
|
|
@@ -184,7 +241,7 @@ class IModelImporter {
|
|
|
184
241
|
}
|
|
185
242
|
}
|
|
186
243
|
/** Import the specified ElementProps (either as an insert or an update) into the target iModel. */
|
|
187
|
-
importElement(elementProps) {
|
|
244
|
+
async importElement(elementProps) {
|
|
188
245
|
if (undefined !== elementProps.id &&
|
|
189
246
|
this.doNotUpdateElement(elementProps.id)) {
|
|
190
247
|
core_bentley_1.Logger.logInfo(loggerCategory, `Do not update target element ${elementProps.id}`);
|
|
@@ -192,7 +249,13 @@ class IModelImporter {
|
|
|
192
249
|
}
|
|
193
250
|
if (this.options.preserveElementIdsForFiltering) {
|
|
194
251
|
if (elementProps.id === undefined) {
|
|
195
|
-
|
|
252
|
+
core_bentley_1.ITwinError.throwError({
|
|
253
|
+
iTwinErrorId: {
|
|
254
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
255
|
+
key: IModelTransformerError_1.IModelTransformerError.ElementIdRequired,
|
|
256
|
+
},
|
|
257
|
+
message: "elementProps.id must be defined during a preserveIds operation",
|
|
258
|
+
});
|
|
196
259
|
}
|
|
197
260
|
// Categories are the only element that onInserted will immediately insert a new element (their default subcategory)
|
|
198
261
|
// since default subcategories always exist and always will be inserted after their categories, we treat them as an update
|
|
@@ -200,24 +263,24 @@ class IModelImporter {
|
|
|
200
263
|
// Always present elements (0xe, 0x1, 0x10) also will be updated to prevent duplicate inserts.
|
|
201
264
|
if ((isSubCategory(elementProps) && isDefaultSubCategory(elementProps)) ||
|
|
202
265
|
this._rootElementIds.has(elementProps.id)) {
|
|
203
|
-
this.onUpdateElement(elementProps);
|
|
266
|
+
await this.onUpdateElement(elementProps);
|
|
204
267
|
}
|
|
205
268
|
else {
|
|
206
269
|
if (this._elementsToUpdateDuringPreserveIds.has(elementProps.id)) {
|
|
207
|
-
this.tryUpdateElement(elementProps);
|
|
270
|
+
await this.tryUpdateElement(elementProps);
|
|
208
271
|
this._elementsToUpdateDuringPreserveIds.delete(elementProps.id);
|
|
209
272
|
}
|
|
210
273
|
else {
|
|
211
|
-
this.onInsertElement(elementProps);
|
|
274
|
+
await this.onInsertElement(elementProps);
|
|
212
275
|
}
|
|
213
276
|
}
|
|
214
277
|
}
|
|
215
278
|
else {
|
|
216
279
|
if (undefined !== elementProps.id) {
|
|
217
|
-
this.tryUpdateElement(elementProps);
|
|
280
|
+
await this.tryUpdateElement(elementProps);
|
|
218
281
|
}
|
|
219
282
|
else {
|
|
220
|
-
elementProps.id = this.onInsertElement(elementProps); // targetElementProps.id assigned by insertElement
|
|
283
|
+
elementProps.id = await this.onInsertElement(elementProps); // targetElementProps.id assigned by insertElement
|
|
221
284
|
}
|
|
222
285
|
}
|
|
223
286
|
return elementProps.id;
|
|
@@ -226,15 +289,15 @@ class IModelImporter {
|
|
|
226
289
|
* @returns The Id of the newly inserted Element.
|
|
227
290
|
* @note A subclass may override this method to customize insert behavior but should call `super.onInsertElement`.
|
|
228
291
|
*/
|
|
229
|
-
onInsertElement(elementProps) {
|
|
292
|
+
async onInsertElement(elementProps) {
|
|
230
293
|
try {
|
|
231
|
-
const elementId = this.
|
|
294
|
+
const elementId = this._editTxn.insertElement(elementProps, {
|
|
232
295
|
forceUseId: this.options.preserveElementIdsForFiltering,
|
|
233
296
|
});
|
|
234
297
|
// set the id like [IModelDb.insertElement]($backend), does, the raw nativeDb method does not
|
|
235
298
|
elementProps.id = elementId;
|
|
236
299
|
core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatElementForLogger(elementProps)}`);
|
|
237
|
-
this.trackProgress();
|
|
300
|
+
await this.trackProgress();
|
|
238
301
|
if (this.options.simplifyElementGeometry) {
|
|
239
302
|
this.targetDb.simplifyElementGeometry({
|
|
240
303
|
id: elementId,
|
|
@@ -246,23 +309,34 @@ class IModelImporter {
|
|
|
246
309
|
}
|
|
247
310
|
catch (error) {
|
|
248
311
|
if (!this.targetDb.containsClass(elementProps.classFullName)) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
312
|
+
core_bentley_1.ITwinError.throwError({
|
|
313
|
+
iTwinErrorId: {
|
|
314
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
315
|
+
key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
|
|
316
|
+
},
|
|
317
|
+
message: `Element class "${elementProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
|
|
318
|
+
cause: error,
|
|
319
|
+
});
|
|
252
320
|
}
|
|
253
|
-
throw error;
|
|
321
|
+
throw error;
|
|
254
322
|
}
|
|
255
323
|
}
|
|
256
324
|
/** Update an existing Element in the target iModel from the specified ElementProps.
|
|
257
325
|
* @note A subclass may override this method to customize update behavior but should call `super.onUpdateElement`.
|
|
258
326
|
*/
|
|
259
|
-
onUpdateElement(elementProps) {
|
|
327
|
+
async onUpdateElement(elementProps) {
|
|
260
328
|
if (!elementProps.id) {
|
|
261
|
-
|
|
329
|
+
core_bentley_1.ITwinError.throwError({
|
|
330
|
+
iTwinErrorId: {
|
|
331
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
332
|
+
key: IModelTransformerError_1.IModelTransformerError.ElementIdRequired,
|
|
333
|
+
},
|
|
334
|
+
message: "ElementId not provided",
|
|
335
|
+
});
|
|
262
336
|
}
|
|
263
|
-
this.
|
|
337
|
+
this._editTxn.updateElement(elementProps);
|
|
264
338
|
core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatElementForLogger(elementProps)}`);
|
|
265
|
-
this.trackProgress();
|
|
339
|
+
await this.trackProgress();
|
|
266
340
|
if (this.options.simplifyElementGeometry) {
|
|
267
341
|
this.targetDb.simplifyElementGeometry({
|
|
268
342
|
id: elementProps.id,
|
|
@@ -271,34 +345,83 @@ class IModelImporter {
|
|
|
271
345
|
core_bentley_1.Logger.logInfo(loggerCategory, `Simplified element geometry for ${this.formatElementForLogger(elementProps)}`);
|
|
272
346
|
}
|
|
273
347
|
}
|
|
274
|
-
/**
|
|
275
|
-
|
|
276
|
-
|
|
348
|
+
/** Deletes one target element tree through [[deleteElements]]. */
|
|
349
|
+
async deleteElement(elementId) {
|
|
350
|
+
await this.deleteElements(new Set([elementId]));
|
|
351
|
+
}
|
|
352
|
+
/** Adds roots required by code-scope dependencies, then deletes the target element trees in as few native operations as their references allow.
|
|
353
|
+
* Usually one native operation is enough. When a tree contains a definition, such as a category, that another deleted element uses, the elements that use it are deleted first.
|
|
354
|
+
* Elements that something outside the deleted trees still references are kept, with a warning, and the rest are deleted.
|
|
355
|
+
* @note An override must call `super.onDeleteElements` once to perform the deletion.
|
|
277
356
|
*/
|
|
278
|
-
|
|
279
|
-
(0,
|
|
280
|
-
|
|
281
|
-
|
|
357
|
+
async onDeleteElements(elementIds) {
|
|
358
|
+
const plan = await (0, ElementBulkDelete_1.planBulkDelete)(this.targetDb, elementIds);
|
|
359
|
+
if (plan.keptReferences.size > 0) {
|
|
360
|
+
core_bentley_1.Logger.logWarning(loggerCategory, `Kept ${plan.keptReferences.size} elements that elements outside the deleted trees still reference, and the elements that contain them: ${formatKeptReferences(plan.keptReferences)}`);
|
|
361
|
+
}
|
|
362
|
+
if (plan.phases.length === 0)
|
|
363
|
+
return;
|
|
364
|
+
let rootCount = 0;
|
|
365
|
+
for (const roots of plan.phases) {
|
|
366
|
+
rootCount += roots.length;
|
|
367
|
+
this.deleteElementRoots(roots);
|
|
368
|
+
}
|
|
369
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Deleted ${elementIds.size} requested element trees using ${rootCount} native deletion roots in ${plan.phases.length} native operations`);
|
|
370
|
+
await this.trackProgress(elementIds.size);
|
|
371
|
+
}
|
|
372
|
+
deleteElementRoots(roots) {
|
|
373
|
+
const result = this._editTxn.deleteElements(roots);
|
|
374
|
+
if (result.status !== core_backend_1.BulkDeleteElementsStatus.Success) {
|
|
375
|
+
core_bentley_1.ITwinError.throwError({
|
|
376
|
+
iTwinErrorId: {
|
|
377
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
378
|
+
key: IModelTransformerError_1.IModelTransformerError.ElementBulkDeleteFailed,
|
|
379
|
+
},
|
|
380
|
+
message: `Bulk element deletion failed: status ${core_backend_1.BulkDeleteElementsStatus[result.status]}, ` +
|
|
381
|
+
`SQLite status ${result.sqlDeleteStatus}, failed element IDs: ${[
|
|
382
|
+
...result.failedIds,
|
|
383
|
+
].join(", ")}`,
|
|
384
|
+
status: result.status,
|
|
385
|
+
sqlDeleteStatus: result.sqlDeleteStatus,
|
|
386
|
+
failedIds: new Set(result.failedIds),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
282
389
|
}
|
|
283
|
-
/**
|
|
284
|
-
|
|
285
|
-
|
|
390
|
+
/** Deletes target element trees, using one native operation unless references between the trees require more.
|
|
391
|
+
* Requested roots in [[doNotUpdateElementIds]] are skipped. This does not prevent an element in that set from being deleted as part of another root's cascade.
|
|
392
|
+
* Elements that something outside the deleted trees still references are kept, along with their child elements and sub-models and the elements that contain them. A warning lists them.
|
|
393
|
+
* @throws [[ElementBulkDeleteError]] if a native operation fails for any root, for example because geometry outside the deleted trees uses a geometry part in them, or because of a reference in a domain schema. The transformer does not check those references first. Deletions from that and earlier native operations stay pending in the caller-owned transaction. Abandon the transaction before retrying.
|
|
394
|
+
*/
|
|
395
|
+
async deleteElements(elementIds) {
|
|
396
|
+
const idsToDelete = new Set();
|
|
397
|
+
for (const elementId of elementIds) {
|
|
398
|
+
if (!this.doNotUpdateElement(elementId)) {
|
|
399
|
+
idsToDelete.add(elementId);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
286
402
|
core_bentley_1.Logger.logInfo(loggerCategory, `Do not delete target element ${elementId}`);
|
|
287
|
-
return;
|
|
288
403
|
}
|
|
289
|
-
|
|
404
|
+
if (idsToDelete.size === 0)
|
|
405
|
+
return;
|
|
406
|
+
await this.onDeleteElements(idsToDelete);
|
|
407
|
+
// Element deletion cascades can remove descendants and their submodels.
|
|
408
|
+
for (const cache of this._entityExistenceCaches)
|
|
409
|
+
cache.clearDb(this.targetDb);
|
|
290
410
|
}
|
|
291
411
|
/** Delete the specified Model from the target iModel.
|
|
292
412
|
* @note A subclass may override this method to customize delete behavior but should call `super.onDeleteModel`.
|
|
293
413
|
*/
|
|
294
414
|
async onDeleteModel(modelId) {
|
|
295
|
-
this.
|
|
415
|
+
this._editTxn.deleteModel(modelId);
|
|
296
416
|
core_bentley_1.Logger.logInfo(loggerCategory, `Deleted model ${modelId}`);
|
|
297
|
-
this.trackProgress();
|
|
417
|
+
await this.trackProgress();
|
|
298
418
|
}
|
|
299
419
|
/** Delete the specified Model from the target iModel. */
|
|
300
420
|
async deleteModel(modelId) {
|
|
301
421
|
await this.onDeleteModel(modelId);
|
|
422
|
+
const modelReference = core_backend_1.EntityReferences.fromEntityType(modelId, core_common_1.ConcreteEntityTypes.Model);
|
|
423
|
+
for (const cache of this._entityExistenceCaches)
|
|
424
|
+
cache.invalidate(this.targetDb, modelReference);
|
|
302
425
|
}
|
|
303
426
|
/** Format an Element for the Logger. */
|
|
304
427
|
formatElementForLogger(elementProps) {
|
|
@@ -309,25 +432,79 @@ class IModelImporter {
|
|
|
309
432
|
: "";
|
|
310
433
|
return `${elementProps.classFullName} ${namePiece}[${elementProps.id}]`;
|
|
311
434
|
}
|
|
312
|
-
/** Import an ElementUniqueAspect into the target iModel.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
435
|
+
/** Import an ElementUniqueAspect into the target iModel.
|
|
436
|
+
* @note Only an existing aspect of exactly `aspectProps.classFullName` on the owner is reused, and it is updated only when its properties differ.
|
|
437
|
+
* If there is none, the owner's unique aspects of a base or derived class are deleted through [[onDeleteElementAspect]] before the aspect is inserted, because iModel unique-aspect writes treat those classes as one slot.
|
|
438
|
+
* @returns the ID of the reused or inserted aspect
|
|
439
|
+
*/
|
|
440
|
+
async importElementUniqueAspect(aspectProps) {
|
|
441
|
+
const elementId = aspectProps.element.id;
|
|
442
|
+
const existing = this.getTargetAspects(elementId, aspectProps.classFullName)[0];
|
|
443
|
+
if (existing === undefined) {
|
|
444
|
+
// iModel unique-aspect writes treat base and derived classes as one slot:
|
|
445
|
+
// inserting replaces an aspect of a derived class, and deleting an aspect
|
|
446
|
+
// of a base class also deletes derived ones. Delete related aspects first
|
|
447
|
+
// so deletion hooks run and later deletions cannot remove the new aspect.
|
|
448
|
+
for (const aspectId of await this.queryRelatedUniqueAspectIds(elementId, aspectProps.classFullName)) {
|
|
449
|
+
this._elementAspectCleanup.retain(aspectId);
|
|
450
|
+
const aspect = (0, ElementAspectCleanup_1.tryGetAspect)(this.targetDb, aspectId);
|
|
451
|
+
if (aspect !== undefined)
|
|
452
|
+
await this.onDeleteElementAspect(aspect);
|
|
453
|
+
}
|
|
316
454
|
return this.onInsertElementAspect(aspectProps);
|
|
317
455
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
456
|
+
this._elementAspectCleanup.retain(existing.id);
|
|
457
|
+
if (hasEntityChanged(existing, aspectProps)) {
|
|
458
|
+
aspectProps.id = existing.id;
|
|
459
|
+
await this.onUpdateElementAspect(aspectProps);
|
|
321
460
|
}
|
|
322
|
-
return
|
|
461
|
+
return existing.id;
|
|
462
|
+
}
|
|
463
|
+
/** Returns the element's target aspects of exactly `classFullName`, in ECInstanceId order.
|
|
464
|
+
* Inside a transformer owner batch these come from the batch's loaded aspects; otherwise from the target iModel.
|
|
465
|
+
*/
|
|
466
|
+
getTargetAspects(elementId, classFullName) {
|
|
467
|
+
return (this._elementAspectCleanup.getAspects(elementId, classFullName) ??
|
|
468
|
+
// getAspects is polymorphic; keep only the exact class so aspects of a
|
|
469
|
+
// derived class are handled with their own class.
|
|
470
|
+
this.targetDb.elements
|
|
471
|
+
.getAspects(elementId, classFullName)
|
|
472
|
+
.filter((aspect) => (0, ElementAspectCleanup_1.isSameClass)(aspect, classFullName)));
|
|
473
|
+
}
|
|
474
|
+
/** Returns the element's unique aspects whose class is a base or derived class of `classFullName`, excluding that class itself. */
|
|
475
|
+
async queryRelatedUniqueAspectIds(elementId, classFullName) {
|
|
476
|
+
// Most owners have no unique aspect of another class; the loaded batch
|
|
477
|
+
// aspects answer that without a query.
|
|
478
|
+
const loaded = this._elementAspectCleanup.getAspects(elementId);
|
|
479
|
+
if (loaded !== undefined &&
|
|
480
|
+
!loaded.some((aspect) => aspect instanceof core_backend_1.ElementUniqueAspect &&
|
|
481
|
+
!(0, ElementAspectCleanup_1.isSameClass)(aspect, classFullName)))
|
|
482
|
+
return [];
|
|
483
|
+
const ids = [];
|
|
484
|
+
for await (const row of this.targetDb.createQueryReader(`SELECT aspect.ECInstanceId id FROM bis.ElementUniqueAspect aspect
|
|
485
|
+
WHERE aspect.Element.Id = :elementId
|
|
486
|
+
AND aspect.ECClassId <> ec_classid(:classFullName)
|
|
487
|
+
AND (
|
|
488
|
+
aspect.ECClassId IN (
|
|
489
|
+
SELECT SourceECInstanceId FROM meta.ClassHasAllBaseClasses
|
|
490
|
+
WHERE TargetECInstanceId = ec_classid(:classFullName))
|
|
491
|
+
OR aspect.ECClassId IN (
|
|
492
|
+
SELECT TargetECInstanceId FROM meta.ClassHasAllBaseClasses
|
|
493
|
+
WHERE SourceECInstanceId = ec_classid(:classFullName)))`, new core_common_1.QueryBinder()
|
|
494
|
+
.bindId("elementId", elementId)
|
|
495
|
+
.bindString("classFullName", classFullName.replace(".", ":")), { usePrimaryConn: true })) {
|
|
496
|
+
ids.push(row.id);
|
|
497
|
+
}
|
|
498
|
+
return ids;
|
|
323
499
|
}
|
|
324
500
|
/** Import the collection of ElementMultiAspects into the target iModel.
|
|
325
501
|
* @param aspectPropsArray The ElementMultiAspects to import
|
|
326
502
|
* @param filterFunc Optional filter func that is used to exclude target ElementMultiAspects that were added during iModel transformation from the update detection logic.
|
|
327
503
|
* @note For insert vs. update reasons, it is important to process all ElementMultiAspects owned by an Element at once since we don't have aspect-specific provenance.
|
|
504
|
+
* @note Aspects are grouped by `classFullName`. Each group is matched in order only against existing target aspects of exactly that class: matches are updated when their properties differ, extra props are inserted, and extra target aspects of that class are deleted. Target aspects of other classes, including derived classes, are not changed.
|
|
328
505
|
* @returns the array of ids of the resulting ElementMultiAspects, in the same order of the aspectPropsArray parameter
|
|
329
506
|
*/
|
|
330
|
-
importElementMultiAspects(aspectPropsArray,
|
|
507
|
+
async importElementMultiAspects(aspectPropsArray,
|
|
331
508
|
/** caller must use this to enforce any aspects added by IModelTransformer are not considered for update */
|
|
332
509
|
filterFunc = () => true) {
|
|
333
510
|
const result = new Array(aspectPropsArray.length).fill(undefined);
|
|
@@ -335,91 +512,104 @@ class IModelImporter {
|
|
|
335
512
|
return result;
|
|
336
513
|
}
|
|
337
514
|
const elementId = aspectPropsArray[0].element.id;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
515
|
+
const proposedAspectsByClass = new Map();
|
|
516
|
+
aspectPropsArray.forEach((props, index) => {
|
|
517
|
+
const proposedAspects = proposedAspectsByClass.get(props.classFullName) ?? [];
|
|
518
|
+
proposedAspects.push({ props, index });
|
|
519
|
+
proposedAspectsByClass.set(props.classFullName, proposedAspects);
|
|
342
520
|
});
|
|
343
521
|
// Handle ElementMultiAspects in groups by class
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
.map((props, index) => ({ props, index }))
|
|
347
|
-
.filter(({ props }) => aspectClassFullName === props.classFullName);
|
|
348
|
-
const currentAspects = this.targetDb.elements
|
|
349
|
-
.getAspects(elementId, aspectClassFullName)
|
|
522
|
+
for (const [aspectClassFullName, proposedAspects,] of proposedAspectsByClass) {
|
|
523
|
+
const currentAspects = this.getTargetAspects(elementId, aspectClassFullName)
|
|
350
524
|
.map((props, index) => ({ props, index }))
|
|
351
525
|
.filter(({ props }) => filterFunc(props));
|
|
526
|
+
for (const { props } of currentAspects)
|
|
527
|
+
this._elementAspectCleanup.retain(props.id);
|
|
352
528
|
if (proposedAspects.length >= currentAspects.length) {
|
|
353
|
-
|
|
529
|
+
for (let index = 0; index < proposedAspects.length; index++) {
|
|
530
|
+
const { props, index: resultIndex } = proposedAspects[index];
|
|
354
531
|
let id;
|
|
355
532
|
if (index < currentAspects.length) {
|
|
356
533
|
id = currentAspects[index].props.id;
|
|
357
534
|
props.id = id;
|
|
358
535
|
if (hasEntityChanged(currentAspects[index].props, props)) {
|
|
359
|
-
this.onUpdateElementAspect(props);
|
|
536
|
+
await this.onUpdateElementAspect(props);
|
|
360
537
|
}
|
|
361
538
|
id = props.id;
|
|
362
539
|
}
|
|
363
540
|
else {
|
|
364
|
-
id = this.onInsertElementAspect(props);
|
|
541
|
+
id = await this.onInsertElementAspect(props);
|
|
365
542
|
}
|
|
366
543
|
result[resultIndex] = id;
|
|
367
|
-
}
|
|
544
|
+
}
|
|
368
545
|
}
|
|
369
546
|
else {
|
|
370
|
-
|
|
371
|
-
|
|
547
|
+
for (let index = 0; index < currentAspects.length; index++) {
|
|
548
|
+
const { props } = currentAspects[index];
|
|
372
549
|
if (index < proposedAspects.length) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
550
|
+
const { props: proposedProps, index: resultIndex } = proposedAspects[index];
|
|
551
|
+
const id = props.id;
|
|
552
|
+
proposedProps.id = id;
|
|
553
|
+
if (hasEntityChanged(props, proposedProps)) {
|
|
554
|
+
await this.onUpdateElementAspect(proposedProps);
|
|
377
555
|
}
|
|
378
556
|
result[resultIndex] = id;
|
|
379
557
|
}
|
|
380
558
|
else {
|
|
381
|
-
this.onDeleteElementAspect(props);
|
|
559
|
+
await this.onDeleteElementAspect(props);
|
|
382
560
|
}
|
|
383
|
-
}
|
|
561
|
+
}
|
|
384
562
|
}
|
|
385
|
-
}
|
|
386
|
-
|
|
563
|
+
}
|
|
564
|
+
(0, node_assert_1.strict)(result.every((r) => r !== undefined));
|
|
387
565
|
return result;
|
|
388
566
|
}
|
|
389
567
|
/** Insert the ElementAspect into the target iModel.
|
|
390
568
|
* @note A subclass may override this method to customize insert behavior but should call `super.onInsertElementAspect`.
|
|
391
569
|
*/
|
|
392
|
-
onInsertElementAspect(aspectProps) {
|
|
570
|
+
async onInsertElementAspect(aspectProps) {
|
|
571
|
+
// Also covers overrides that write another owner's aspects through this hook.
|
|
572
|
+
this._elementAspectCleanup.beforeWrite(aspectProps.element.id);
|
|
393
573
|
try {
|
|
394
|
-
const id = this.
|
|
574
|
+
const id = this._editTxn.insertAspect(aspectProps);
|
|
575
|
+
this._elementAspectCleanup.afterWrite();
|
|
395
576
|
core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatElementAspectForLogger(aspectProps)}`);
|
|
396
|
-
this.trackProgress();
|
|
577
|
+
await this.trackProgress();
|
|
397
578
|
return id;
|
|
398
579
|
}
|
|
399
580
|
catch (error) {
|
|
400
581
|
if (!this.targetDb.containsClass(aspectProps.classFullName)) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
582
|
+
core_bentley_1.ITwinError.throwError({
|
|
583
|
+
iTwinErrorId: {
|
|
584
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
585
|
+
key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
|
|
586
|
+
},
|
|
587
|
+
message: `ElementAspect class "${aspectProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
|
|
588
|
+
cause: error,
|
|
589
|
+
});
|
|
404
590
|
}
|
|
405
|
-
throw error;
|
|
591
|
+
throw error;
|
|
406
592
|
}
|
|
407
593
|
}
|
|
408
594
|
/** Update the ElementAspect within the target iModel.
|
|
409
595
|
* @note A subclass may override this method to customize update behavior but should call `super.onUpdateElementAspect`.
|
|
410
596
|
*/
|
|
411
|
-
onUpdateElementAspect(aspectProps) {
|
|
412
|
-
this.
|
|
597
|
+
async onUpdateElementAspect(aspectProps) {
|
|
598
|
+
this._elementAspectCleanup.beforeWrite(aspectProps.element.id);
|
|
599
|
+
this._editTxn.updateAspect(aspectProps);
|
|
600
|
+
this._elementAspectCleanup.afterWrite();
|
|
413
601
|
core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatElementAspectForLogger(aspectProps)}`);
|
|
414
|
-
this.trackProgress();
|
|
602
|
+
await this.trackProgress();
|
|
415
603
|
}
|
|
416
604
|
/** Delete the specified ElementAspect from the target iModel.
|
|
417
605
|
* @note A subclass may override this method to customize delete behavior but should call `super.onDeleteElementAspect`.
|
|
418
606
|
*/
|
|
419
|
-
onDeleteElementAspect(targetElementAspect) {
|
|
420
|
-
this.
|
|
607
|
+
async onDeleteElementAspect(targetElementAspect) {
|
|
608
|
+
this._elementAspectCleanup.beforeWrite(targetElementAspect.element.id);
|
|
609
|
+
this._editTxn.deleteAspect(targetElementAspect.id);
|
|
610
|
+
this._elementAspectCleanup.afterWrite();
|
|
421
611
|
core_bentley_1.Logger.logInfo(loggerCategory, `Deleted ${this.formatElementAspectForLogger(targetElementAspect)}`);
|
|
422
|
-
this.trackProgress();
|
|
612
|
+
await this.trackProgress();
|
|
423
613
|
}
|
|
424
614
|
/** Format an ElementAspect for the Logger. */
|
|
425
615
|
formatElementAspectForLogger(elementAspectProps) {
|
|
@@ -428,7 +618,7 @@ class IModelImporter {
|
|
|
428
618
|
/** Import the specified RelationshipProps (either as an insert or an update) into the target iModel.
|
|
429
619
|
* @returns The instance Id of the inserted or updated Relationship.
|
|
430
620
|
*/
|
|
431
|
-
importRelationship(relationshipProps) {
|
|
621
|
+
async importRelationship(relationshipProps) {
|
|
432
622
|
if (undefined === relationshipProps.sourceId ||
|
|
433
623
|
!core_bentley_1.Id64.isValidId64(relationshipProps.sourceId)) {
|
|
434
624
|
core_bentley_1.Logger.logInfo(loggerCategory, `Ignoring ${relationshipProps.classFullName} instance because of invalid RelationshipProps.sourceId`);
|
|
@@ -449,7 +639,7 @@ class IModelImporter {
|
|
|
449
639
|
// if relationship found, update it
|
|
450
640
|
relationshipProps.id = relationship.id;
|
|
451
641
|
if (hasEntityChanged(relationship, relationshipProps)) {
|
|
452
|
-
this.onUpdateRelationship(relationshipProps);
|
|
642
|
+
await this.onUpdateRelationship(relationshipProps);
|
|
453
643
|
}
|
|
454
644
|
return relationshipProps.id;
|
|
455
645
|
}
|
|
@@ -461,62 +651,75 @@ class IModelImporter {
|
|
|
461
651
|
* @returns The instance Id of the newly inserted relationship.
|
|
462
652
|
* @note A subclass may override this method to customize insert behavior but should call `super.onInsertRelationship`.
|
|
463
653
|
*/
|
|
464
|
-
onInsertRelationship(relationshipProps) {
|
|
654
|
+
async onInsertRelationship(relationshipProps) {
|
|
465
655
|
try {
|
|
466
|
-
const targetRelInstanceId = this.
|
|
656
|
+
const targetRelInstanceId = this._editTxn.insertRelationship(relationshipProps);
|
|
467
657
|
core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatRelationshipForLogger(relationshipProps)}`);
|
|
468
|
-
this.trackProgress();
|
|
658
|
+
await this.trackProgress();
|
|
469
659
|
return targetRelInstanceId;
|
|
470
660
|
}
|
|
471
661
|
catch (error) {
|
|
472
662
|
if (!this.targetDb.containsClass(relationshipProps.classFullName)) {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
663
|
+
core_bentley_1.ITwinError.throwError({
|
|
664
|
+
iTwinErrorId: {
|
|
665
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
666
|
+
key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
|
|
667
|
+
},
|
|
668
|
+
message: `Relationship class "${relationshipProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
|
|
669
|
+
cause: error,
|
|
670
|
+
});
|
|
476
671
|
}
|
|
477
|
-
throw error;
|
|
672
|
+
throw error;
|
|
478
673
|
}
|
|
479
674
|
}
|
|
480
675
|
/** Update an existing Relationship in the target iModel from the specified RelationshipProps.
|
|
481
676
|
* @note A subclass may override this method to customize update behavior but should call `super.onUpdateRelationship`.
|
|
482
677
|
*/
|
|
483
|
-
onUpdateRelationship(relationshipProps) {
|
|
678
|
+
async onUpdateRelationship(relationshipProps) {
|
|
484
679
|
if (!relationshipProps.id) {
|
|
485
|
-
|
|
680
|
+
core_bentley_1.ITwinError.throwError({
|
|
681
|
+
iTwinErrorId: {
|
|
682
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
683
|
+
key: IModelTransformerError_1.IModelTransformerError.RelationshipIdRequired,
|
|
684
|
+
},
|
|
685
|
+
message: "Relationship instance Id not provided",
|
|
686
|
+
});
|
|
486
687
|
}
|
|
487
|
-
this.
|
|
688
|
+
this._editTxn.updateRelationship(relationshipProps);
|
|
488
689
|
core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatRelationshipForLogger(relationshipProps)}`);
|
|
489
|
-
this.trackProgress();
|
|
690
|
+
await this.trackProgress();
|
|
490
691
|
}
|
|
491
692
|
/** Delete the specified Relationship from the target iModel. */
|
|
492
|
-
onDeleteRelationship(relationshipProps) {
|
|
693
|
+
async onDeleteRelationship(relationshipProps) {
|
|
493
694
|
// Only passing in what deleteInstance actually uses, full relationshipProps is not necessary.
|
|
494
|
-
this.
|
|
695
|
+
this._editTxn.deleteRelationship({
|
|
495
696
|
id: relationshipProps.id,
|
|
496
697
|
classFullName: relationshipProps.classFullName,
|
|
497
698
|
});
|
|
498
699
|
core_bentley_1.Logger.logInfo(loggerCategory, `Deleted relationship ${relationshipProps.classFullName} id=${relationshipProps.id}`);
|
|
499
|
-
this.trackProgress();
|
|
700
|
+
await this.trackProgress();
|
|
500
701
|
}
|
|
501
702
|
/** Delete the specified Relationship from the target iModel. */
|
|
502
|
-
deleteRelationship(relationshipProps) {
|
|
503
|
-
this.onDeleteRelationship(relationshipProps);
|
|
703
|
+
async deleteRelationship(relationshipProps) {
|
|
704
|
+
await this.onDeleteRelationship(relationshipProps);
|
|
504
705
|
}
|
|
505
706
|
/** Format a Relationship for the Logger. */
|
|
506
707
|
formatRelationshipForLogger(relProps) {
|
|
507
708
|
return `${relProps.classFullName} sourceId=[${relProps.sourceId}] targetId=[${relProps.targetId}]`;
|
|
508
709
|
}
|
|
509
710
|
/** Tracks incremental progress */
|
|
510
|
-
trackProgress() {
|
|
511
|
-
this._progressCounter
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
711
|
+
async trackProgress(increment = 1) {
|
|
712
|
+
const previousProgressCounter = this._progressCounter;
|
|
713
|
+
this._progressCounter += increment;
|
|
714
|
+
const progressCallbacks = Math.floor(this._progressCounter / this.progressInterval) -
|
|
715
|
+
Math.floor(previousProgressCounter / this.progressInterval);
|
|
716
|
+
for (let i = 0; i < progressCallbacks; ++i)
|
|
717
|
+
await this.onProgress();
|
|
515
718
|
}
|
|
516
719
|
/** This method is called when IModelImporter has made incremental progress based on the [[progressInterval]] setting.
|
|
517
720
|
* @note A subclass may override this method to report custom progress but should call `super.onProgress`.
|
|
518
721
|
*/
|
|
519
|
-
onProgress() { }
|
|
722
|
+
async onProgress() { }
|
|
520
723
|
/** Optionally compute the projectExtents for the target iModel depending on the options for this IModelImporter.
|
|
521
724
|
* @note This method is automatically called from [IModelTransformer.process]($transformer).
|
|
522
725
|
* @see [IModelDb.computeProjectExtents]($backend), [[autoExtendProjectExtents]]
|
|
@@ -533,13 +736,15 @@ class IModelImporter {
|
|
|
533
736
|
const excludeOutliers = typeof this.options.autoExtendProjectExtents === "object"
|
|
534
737
|
? this.options.autoExtendProjectExtents.excludeOutliers
|
|
535
738
|
: false;
|
|
536
|
-
// If excludeOutliers is true, use the extents without outliers.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
739
|
+
// If excludeOutliers is true, use the extents without outliers.
|
|
740
|
+
// Otherwise prefer extentsWithOutliers, but fall back to extents if it's undefined or a null range.
|
|
741
|
+
const extentsWithOutliers = computedProjectExtents.extentsWithOutliers;
|
|
742
|
+
const useExtentsWithOutliers = !excludeOutliers && extentsWithOutliers && !extentsWithOutliers.isNull;
|
|
743
|
+
const newProjectExtents = useExtentsWithOutliers
|
|
744
|
+
? extentsWithOutliers
|
|
745
|
+
: computedProjectExtents.extents;
|
|
541
746
|
if (!newProjectExtents.isAlmostEqual(this.targetDb.projectExtents)) {
|
|
542
|
-
this.
|
|
747
|
+
this._editTxn.updateProjectExtents(newProjectExtents);
|
|
543
748
|
core_bentley_1.Logger.logInfo(loggerCategory, `Updated projectExtents=${JSON.stringify(this.targetDb.projectExtents)}`);
|
|
544
749
|
}
|
|
545
750
|
if (!excludeOutliers &&
|
|
@@ -572,7 +777,7 @@ class IModelImporter {
|
|
|
572
777
|
for (const [elementId, codeValue] of this._duplicateCodeValueMap) {
|
|
573
778
|
const element = this.targetDb.elements.getElement(elementId);
|
|
574
779
|
element.code.value = codeValue;
|
|
575
|
-
element.
|
|
780
|
+
this._editTxn.updateElement(element.toJSON());
|
|
576
781
|
}
|
|
577
782
|
this._duplicateCodeValueMap.clear();
|
|
578
783
|
}
|
|
@@ -580,7 +785,8 @@ class IModelImporter {
|
|
|
580
785
|
* Needs to be called to perform necessary cleanup operations.
|
|
581
786
|
* By not calling `finalize` there is a risk that data imported into targetDb will not be as expected.
|
|
582
787
|
*
|
|
583
|
-
* @note
|
|
788
|
+
* @note [[IModelTransformer.process]] invokes this method automatically. Call it explicitly after
|
|
789
|
+
* composing the transformer's lower-level subset processing methods.
|
|
584
790
|
*/
|
|
585
791
|
finalize() {
|
|
586
792
|
this.resolveDuplicateCodeValues();
|
|
@@ -595,7 +801,11 @@ exports.IModelImporter = IModelImporter;
|
|
|
595
801
|
*/
|
|
596
802
|
function hasEntityChanged(entity, entityProps, namesToIgnore) {
|
|
597
803
|
let changed = false;
|
|
598
|
-
entity.
|
|
804
|
+
const entityClass = entity.iModel.schemaContext.getSchemaItemSync(entity.schemaItemKey);
|
|
805
|
+
(0, node_assert_1.strict)(ecschema_metadata_1.EntityClass.isEntityClass(entityClass) ||
|
|
806
|
+
ecschema_metadata_1.RelationshipClass.isRelationshipClass(entityClass), `Cannot get metadata for ${entity.classFullName}.`);
|
|
807
|
+
for (const property of entityClass.getPropertiesSync()) {
|
|
808
|
+
const propertyName = property.name;
|
|
599
809
|
if (!changed) {
|
|
600
810
|
if (namesToIgnore && namesToIgnore.has(propertyName)) {
|
|
601
811
|
// skip
|
|
@@ -610,7 +820,7 @@ function hasEntityChanged(entity, entityProps, namesToIgnore) {
|
|
|
610
820
|
changed = hasValueChanged(entity.asAny[core_common_1.ECJsNames.toJsName(propertyName)], entityProps[core_common_1.ECJsNames.toJsName(propertyName)]);
|
|
611
821
|
}
|
|
612
822
|
}
|
|
613
|
-
}
|
|
823
|
+
}
|
|
614
824
|
return changed;
|
|
615
825
|
}
|
|
616
826
|
/** Returns true if the specified binary values are different. */
|
|
@@ -640,9 +850,21 @@ function isDefaultSubCategory(props) {
|
|
|
640
850
|
if (props.id === undefined)
|
|
641
851
|
return false;
|
|
642
852
|
if (!core_bentley_1.Id64.isId64(props.id))
|
|
643
|
-
|
|
853
|
+
core_bentley_1.ITwinError.throwError({
|
|
854
|
+
iTwinErrorId: {
|
|
855
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
856
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidSubCategory,
|
|
857
|
+
},
|
|
858
|
+
message: "subcategory had invalid id",
|
|
859
|
+
});
|
|
644
860
|
if (props.parent?.id === undefined)
|
|
645
|
-
|
|
861
|
+
core_bentley_1.ITwinError.throwError({
|
|
862
|
+
iTwinErrorId: {
|
|
863
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
864
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidSubCategory,
|
|
865
|
+
},
|
|
866
|
+
message: `subcategory with id ${props.id} had no parent`,
|
|
867
|
+
});
|
|
646
868
|
return props.id === core_backend_1.IModelDb.getDefaultSubCategoryId(props.parent.id);
|
|
647
869
|
}
|
|
648
870
|
//# sourceMappingURL=IModelImporter.js.map
|