@itwin/imodel-transformer 2.0.0-dev.6 → 2.0.0-dev.61
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 -34
- 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 -3
- package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
- package/lib/cjs/ECReferenceTypesCache.js +61 -104
- 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 +87 -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 +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/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 +56 -32
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts +9 -3
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +66 -31
- 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 +893 -178
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +75 -30
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +292 -132
- 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 +130 -196
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +697 -1155
- 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 +177 -0
- package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
- package/lib/cjs/ProvenanceManager.js +719 -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 +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 +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/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/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
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.TemplateModelCloner = exports.IModelTransformer = void 0;
|
|
4
37
|
/*---------------------------------------------------------------------------------------------
|
|
@@ -8,10 +41,11 @@ exports.TemplateModelCloner = exports.IModelTransformer = void 0;
|
|
|
8
41
|
/** @packageDocumentation
|
|
9
42
|
* @module iModels
|
|
10
43
|
*/
|
|
11
|
-
const path = require("path");
|
|
12
|
-
const Semver = require("semver");
|
|
13
|
-
const
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const Semver = __importStar(require("semver"));
|
|
46
|
+
const node_assert_1 = require("node:assert");
|
|
14
47
|
const core_bentley_1 = require("@itwin/core-bentley");
|
|
48
|
+
const ECSchemaMetaData = __importStar(require("@itwin/ecschema-metadata"));
|
|
15
49
|
const core_geometry_1 = require("@itwin/core-geometry");
|
|
16
50
|
const core_backend_1 = require("@itwin/core-backend");
|
|
17
51
|
const core_common_1 = require("@itwin/core-common");
|
|
@@ -19,15 +53,14 @@ const IModelExporter_1 = require("./IModelExporter");
|
|
|
19
53
|
const IModelImporter_1 = require("./IModelImporter");
|
|
20
54
|
const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
|
|
21
55
|
const IModelCloneContext_1 = require("./IModelCloneContext");
|
|
22
|
-
const EntityUnifier_1 = require("./EntityUnifier");
|
|
23
56
|
const Algo_1 = require("./Algo");
|
|
57
|
+
const SyncTypeResolver_1 = require("./SyncTypeResolver");
|
|
58
|
+
const ProvenanceManager_1 = require("./ProvenanceManager");
|
|
59
|
+
const SchemaProcessingStrategy_1 = require("./schema-processing/SchemaProcessingStrategy");
|
|
60
|
+
const SchemaProcessingCoordinator_1 = require("./schema-processing/SchemaProcessingCoordinator");
|
|
61
|
+
const ChangesetScanner_1 = require("./ChangesetScanner");
|
|
62
|
+
const IModelTransformerError_1 = require("./IModelTransformerError");
|
|
24
63
|
const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelTransformer;
|
|
25
|
-
const nullLastProvenanceEntityInfo = {
|
|
26
|
-
entityId: core_bentley_1.Id64.invalid,
|
|
27
|
-
aspectId: core_bentley_1.Id64.invalid,
|
|
28
|
-
aspectVersion: "",
|
|
29
|
-
aspectKind: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
30
|
-
};
|
|
31
64
|
/**
|
|
32
65
|
* Apply a function to each Id64 in a supported container type of Id64s.
|
|
33
66
|
* Currently only supports raw Id64String or RelatedElement-like objects containing an `id` property that is a Id64String,
|
|
@@ -53,10 +86,16 @@ function mapId64(idContainer, func) {
|
|
|
53
86
|
results.push(func(idContainer.id));
|
|
54
87
|
}
|
|
55
88
|
else {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
core_bentley_1.ITwinError.throwError({
|
|
90
|
+
iTwinErrorId: {
|
|
91
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
92
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidEntityReference,
|
|
93
|
+
},
|
|
94
|
+
message: [
|
|
95
|
+
`Id64 container '${JSON.stringify(idContainer)}' is unsupported.`,
|
|
96
|
+
"Currently only singular Id64 strings or prop-like objects containing an 'id' property are supported.",
|
|
97
|
+
].join("\n"),
|
|
98
|
+
});
|
|
60
99
|
}
|
|
61
100
|
return results;
|
|
62
101
|
}
|
|
@@ -67,6 +106,7 @@ function mapId64(idContainer, func) {
|
|
|
67
106
|
class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
68
107
|
/** The IModelExporter that will export from the source iModel. */
|
|
69
108
|
exporter;
|
|
109
|
+
_schemaProcessingCoordinator;
|
|
70
110
|
/** The IModelImporter that will import into the target iModel. */
|
|
71
111
|
importer;
|
|
72
112
|
/** The normally read-only source iModel.
|
|
@@ -75,8 +115,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
75
115
|
sourceDb;
|
|
76
116
|
/** The read/write target iModel. */
|
|
77
117
|
targetDb;
|
|
78
|
-
/** The
|
|
79
|
-
context
|
|
118
|
+
/** The supported transformation context for this transformer. */
|
|
119
|
+
get context() {
|
|
120
|
+
return this._cloneContext;
|
|
121
|
+
}
|
|
122
|
+
/** The clone context used by the transformer implementation. */
|
|
123
|
+
_cloneContext;
|
|
80
124
|
/** The transform to be applied to the placement of spatial elements
|
|
81
125
|
* This transform should be applied when:
|
|
82
126
|
* - source and target db have different ECEF locations
|
|
@@ -86,7 +130,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
86
130
|
* @beta
|
|
87
131
|
*/
|
|
88
132
|
_linearSpatialTransform;
|
|
89
|
-
|
|
133
|
+
/** The [[EditTxn]] used for write operations on the target iModel.
|
|
134
|
+
* The caller is responsible for starting, committing or abandoning this transaction.
|
|
135
|
+
* @beta
|
|
136
|
+
*/
|
|
137
|
+
_targetEditTxn;
|
|
138
|
+
/** The optional [[EditTxn]] used for write operations on the source iModel during reverse synchronization.
|
|
139
|
+
* @beta
|
|
140
|
+
*/
|
|
141
|
+
_sourceEditTxn;
|
|
142
|
+
_syncTypeResolver;
|
|
143
|
+
_provenanceManager;
|
|
90
144
|
/** The Id of the Element in the **target** iModel that represents the **source** repository as a whole and scopes its [ExternalSourceAspect]($backend) instances. */
|
|
91
145
|
get targetScopeElementId() {
|
|
92
146
|
return this._options.targetScopeElementId;
|
|
@@ -95,138 +149,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
95
149
|
_elementsWithExplicitlyTrackedProvenance = new Set();
|
|
96
150
|
_partiallyCommittedElementIds = new Set();
|
|
97
151
|
_partiallyCommittedAspectIds = new Set();
|
|
98
|
-
/** the options that were used to initialize this transformer */
|
|
99
|
-
_options;
|
|
100
152
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
153
|
+
* Tracks target element IDs that were remapped by Code during the current
|
|
154
|
+
* transformation pass. Used to prevent deletion of target elements that have been remapped
|
|
155
|
+
* to a new source element in the same pass (e.g., when a source element is deleted and a
|
|
156
|
+
* new one with the same properties is added, causing a remap to the same target).
|
|
105
157
|
*/
|
|
106
|
-
|
|
107
|
-
static noEsaSyncDirectionErrorMessage = "Couldn't find an external source aspect to determine sync direction. This often means that the master->branch relationship has not been established. Consider running the transformer with wasSourceIModelCopiedToTarget set to true.";
|
|
108
|
-
/**
|
|
109
|
-
* Queries for an esa which matches the props in the provided aspectProps.
|
|
110
|
-
* @param dbToQuery db to run the query on for scope external source
|
|
111
|
-
* @param aspectProps aspectProps to search for @see ExternalSourceAspectProps
|
|
112
|
-
*/
|
|
113
|
-
static queryScopeExternalSourceAspect(dbToQuery, aspectProps) {
|
|
114
|
-
const sql = `
|
|
115
|
-
SELECT ECInstanceId, Version, JsonProperties
|
|
116
|
-
FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
117
|
-
WHERE Element.Id=:elementId
|
|
118
|
-
AND Scope.Id=:scopeId
|
|
119
|
-
AND Kind=:kind
|
|
120
|
-
AND Identifier=:identifier
|
|
121
|
-
LIMIT 1
|
|
122
|
-
`;
|
|
123
|
-
// if (aspectProps.scope === undefined) return undefined;
|
|
124
|
-
// const params = new QueryBinder().bindId("elementId", aspectProps.element.id);
|
|
125
|
-
// params.bindId("scopeId", aspectProps.scope.id);
|
|
126
|
-
// params.bindString("kind", aspectProps.kind);
|
|
127
|
-
// params.bindString("identifier", aspectProps.identifier);
|
|
128
|
-
// const result = await dbToQuery.createQueryReader(sql);
|
|
129
|
-
// if (result) {
|
|
130
|
-
// const aspectId = result.current.id;
|
|
131
|
-
// const versionValue = result.current[1];
|
|
132
|
-
// const version = versionValue.isNull
|
|
133
|
-
// ? undefined
|
|
134
|
-
// : versionValue.getString();
|
|
135
|
-
// const jsonPropsValue = result.current[2];
|
|
136
|
-
// const jsonProperties = jsonPropsValue.isNull
|
|
137
|
-
// ? undefined
|
|
138
|
-
// : jsonPropsValue.getString();
|
|
139
|
-
// return { aspectId, version, jsonProperties };
|
|
140
|
-
// }
|
|
141
|
-
// else
|
|
142
|
-
// return undefined;
|
|
143
|
-
// eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
|
|
144
|
-
return dbToQuery.withPreparedStatement(sql, (statement) => {
|
|
145
|
-
statement.bindId("elementId", aspectProps.element.id);
|
|
146
|
-
if (aspectProps.scope === undefined)
|
|
147
|
-
return undefined; // return instead of binding an invalid id
|
|
148
|
-
statement.bindId("scopeId", aspectProps.scope.id);
|
|
149
|
-
statement.bindString("kind", aspectProps.kind);
|
|
150
|
-
statement.bindString("identifier", aspectProps.identifier);
|
|
151
|
-
if (core_bentley_1.DbResult.BE_SQLITE_ROW !== statement.step())
|
|
152
|
-
return undefined;
|
|
153
|
-
const aspectId = statement.getValue(0).getId();
|
|
154
|
-
const versionValue = statement.getValue(1);
|
|
155
|
-
const version = versionValue.isNull
|
|
156
|
-
? undefined
|
|
157
|
-
: versionValue.getString();
|
|
158
|
-
const jsonPropsValue = statement.getValue(2);
|
|
159
|
-
const jsonProperties = jsonPropsValue.isNull
|
|
160
|
-
? undefined
|
|
161
|
-
: jsonPropsValue.getString();
|
|
162
|
-
return { aspectId, version, jsonProperties };
|
|
163
|
-
});
|
|
164
|
-
}
|
|
158
|
+
_targetElementIdsRemappedByCode = new Set();
|
|
165
159
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* @throws if no scoping ESA can be found in either the sourceDb or targetDb which describes a master branch relationship between the two databases.
|
|
170
|
-
* @returns "forward" or "reverse"
|
|
160
|
+
* Tracks target model IDs that were imported (inserted or updated) during the current
|
|
161
|
+
* transformation pass. Used to prevent deletion of target models that have been recreated
|
|
162
|
+
* in the same pass (e.g. when a partition element is recreated with a new model).
|
|
171
163
|
*/
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
const aspectProps = {
|
|
176
|
-
id: undefined,
|
|
177
|
-
version: undefined,
|
|
178
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
179
|
-
element: {
|
|
180
|
-
id: targetScopeElementId,
|
|
181
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
182
|
-
},
|
|
183
|
-
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
184
|
-
identifier: sourceDb.iModelId,
|
|
185
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
186
|
-
jsonProperties: undefined,
|
|
187
|
-
};
|
|
188
|
-
/** First check if the targetDb is the branch (branch is the @see provenanceDb) */
|
|
189
|
-
const esaPropsFromTargetDb = this.queryScopeExternalSourceAspect(targetDb, aspectProps);
|
|
190
|
-
if (esaPropsFromTargetDb !== undefined) {
|
|
191
|
-
return "forward"; // we found an esa assuming targetDb is the provenanceDb/branch so this is a forward sync.
|
|
192
|
-
}
|
|
193
|
-
// Now check if the sourceDb is the branch
|
|
194
|
-
aspectProps.identifier = targetDb.iModelId;
|
|
195
|
-
const esaPropsFromSourceDb = this.queryScopeExternalSourceAspect(sourceDb, aspectProps);
|
|
196
|
-
if (esaPropsFromSourceDb !== undefined) {
|
|
197
|
-
return "reverse"; // we found an esa assuming sourceDb is the provenanceDb/branch so this is a reverse sync.
|
|
198
|
-
}
|
|
199
|
-
throw new Error(this.noEsaSyncDirectionErrorMessage);
|
|
200
|
-
}
|
|
201
|
-
determineSyncType() {
|
|
202
|
-
if (this._isProvenanceInitTransform) {
|
|
203
|
-
return "forward";
|
|
204
|
-
}
|
|
205
|
-
if (!this._options.argsForProcessChanges) {
|
|
206
|
-
return "not-sync";
|
|
207
|
-
}
|
|
208
|
-
try {
|
|
209
|
-
return IModelTransformer.determineSyncType(this.sourceDb, this.targetDb, this.targetScopeElementId);
|
|
210
|
-
}
|
|
211
|
-
catch (err) {
|
|
212
|
-
if (err instanceof Error &&
|
|
213
|
-
err.message === IModelTransformer.noEsaSyncDirectionErrorMessage &&
|
|
214
|
-
this._allowNoScopingESA) {
|
|
215
|
-
return "forward";
|
|
216
|
-
}
|
|
217
|
-
throw err;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
get isReverseSynchronization() {
|
|
221
|
-
if (this._syncType === undefined)
|
|
222
|
-
this._syncType = this.determineSyncType();
|
|
223
|
-
return this._syncType === "reverse";
|
|
224
|
-
}
|
|
225
|
-
get isForwardSynchronization() {
|
|
226
|
-
if (this._syncType === undefined)
|
|
227
|
-
this._syncType = this.determineSyncType();
|
|
228
|
-
return this._syncType === "forward";
|
|
229
|
-
}
|
|
164
|
+
_targetModelsImportedInCurrentTransform = new Set();
|
|
165
|
+
/** the options that were used to initialize this transformer */
|
|
166
|
+
_options;
|
|
230
167
|
_changesetRanges = undefined;
|
|
231
168
|
/**
|
|
232
169
|
* Set if the transformer is being used to perform the provenance initialization step of a fork initialization.
|
|
@@ -235,24 +172,19 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
235
172
|
_isProvenanceInitTransform;
|
|
236
173
|
/** The element classes that are considered to define provenance in the iModel */
|
|
237
174
|
static get provenanceElementClasses() {
|
|
238
|
-
return
|
|
239
|
-
core_backend_1.FolderLink,
|
|
240
|
-
core_backend_1.SynchronizationConfigLink,
|
|
241
|
-
core_backend_1.ExternalSource,
|
|
242
|
-
core_backend_1.ExternalSourceAttachment,
|
|
243
|
-
];
|
|
175
|
+
return ProvenanceManager_1.ProvenanceManager.provenanceElementClasses;
|
|
244
176
|
}
|
|
245
177
|
/** The element aspect classes that are considered to define provenance in the iModel */
|
|
246
178
|
static get provenanceElementAspectClasses() {
|
|
247
|
-
return
|
|
179
|
+
return ProvenanceManager_1.ProvenanceManager.provenanceElementAspectClasses;
|
|
248
180
|
}
|
|
249
181
|
/** Construct a new IModelTransformer
|
|
250
|
-
* @param
|
|
251
|
-
* @param target Specifies the target IModelImporter or the target IModelDb that will be used to construct the target IModelImporter.
|
|
182
|
+
* @param args The required arguments including source and target.
|
|
252
183
|
* @param options The options that specify how the transformation should be done.
|
|
253
184
|
*/
|
|
254
|
-
constructor(
|
|
185
|
+
constructor(args, options) {
|
|
255
186
|
super();
|
|
187
|
+
const { source, target } = args;
|
|
256
188
|
// initialize IModelTransformOptions
|
|
257
189
|
this._options = {
|
|
258
190
|
...options,
|
|
@@ -290,7 +222,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
290
222
|
this.exporter.excludeElementAspectClass(core_backend_1.ChannelRootAspect.classFullName); // Channel boundaries within the source iModel are not relevant to the target iModel
|
|
291
223
|
this.exporter.excludeElementAspectClass("BisCore:TextAnnotationData"); // This ElementAspect is auto-created by the BisCore:TextAnnotation2d/3d element handlers
|
|
292
224
|
// initialize importer and targetDb
|
|
293
|
-
if (target instanceof core_backend_1.
|
|
225
|
+
if (target instanceof core_backend_1.EditTxn) {
|
|
294
226
|
this.importer = new IModelImporter_1.IModelImporter(target, {
|
|
295
227
|
preserveElementIdsForFiltering: this._options.preserveElementIdsForFiltering,
|
|
296
228
|
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
@@ -301,23 +233,22 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
301
233
|
this.validateSharedOptionsMatch();
|
|
302
234
|
}
|
|
303
235
|
this.targetDb = this.importer.targetDb;
|
|
236
|
+
this._targetEditTxn = this.importer.editTxn;
|
|
237
|
+
this._sourceEditTxn = options?.sourceEditTxn;
|
|
238
|
+
this._schemaProcessingCoordinator = new SchemaProcessingCoordinator_1.SchemaProcessingCoordinator({
|
|
239
|
+
exporter: this.exporter,
|
|
240
|
+
targetDb: this.targetDb,
|
|
241
|
+
getSchemaExportDirectory: () => this._schemaExportDir,
|
|
242
|
+
shouldExportSchema: async (schemaKey) => this.shouldExportSchema(schemaKey),
|
|
243
|
+
serializeSourceSchema: async (schema) => this.onExportSchema(schema),
|
|
244
|
+
});
|
|
245
|
+
this.exporter.elementAspectExportCoordinator.setPreparation(async (excludedClasses, elementIds) => this.prepareElementAspects(excludedClasses, elementIds));
|
|
304
246
|
// create the IModelCloneContext, it must be initialized later
|
|
305
|
-
this.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
target: this.targetDb.changeset.index,
|
|
311
|
-
source: this.sourceDb.changeset.index,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
// this internal is guaranteed stable for just transformer usage
|
|
315
|
-
/* eslint-disable @itwin/no-internal */
|
|
316
|
-
if (("codeValueBehavior" in this.sourceDb)) {
|
|
317
|
-
this.sourceDb.codeValueBehavior = "exact";
|
|
318
|
-
this.targetDb.codeValueBehavior = "exact";
|
|
319
|
-
}
|
|
320
|
-
/* eslint-enable @itwin/no-internal */
|
|
247
|
+
this._cloneContext = new IModelCloneContext_1.IModelCloneContext(this.sourceDb, this.targetDb);
|
|
248
|
+
this.importer.registerEntityExistenceCache(this._cloneContext.existenceCache);
|
|
249
|
+
this.setCodeValueBehavior("exact");
|
|
250
|
+
this._syncTypeResolver = new SyncTypeResolver_1.SyncTypeResolver(this.sourceDb, this.targetDb, this._options.targetScopeElementId, !!this._isProvenanceInitTransform, !!this._options.argsForProcessChanges);
|
|
251
|
+
this._provenanceManager = new ProvenanceManager_1.ProvenanceManager(this._options.targetScopeElementId, this._options, this._cloneContext, this._syncTypeResolver, this._targetEditTxn, this._sourceEditTxn);
|
|
321
252
|
if (this._options.tryAlignGeolocation) {
|
|
322
253
|
if (this.sourceDb.geographicCoordinateSystem ||
|
|
323
254
|
this.targetDb.geographicCoordinateSystem) {
|
|
@@ -339,19 +270,31 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
339
270
|
validateSharedOptionsMatch() {
|
|
340
271
|
if (Boolean(this._options.preserveElementIdsForFiltering) !==
|
|
341
272
|
this.importer.options.preserveElementIdsForFiltering) {
|
|
342
|
-
|
|
343
|
-
|
|
273
|
+
core_bentley_1.ITwinError.throwError({
|
|
274
|
+
iTwinErrorId: {
|
|
275
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
276
|
+
key: IModelTransformerError_1.IModelTransformerError.ImporterOptionMismatch,
|
|
277
|
+
},
|
|
278
|
+
message: "A custom importer was passed as a target but its 'preserveElementIdsForFiltering' option is out of sync with the transformer's option.",
|
|
279
|
+
});
|
|
344
280
|
}
|
|
345
281
|
if (Boolean(this._options.skipPropagateChangesToRootElements) !==
|
|
346
282
|
this.importer.options.skipPropagateChangesToRootElements) {
|
|
347
|
-
|
|
348
|
-
|
|
283
|
+
core_bentley_1.ITwinError.throwError({
|
|
284
|
+
iTwinErrorId: {
|
|
285
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
286
|
+
key: IModelTransformerError_1.IModelTransformerError.ImporterOptionMismatch,
|
|
287
|
+
},
|
|
288
|
+
message: "A custom importer was passed as a target but its 'skipPropagateChangesToRootElements' option is out of sync with the transformer's option.",
|
|
289
|
+
});
|
|
349
290
|
}
|
|
350
291
|
}
|
|
351
292
|
/** Dispose any native resources associated with this IModelTransformer. */
|
|
352
293
|
dispose() {
|
|
353
294
|
core_bentley_1.Logger.logTrace(loggerCategory, "dispose()");
|
|
354
|
-
this.
|
|
295
|
+
this.importer.unregisterEntityExistenceCache(this._cloneContext.existenceCache);
|
|
296
|
+
this._cloneContext.existenceCache.clear();
|
|
297
|
+
this._cloneContext[Symbol.dispose]();
|
|
355
298
|
}
|
|
356
299
|
/** Log current settings that affect IModelTransformer's behavior. */
|
|
357
300
|
logSettings() {
|
|
@@ -368,75 +311,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
368
311
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
369
312
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
370
313
|
}
|
|
371
|
-
/** Return the IModelDb where IModelTransformer will store its provenance.
|
|
372
|
-
* @note This will be [[targetDb]] except when it is a reverse synchronization. In that case it be [[sourceDb]].
|
|
373
|
-
*/
|
|
374
|
-
get provenanceDb() {
|
|
375
|
-
return this.isReverseSynchronization ? this.sourceDb : this.targetDb;
|
|
376
|
-
}
|
|
377
|
-
/** Return the IModelDb where IModelTransformer looks for entities referred to by stored provenance.
|
|
378
|
-
* @note This will be [[sourceDb]] except when it is a reverse synchronization. In that case it be [[targetDb]].
|
|
379
|
-
*/
|
|
380
|
-
get provenanceSourceDb() {
|
|
381
|
-
return this.isReverseSynchronization ? this.targetDb : this.sourceDb;
|
|
382
|
-
}
|
|
383
|
-
/** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
|
|
384
|
-
static initElementProvenanceOptions(sourceElementId, targetElementId, args) {
|
|
385
|
-
const elementId = args.isReverseSynchronization
|
|
386
|
-
? sourceElementId
|
|
387
|
-
: targetElementId;
|
|
388
|
-
const version = args.isReverseSynchronization
|
|
389
|
-
? args.targetDb.elements.queryLastModifiedTime(targetElementId)
|
|
390
|
-
: args.sourceDb.elements.queryLastModifiedTime(sourceElementId);
|
|
391
|
-
const aspectIdentifier = args.isReverseSynchronization
|
|
392
|
-
? targetElementId
|
|
393
|
-
: sourceElementId;
|
|
394
|
-
const aspectProps = {
|
|
395
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
396
|
-
element: {
|
|
397
|
-
id: elementId,
|
|
398
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
399
|
-
},
|
|
400
|
-
scope: { id: args.targetScopeElementId },
|
|
401
|
-
identifier: aspectIdentifier,
|
|
402
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
403
|
-
version,
|
|
404
|
-
};
|
|
405
|
-
return aspectProps;
|
|
406
|
-
}
|
|
407
|
-
static async initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, args) {
|
|
408
|
-
const provenanceDb = args.isReverseSynchronization
|
|
409
|
-
? args.sourceDb
|
|
410
|
-
: args.targetDb;
|
|
411
|
-
const aspectIdentifier = args.isReverseSynchronization
|
|
412
|
-
? targetRelInstanceId
|
|
413
|
-
: sourceRelInstanceId;
|
|
414
|
-
const provenanceRelInstanceId = args.isReverseSynchronization
|
|
415
|
-
? sourceRelInstanceId
|
|
416
|
-
: targetRelInstanceId;
|
|
417
|
-
const sql = "SELECT SourceECInstanceId FROM bis.ElementRefersToElements WHERE ECInstanceId=?";
|
|
418
|
-
const params = new core_common_1.QueryBinder().bindId(1, provenanceRelInstanceId);
|
|
419
|
-
const reader = provenanceDb.createQueryReader(sql, params, {
|
|
420
|
-
usePrimaryConn: true,
|
|
421
|
-
});
|
|
422
|
-
nodeAssert(await reader.step(), "relationship provenance query returned no rows");
|
|
423
|
-
const elementId = reader.current[0];
|
|
424
|
-
const jsonProperties = args.forceOldRelationshipProvenanceMethod
|
|
425
|
-
? { targetRelInstanceId }
|
|
426
|
-
: { provenanceRelInstanceId };
|
|
427
|
-
const aspectProps = {
|
|
428
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
429
|
-
element: {
|
|
430
|
-
id: elementId,
|
|
431
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
432
|
-
},
|
|
433
|
-
scope: { id: args.targetScopeElementId },
|
|
434
|
-
identifier: aspectIdentifier,
|
|
435
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
|
|
436
|
-
jsonProperties: JSON.stringify(jsonProperties),
|
|
437
|
-
};
|
|
438
|
-
return aspectProps;
|
|
439
|
-
}
|
|
440
314
|
/**
|
|
441
315
|
* Previously the transformer would insert provenance always pointing to the "target" relationship.
|
|
442
316
|
* It should (and now by default does) instead insert provenance pointing to the provenanceSource
|
|
@@ -445,43 +319,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
445
319
|
*/
|
|
446
320
|
_forceOldRelationshipProvenanceMethod = false;
|
|
447
321
|
/** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
|
|
448
|
-
initElementProvenance(sourceElementId, targetElementId) {
|
|
449
|
-
return
|
|
450
|
-
isReverseSynchronization: this.isReverseSynchronization,
|
|
451
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
452
|
-
sourceDb: this.sourceDb,
|
|
453
|
-
targetDb: this.targetDb,
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
/** Create an ExternalSourceAspectProps in a standard way for a Relationship in an iModel --> iModel transformations.
|
|
457
|
-
* The ExternalSourceAspect is meant to be owned by the Element in the target iModel that is the `sourceId` of transformed relationship.
|
|
458
|
-
* The `identifier` property of the ExternalSourceAspect will be the ECInstanceId of the relationship in the master iModel.
|
|
459
|
-
* The ECInstanceId of the relationship in the branch iModel will be stored in the JsonProperties of the ExternalSourceAspect.
|
|
460
|
-
*/
|
|
461
|
-
async initRelationshipProvenance(sourceRelationship, targetRelInstanceId) {
|
|
462
|
-
return await IModelTransformer.initRelationshipProvenanceOptions(sourceRelationship.id, targetRelInstanceId, {
|
|
463
|
-
sourceDb: this.sourceDb,
|
|
464
|
-
targetDb: this.targetDb,
|
|
465
|
-
isReverseSynchronization: this.isReverseSynchronization,
|
|
466
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
467
|
-
forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
/** NOTE: the json properties must be converted to string before insertion */
|
|
471
|
-
_targetScopeProvenanceProps = undefined;
|
|
472
|
-
/**
|
|
473
|
-
* Index of the changeset that the transformer was at when the transformation begins (was constructed).
|
|
474
|
-
* Used to determine at the end which changesets were part of a synchronization.
|
|
475
|
-
*/
|
|
476
|
-
_startingChangesetIndices = undefined;
|
|
477
|
-
_cachedSynchronizationVersion = undefined;
|
|
478
|
-
/**
|
|
479
|
-
* We cache the synchronization version to avoid querying the target scoping ESA multiple times.
|
|
480
|
-
* If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
|
|
481
|
-
* Sets this._cachedSynchronizationVersion to undefined.
|
|
482
|
-
*/
|
|
483
|
-
clearCachedSynchronizationVersion() {
|
|
484
|
-
this._cachedSynchronizationVersion = undefined;
|
|
322
|
+
async initElementProvenance(sourceElementId, targetElementId) {
|
|
323
|
+
return this._provenanceManager.initElementProvenance(sourceElementId, targetElementId);
|
|
485
324
|
}
|
|
486
325
|
/** the changeset in the scoping element's source version found for this transformation
|
|
487
326
|
* @note the version depends on whether this is a reverse synchronization or not, as
|
|
@@ -490,436 +329,59 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
490
329
|
* @note empty string and -1 for changeset and index if it was transformed before federation guid update (pre 1.x) and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] === "unsafe-migrate".
|
|
491
330
|
* @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
|
|
492
331
|
*/
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const provenanceScopeAspect = this.tryGetProvenanceScopeAspect();
|
|
496
|
-
if (!provenanceScopeAspect) {
|
|
497
|
-
return { index: -1, id: "" }; // first synchronization.
|
|
498
|
-
}
|
|
499
|
-
const version = this.isReverseSynchronization
|
|
500
|
-
? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
|
|
501
|
-
: provenanceScopeAspect.version;
|
|
502
|
-
if (!version &&
|
|
503
|
-
this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
|
|
504
|
-
return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
|
|
505
|
-
}
|
|
506
|
-
if (version === undefined) {
|
|
507
|
-
throw new Error(`Could not find synchronization version in scope aspect. This may be due to the last successful run of the transformer being done with an older version.
|
|
508
|
-
Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
|
|
509
|
-
}
|
|
510
|
-
const [id, index] = version === "" ? ["", -1] : version.split(";");
|
|
511
|
-
if (Number.isNaN(Number(index)))
|
|
512
|
-
throw new Error("Could not parse version data from scope aspect");
|
|
513
|
-
this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
|
|
514
|
-
}
|
|
515
|
-
return this._cachedSynchronizationVersion;
|
|
332
|
+
async getSynchronizationVersion() {
|
|
333
|
+
return this._provenanceManager.getSynchronizationVersion();
|
|
516
334
|
}
|
|
517
335
|
/**
|
|
518
336
|
* @returns provenance scope aspect if it exists in the provenanceDb.
|
|
519
337
|
* Provenance scope aspect is created and inserted into provenanceDb when [[initScopeProvenance]] is invoked.
|
|
520
338
|
*/
|
|
521
|
-
tryGetProvenanceScopeAspect() {
|
|
522
|
-
|
|
523
|
-
id: undefined,
|
|
524
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
525
|
-
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
526
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
527
|
-
element: { id: this.targetScopeElementId ?? core_common_1.IModel.rootSubjectId },
|
|
528
|
-
identifier: this.provenanceSourceDb.iModelId,
|
|
529
|
-
});
|
|
530
|
-
return scopeProvenanceAspectProps !== undefined
|
|
531
|
-
? this.provenanceDb.elements.getAspect(scopeProvenanceAspectProps.aspectId)
|
|
532
|
-
: undefined;
|
|
339
|
+
async tryGetProvenanceScopeAspect() {
|
|
340
|
+
return this._provenanceManager.tryGetProvenanceScopeAspect();
|
|
533
341
|
}
|
|
534
342
|
/**
|
|
535
343
|
* Make sure there are no conflicting other scope-type external source aspects on the *target scope element*,
|
|
536
344
|
* If there are none at all, insert one, then this must be a first synchronization.
|
|
537
|
-
* @returns the last synced version (changesetId) on the target scope's external source aspect,
|
|
538
|
-
* if this was a [BriefcaseDb]($backend)
|
|
539
345
|
*/
|
|
540
346
|
async initScopeProvenance() {
|
|
541
|
-
|
|
542
|
-
id: undefined,
|
|
543
|
-
version: undefined,
|
|
544
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
545
|
-
element: {
|
|
546
|
-
id: this.targetScopeElementId,
|
|
547
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
548
|
-
},
|
|
549
|
-
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
550
|
-
identifier: this.provenanceSourceDb.iModelId,
|
|
551
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
552
|
-
jsonProperties: undefined,
|
|
553
|
-
};
|
|
554
|
-
const foundEsaProps = IModelTransformer.queryScopeExternalSourceAspect(this.provenanceDb, aspectProps); // this query includes "identifier"
|
|
555
|
-
if (foundEsaProps === undefined) {
|
|
556
|
-
aspectProps.version = ""; // empty since never before transformed. Will be updated in [[finalizeTransformation]]
|
|
557
|
-
aspectProps.jsonProperties = {
|
|
558
|
-
pendingReverseSyncChangesetIndices: [],
|
|
559
|
-
pendingSyncChangesetIndices: [],
|
|
560
|
-
reverseSyncVersion: "", // empty since never before transformed. Will be updated in first reverse sync
|
|
561
|
-
};
|
|
562
|
-
// this query does not include "identifier" to find possible conflicts
|
|
563
|
-
const sql = `
|
|
564
|
-
SELECT ECInstanceId
|
|
565
|
-
FROM ${core_backend_1.ExternalSourceAspect.classFullName}
|
|
566
|
-
WHERE Element.Id=:elementId
|
|
567
|
-
AND Scope.Id=:scopeId
|
|
568
|
-
AND Kind=:kind
|
|
569
|
-
LIMIT 1
|
|
570
|
-
`;
|
|
571
|
-
const params = new core_common_1.QueryBinder();
|
|
572
|
-
params.bindId("elementId", aspectProps.element.id);
|
|
573
|
-
params.bindId("scopeId", aspectProps.scope.id); // this scope.id can never be invalid, we create it above
|
|
574
|
-
params.bindString("kind", aspectProps.kind);
|
|
575
|
-
const reader = this.provenanceDb.createQueryReader(sql, params, {
|
|
576
|
-
usePrimaryConn: true,
|
|
577
|
-
});
|
|
578
|
-
const hasConflictingScope = await reader.step();
|
|
579
|
-
if (hasConflictingScope) {
|
|
580
|
-
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Provenance scope conflict");
|
|
581
|
-
}
|
|
582
|
-
if (!this._options.noProvenance) {
|
|
583
|
-
const id = this.provenanceDb.elements.insertAspect({
|
|
584
|
-
...aspectProps,
|
|
585
|
-
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
586
|
-
});
|
|
587
|
-
aspectProps.id = id;
|
|
588
|
-
// Busting a potential cached version
|
|
589
|
-
this.clearCachedSynchronizationVersion();
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
// foundEsaProps is defined.
|
|
594
|
-
aspectProps.id = foundEsaProps.aspectId;
|
|
595
|
-
aspectProps.version = foundEsaProps.version;
|
|
596
|
-
aspectProps.jsonProperties = foundEsaProps.jsonProperties
|
|
597
|
-
? JSON.parse(foundEsaProps.jsonProperties)
|
|
598
|
-
: undefined;
|
|
599
|
-
// Clone oldProps incase they're changed for logging purposes
|
|
600
|
-
const oldProps = JSON.parse(JSON.stringify(aspectProps));
|
|
601
|
-
if (this.handleUnsafeMigrate(aspectProps)) {
|
|
602
|
-
core_bentley_1.Logger.logInfo(loggerCategory, "Unsafe migrate made a change to the target scope's external source aspect. Updating aspect in database.", { oldProps, newProps: aspectProps });
|
|
603
|
-
this.provenanceDb.elements.updateAspect({
|
|
604
|
-
...aspectProps,
|
|
605
|
-
jsonProperties: JSON.stringify(aspectProps.jsonProperties),
|
|
606
|
-
});
|
|
607
|
-
// Busting a potential cached version
|
|
608
|
-
this.clearCachedSynchronizationVersion();
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
this._targetScopeProvenanceProps =
|
|
612
|
-
aspectProps;
|
|
613
|
-
}
|
|
614
|
-
/** Returns true if a change was made to the aspectProps. */
|
|
615
|
-
handleUnsafeMigrate(aspectProps) {
|
|
616
|
-
let madeChange = false;
|
|
617
|
-
if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
|
|
618
|
-
return madeChange;
|
|
619
|
-
const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
|
|
620
|
-
const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
|
|
621
|
-
"";
|
|
622
|
-
if (aspectProps.version === undefined ||
|
|
623
|
-
(aspectProps.version === "" &&
|
|
624
|
-
aspectProps.version !== fallbackSyncVersionToUse)) {
|
|
625
|
-
aspectProps.version = fallbackSyncVersionToUse;
|
|
626
|
-
madeChange = true;
|
|
627
|
-
}
|
|
628
|
-
if (aspectProps.jsonProperties === undefined) {
|
|
629
|
-
aspectProps.jsonProperties = {
|
|
630
|
-
pendingReverseSyncChangesetIndices: [],
|
|
631
|
-
pendingSyncChangesetIndices: [],
|
|
632
|
-
reverseSyncVersion: fallbackReverseSyncVersionToUse,
|
|
633
|
-
};
|
|
634
|
-
madeChange = true;
|
|
635
|
-
}
|
|
636
|
-
else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
|
|
637
|
-
(aspectProps.jsonProperties.reverseSyncVersion === "" &&
|
|
638
|
-
aspectProps.jsonProperties.reverseSyncVersion !==
|
|
639
|
-
fallbackReverseSyncVersionToUse)) {
|
|
640
|
-
aspectProps.jsonProperties.reverseSyncVersion =
|
|
641
|
-
fallbackReverseSyncVersionToUse;
|
|
642
|
-
madeChange = true;
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* This case will only be hit when:
|
|
646
|
-
* - first transformation was performed on pre-fedguid transformer.
|
|
647
|
-
* - a second processAll transformation was performed on the same target-source iModels post-fedguid transformer.
|
|
648
|
-
* - change processing was invoked on for the second 'initial' transformation.
|
|
649
|
-
* NOTE: This case likely does not exist anymore, but we will keep it just to be sure.
|
|
650
|
-
*/
|
|
651
|
-
if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
|
|
652
|
-
undefined) {
|
|
653
|
-
core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
|
|
654
|
-
aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
|
|
655
|
-
madeChange = true;
|
|
656
|
-
}
|
|
657
|
-
if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
|
|
658
|
-
core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
|
|
659
|
-
aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
|
|
660
|
-
madeChange = true;
|
|
661
|
-
}
|
|
662
|
-
return madeChange;
|
|
347
|
+
return this._provenanceManager.initScopeProvenance();
|
|
663
348
|
}
|
|
664
349
|
/**
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
* @note provenance is done by federation guids where possible
|
|
668
|
-
* @note this may execute on each element more than once! Only use in cases where that is handled
|
|
350
|
+
* Get the IModelDb where provenance (ExternalSourceAspects) is stored.
|
|
351
|
+
* This will be targetDb except when it is a reverse synchronization, in which case it will be sourceDb.
|
|
669
352
|
*/
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadSchema, "The BisCore schema version of the target database is too old");
|
|
675
|
-
}
|
|
676
|
-
const sourceDb = args.isReverseSynchronization
|
|
677
|
-
? args.provenanceDb
|
|
678
|
-
: args.provenanceSourceDb;
|
|
679
|
-
const targetDb = args.isReverseSynchronization
|
|
680
|
-
? args.provenanceSourceDb
|
|
681
|
-
: args.provenanceDb;
|
|
682
|
-
// query for provenanceDb
|
|
683
|
-
const elementIdByFedGuidQuery = `
|
|
684
|
-
SELECT e.ECInstanceId, FederationGuid
|
|
685
|
-
FROM bis.Element e
|
|
686
|
-
${args.skipPropagateChangesToRootElements
|
|
687
|
-
? "WHERE e.ECInstanceId NOT IN (0x1, 0xe, 0x10) -- special static elements"
|
|
688
|
-
: ""}
|
|
689
|
-
ORDER BY FederationGuid
|
|
690
|
-
`;
|
|
691
|
-
// iterate through sorted list of fed guids from both dbs to get the intersection
|
|
692
|
-
// NOTE: if we exposed the native attach database support,
|
|
693
|
-
// we could get the intersection of fed guids in one query, not sure if it would be faster
|
|
694
|
-
// OR we could do a raw sqlite query...
|
|
695
|
-
const sourceReader = sourceDb.createQueryReader(elementIdByFedGuidQuery);
|
|
696
|
-
const targetReader = targetDb.createQueryReader(elementIdByFedGuidQuery);
|
|
697
|
-
let hasSourceRow = await sourceReader.step();
|
|
698
|
-
let hasTargetRow = await targetReader.step();
|
|
699
|
-
while (hasSourceRow && hasTargetRow) {
|
|
700
|
-
const sourceFedGuid = sourceReader.current.federationGuid;
|
|
701
|
-
const targetFedGuid = targetReader.current.federationGuid;
|
|
702
|
-
if (sourceFedGuid !== undefined &&
|
|
703
|
-
targetFedGuid !== undefined &&
|
|
704
|
-
sourceFedGuid === targetFedGuid) {
|
|
705
|
-
// data flow direction is always sourceDb -> targetDb and it does not depend on where the explicit element provenance is stored
|
|
706
|
-
args.fn(sourceReader.current.id, targetReader.current.id);
|
|
707
|
-
}
|
|
708
|
-
if (targetFedGuid === undefined ||
|
|
709
|
-
(sourceFedGuid !== undefined && sourceFedGuid >= targetFedGuid)) {
|
|
710
|
-
hasTargetRow = await targetReader.step();
|
|
711
|
-
}
|
|
712
|
-
if (sourceFedGuid === undefined ||
|
|
713
|
-
(targetFedGuid !== undefined && sourceFedGuid <= targetFedGuid)) {
|
|
714
|
-
hasSourceRow = await sourceReader.step();
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
// query for provenanceDb
|
|
718
|
-
const provenanceAspectsQuery = `
|
|
719
|
-
SELECT esa.Identifier, Element.Id
|
|
720
|
-
FROM bis.ExternalSourceAspect esa
|
|
721
|
-
WHERE Scope.Id=:scopeId
|
|
722
|
-
AND Kind=:kind
|
|
723
|
-
`;
|
|
724
|
-
// Technically this will a second time call the function (as documented) on
|
|
725
|
-
// victims of the old provenance method that have both fedguids and an inserted aspect.
|
|
726
|
-
// But this is a private function with one known caller where that doesn't matter
|
|
727
|
-
const runFnInDataFlowDirection = (sourceId, targetId) => args.isReverseSynchronization
|
|
728
|
-
? args.fn(sourceId, targetId)
|
|
729
|
-
: args.fn(targetId, sourceId);
|
|
730
|
-
const params = new core_common_1.QueryBinder();
|
|
731
|
-
params.bindId("scopeId", args.targetScopeElementId);
|
|
732
|
-
params.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
733
|
-
const provenanceReader = args.provenanceDb.createQueryReader(provenanceAspectsQuery, params);
|
|
734
|
-
for await (const row of provenanceReader) {
|
|
735
|
-
// ExternalSourceAspect.Identifier is of type string
|
|
736
|
-
const aspectIdentifier = row[0];
|
|
737
|
-
const elementId = row.id;
|
|
738
|
-
runFnInDataFlowDirection(elementId, aspectIdentifier);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
async forEachTrackedElement(fn) {
|
|
742
|
-
return await IModelTransformer.forEachTrackedElement({
|
|
743
|
-
provenanceSourceDb: this.provenanceSourceDb,
|
|
744
|
-
provenanceDb: this.provenanceDb,
|
|
745
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
746
|
-
isReverseSynchronization: this.isReverseSynchronization,
|
|
747
|
-
fn,
|
|
748
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
|
|
749
|
-
});
|
|
353
|
+
async getProvenanceDb() {
|
|
354
|
+
return (await this.getIsReverseSynchronization())
|
|
355
|
+
? this.sourceDb
|
|
356
|
+
: this.targetDb;
|
|
750
357
|
}
|
|
751
358
|
/**
|
|
752
|
-
*
|
|
753
|
-
* The identifier on the ESA is the id of the element in the [[IModelTransformer.provenanceSourceDb]]
|
|
754
|
-
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
755
|
-
* @param entityInProvenanceSourceId
|
|
756
|
-
* @returns the elementId that the ESA is stored on, esa.Element.Id
|
|
359
|
+
* Get whether this is a reverse synchronization.
|
|
757
360
|
*/
|
|
758
|
-
async
|
|
759
|
-
|
|
760
|
-
const sql = `
|
|
761
|
-
SELECT esa.Element.Id
|
|
762
|
-
FROM Bis.ExternalSourceAspect esa
|
|
763
|
-
WHERE esa.Kind=?
|
|
764
|
-
AND esa.Scope.Id=?
|
|
765
|
-
AND esa.Identifier=?
|
|
766
|
-
`;
|
|
767
|
-
const params = new core_common_1.QueryBinder();
|
|
768
|
-
params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
769
|
-
params.bindId(2, this.targetScopeElementId);
|
|
770
|
-
params.bindString(3, entityInProvenanceSourceId);
|
|
771
|
-
const result = this.provenanceDb.createQueryReader(sql, params);
|
|
772
|
-
if (await result.step()) {
|
|
773
|
-
return result.current.id;
|
|
774
|
-
}
|
|
775
|
-
else
|
|
776
|
-
return undefined;
|
|
361
|
+
async getIsReverseSynchronization() {
|
|
362
|
+
return (await this._syncTypeResolver.getSyncType()) === "reverse";
|
|
777
363
|
}
|
|
778
364
|
/**
|
|
779
|
-
*
|
|
780
|
-
* The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
|
|
781
|
-
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
782
|
-
* @param entityInProvenanceSourceId
|
|
783
|
-
* @returns
|
|
365
|
+
* Get whether this is a forward synchronization.
|
|
784
366
|
*/
|
|
785
|
-
async
|
|
786
|
-
|
|
787
|
-
SELECT
|
|
788
|
-
ECInstanceId,
|
|
789
|
-
JSON_EXTRACT(JsonProperties, '$.provenanceRelInstanceId') AS provenanceRelInstId
|
|
790
|
-
FROM Bis.ExternalSourceAspect
|
|
791
|
-
WHERE Kind=?
|
|
792
|
-
AND Scope.Id=?
|
|
793
|
-
AND Identifier=?
|
|
794
|
-
`;
|
|
795
|
-
const params = new core_common_1.QueryBinder();
|
|
796
|
-
params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
797
|
-
params.bindId(2, this.targetScopeElementId);
|
|
798
|
-
params.bindString(3, entityInProvenanceSourceId);
|
|
799
|
-
const result = this.provenanceDb.createQueryReader(sql, params);
|
|
800
|
-
if (await result.step()) {
|
|
801
|
-
const aspectId = result.current.id;
|
|
802
|
-
const provenanceRelInstId = result.current.provenanceRelInstId;
|
|
803
|
-
const provenanceRelInstanceId = provenanceRelInstId !== undefined
|
|
804
|
-
? provenanceRelInstId
|
|
805
|
-
: await this._queryTargetRelId(sourceRelInfo);
|
|
806
|
-
return {
|
|
807
|
-
aspectId,
|
|
808
|
-
relationshipId: provenanceRelInstanceId,
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
else
|
|
812
|
-
return undefined;
|
|
813
|
-
}
|
|
814
|
-
async _queryTargetRelId(sourceRelInfo) {
|
|
815
|
-
const targetRelInfo = {
|
|
816
|
-
sourceId: this.context.findTargetElementId(sourceRelInfo.sourceId),
|
|
817
|
-
targetId: this.context.findTargetElementId(sourceRelInfo.targetId),
|
|
818
|
-
};
|
|
819
|
-
if (targetRelInfo.sourceId === undefined ||
|
|
820
|
-
targetRelInfo.targetId === undefined)
|
|
821
|
-
return undefined; // couldn't find an element, rel is invalid or deleted
|
|
822
|
-
const sql = `
|
|
823
|
-
select ecinstanceid
|
|
824
|
-
from bis.elementreferstoelements
|
|
825
|
-
where sourceecinstanceid=?
|
|
826
|
-
and targetecinstanceid=?
|
|
827
|
-
and ecclassid=?
|
|
828
|
-
`;
|
|
829
|
-
const params = new core_common_1.QueryBinder();
|
|
830
|
-
params.bindId(1, targetRelInfo.sourceId);
|
|
831
|
-
params.bindId(2, targetRelInfo.targetId);
|
|
832
|
-
params.bindId(3, await this._targetClassNameToClassId(sourceRelInfo.classFullName));
|
|
833
|
-
const result = this.targetDb.createQueryReader(sql, params);
|
|
834
|
-
if (await result.step())
|
|
835
|
-
return result.current.id;
|
|
836
|
-
else
|
|
837
|
-
return undefined;
|
|
367
|
+
async getIsForwardSynchronization() {
|
|
368
|
+
return (await this._syncTypeResolver.getSyncType()) === "forward";
|
|
838
369
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
848
|
-
// NOTE: this doesn't handle remapped element classes,
|
|
849
|
-
// but is only used for relationships rn
|
|
850
|
-
async _getRelClassId(db, classFullName) {
|
|
851
|
-
const sql = `
|
|
852
|
-
SELECT c.ECInstanceId
|
|
853
|
-
FROM ECDbMeta.ECClassDef c
|
|
854
|
-
JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
|
|
855
|
-
WHERE s.Name=? AND c.Name=?
|
|
856
|
-
`;
|
|
857
|
-
const params = new core_common_1.QueryBinder();
|
|
858
|
-
const [schemaName, className] = classFullName.indexOf(".") !== -1
|
|
859
|
-
? classFullName.split(".")
|
|
860
|
-
: classFullName.split(":");
|
|
861
|
-
params.bindString(1, schemaName);
|
|
862
|
-
params.bindString(2, className);
|
|
863
|
-
const result = db.createQueryReader(sql, params);
|
|
864
|
-
if (await result.step())
|
|
865
|
-
return result.current.id;
|
|
866
|
-
(0, core_bentley_1.assert)(false, "relationship was not found");
|
|
867
|
-
}
|
|
868
|
-
// Deprecate?
|
|
869
|
-
async _queryElemIdByFedGuid(db, fedGuid) {
|
|
870
|
-
const sql = "SELECT ECInstanceId FROM Bis.Element WHERE FederationGuid=:fedGuid";
|
|
871
|
-
const params = new core_common_1.QueryBinder().bindString("fedGuid", fedGuid);
|
|
872
|
-
const reader = db.createQueryReader(sql, params);
|
|
873
|
-
if (await reader.step()) {
|
|
874
|
-
return reader.current.ecinstanceid;
|
|
875
|
-
}
|
|
876
|
-
return undefined;
|
|
370
|
+
/**
|
|
371
|
+
* Updates the synchronization version on the scope ESA.
|
|
372
|
+
*/
|
|
373
|
+
async updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
|
|
374
|
+
return this._provenanceManager.updateSynchronizationVersion({
|
|
375
|
+
initializeReverseSyncVersion,
|
|
376
|
+
sourceChangeDataState: this._sourceChangeDataState,
|
|
377
|
+
});
|
|
877
378
|
}
|
|
878
379
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
879
380
|
* @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
|
|
880
381
|
* @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
|
|
881
382
|
*/
|
|
882
|
-
shouldDetectDeletes() {
|
|
883
|
-
|
|
884
|
-
return this._syncType === "not-sync";
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements
|
|
888
|
-
* in the source iModel.
|
|
889
|
-
* @deprecated in 1.x. Do not use this. // FIXME<MIKE>: how to better explain this?
|
|
890
|
-
* This method is only called during [[process]] when [[IModelTransformOptions.argsForProcessChanges]] is undefined and the option
|
|
891
|
-
* [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
|
|
892
|
-
* necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] defined, since changeset information is sufficient.
|
|
893
|
-
* @note you do not need to call this directly unless processing a subset of an iModel.
|
|
894
|
-
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
895
|
-
*/
|
|
896
|
-
async detectElementDeletes() {
|
|
897
|
-
const sql = `
|
|
898
|
-
SELECT Identifier, Element.Id
|
|
899
|
-
FROM BisCore.ExternalSourceAspect
|
|
900
|
-
WHERE Scope.Id=:scopeId
|
|
901
|
-
AND Kind=:kind
|
|
902
|
-
`;
|
|
903
|
-
nodeAssert(!this.isReverseSynchronization, "synchronizations with processChanges already detect element deletes, don't call detectElementDeletes");
|
|
904
|
-
// Reported issue: https://github.com/iTwin/itwinjs-core/issues/7989
|
|
905
|
-
// eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
|
|
906
|
-
this.provenanceDb.withPreparedStatement(sql, (stmt) => {
|
|
907
|
-
stmt.bindId("scopeId", this.targetScopeElementId);
|
|
908
|
-
stmt.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
909
|
-
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
910
|
-
// ExternalSourceAspect.Identifier is of type string
|
|
911
|
-
const aspectIdentifier = stmt.getValue(0).getString();
|
|
912
|
-
if (!core_bentley_1.Id64.isValidId64(aspectIdentifier)) {
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
const targetElemId = stmt.getValue(1).getId();
|
|
916
|
-
const wasDeletedInSource = !EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
|
|
917
|
-
entityReference: `e${aspectIdentifier}`,
|
|
918
|
-
});
|
|
919
|
-
if (wasDeletedInSource)
|
|
920
|
-
this.importer.deleteElement(targetElemId);
|
|
921
|
-
}
|
|
922
|
-
});
|
|
383
|
+
async shouldDetectDeletes() {
|
|
384
|
+
return (await this._syncTypeResolver.getSyncType()) === "not-sync";
|
|
923
385
|
}
|
|
924
386
|
/** Transform the specified sourceElement into ElementProps for the target iModel.
|
|
925
387
|
* @param sourceElement The Element from the source iModel to transform.
|
|
@@ -929,7 +391,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
929
391
|
*/
|
|
930
392
|
async onTransformElement(sourceElement) {
|
|
931
393
|
core_bentley_1.Logger.logTrace(loggerCategory, `onTransformElement(${sourceElement.id}) "${sourceElement.getDisplayLabel()}"`);
|
|
932
|
-
const targetElementProps = this.
|
|
394
|
+
const targetElementProps = await this._cloneContext.cloneElement(sourceElement, {
|
|
395
|
+
binaryGeometry: this._options.cloneUsingBinaryGeometry,
|
|
396
|
+
});
|
|
933
397
|
// Special case: source element is the root subject
|
|
934
398
|
if (sourceElement.id === core_common_1.IModel.rootSubjectId) {
|
|
935
399
|
const targetElementId = this.context.findTargetElementId(sourceElement.id);
|
|
@@ -972,7 +436,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
972
436
|
const srcEcefLoc = this.sourceDb.ecefLocation;
|
|
973
437
|
const targetEcefLoc = this.targetDb.ecefLocation;
|
|
974
438
|
if (srcEcefLoc === undefined || targetEcefLoc === undefined) {
|
|
975
|
-
|
|
439
|
+
core_bentley_1.ITwinError.throwError({
|
|
440
|
+
iTwinErrorId: {
|
|
441
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
442
|
+
key: IModelTransformerError_1.IModelTransformerError.GeolocationUnavailable,
|
|
443
|
+
},
|
|
444
|
+
message: "Both source and target ECEF locations must be defined to calculate the transform.",
|
|
445
|
+
});
|
|
976
446
|
}
|
|
977
447
|
if (srcEcefLoc.getTransform().isAlmostEqual(targetEcefLoc.getTransform())) {
|
|
978
448
|
core_bentley_1.Logger.logTrace(loggerCategory, "ECEF data is already aligned. No spatial transforms needed.");
|
|
@@ -981,7 +451,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
981
451
|
const srcSpatialToECEF = srcEcefLoc.getTransform(); // converts relative to ECEF in relation to source
|
|
982
452
|
const targetECEFToSpatial = targetEcefLoc.getTransform().inverse(); // converts ECEF to relative in relation to target
|
|
983
453
|
if (!targetECEFToSpatial) {
|
|
984
|
-
|
|
454
|
+
core_bentley_1.ITwinError.throwError({
|
|
455
|
+
iTwinErrorId: {
|
|
456
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
457
|
+
key: IModelTransformerError_1.IModelTransformerError.GeolocationUnavailable,
|
|
458
|
+
},
|
|
459
|
+
message: "Failed to invert target ECEF transform.",
|
|
460
|
+
});
|
|
985
461
|
}
|
|
986
462
|
const ecefTransform = targetECEFToSpatial.multiplyTransformTransform(srcSpatialToECEF); // chain both transforms
|
|
987
463
|
return ecefTransform;
|
|
@@ -999,15 +475,33 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
999
475
|
calculateTransformFromHelmertTransforms() {
|
|
1000
476
|
if (this.sourceDb.geographicCoordinateSystem?.horizontalCRS === undefined ||
|
|
1001
477
|
this.sourceDb.geographicCoordinateSystem?.verticalCRS === undefined) {
|
|
1002
|
-
|
|
478
|
+
core_bentley_1.ITwinError.throwError({
|
|
479
|
+
iTwinErrorId: {
|
|
480
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
481
|
+
key: IModelTransformerError_1.IModelTransformerError.GeographicCoordinateSystemUnavailable,
|
|
482
|
+
},
|
|
483
|
+
message: "Source iModel does not have a geographic coordinate system defined.",
|
|
484
|
+
});
|
|
1003
485
|
}
|
|
1004
486
|
if (this.targetDb.geographicCoordinateSystem?.horizontalCRS === undefined ||
|
|
1005
487
|
this.targetDb.geographicCoordinateSystem.verticalCRS === undefined) {
|
|
1006
|
-
|
|
488
|
+
core_bentley_1.ITwinError.throwError({
|
|
489
|
+
iTwinErrorId: {
|
|
490
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
491
|
+
key: IModelTransformerError_1.IModelTransformerError.GeographicCoordinateSystemUnavailable,
|
|
492
|
+
},
|
|
493
|
+
message: "Target iModel does not have a geographic coordinate system defined.",
|
|
494
|
+
});
|
|
1007
495
|
}
|
|
1008
496
|
if (!this.sourceDb.geographicCoordinateSystem.horizontalCRS.equals(this.targetDb.geographicCoordinateSystem.horizontalCRS) ||
|
|
1009
497
|
!this.sourceDb.geographicCoordinateSystem.verticalCRS.equals(this.targetDb.geographicCoordinateSystem.verticalCRS)) {
|
|
1010
|
-
|
|
498
|
+
core_bentley_1.ITwinError.throwError({
|
|
499
|
+
iTwinErrorId: {
|
|
500
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
501
|
+
key: IModelTransformerError_1.IModelTransformerError.GeographicCoordinateSystemMismatch,
|
|
502
|
+
},
|
|
503
|
+
message: "Source and target geographic coordinate systems must match to calculate the spatial transform.",
|
|
504
|
+
});
|
|
1011
505
|
}
|
|
1012
506
|
if (this.sourceDb.geographicCoordinateSystem.additionalTransform ===
|
|
1013
507
|
this.targetDb.geographicCoordinateSystem.additionalTransform) {
|
|
@@ -1019,14 +513,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1019
513
|
const targetScale = this.targetDb.geographicCoordinateSystem.additionalTransform
|
|
1020
514
|
?.helmert2DWithZOffset?.scale ?? 1;
|
|
1021
515
|
if (srcScale !== targetScale) {
|
|
1022
|
-
|
|
516
|
+
core_bentley_1.ITwinError.throwError({
|
|
517
|
+
iTwinErrorId: {
|
|
518
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
519
|
+
key: IModelTransformerError_1.IModelTransformerError.GeographicCoordinateSystemMismatch,
|
|
520
|
+
},
|
|
521
|
+
message: "Spatial transform is non rigid. Source and target Helmert transforms must have the same scale to calculate a rigid spatial transform.",
|
|
522
|
+
});
|
|
1023
523
|
}
|
|
1024
524
|
const srcTransform = IModelTransformer.convertHelmertToTransform(this.sourceDb.geographicCoordinateSystem.additionalTransform
|
|
1025
525
|
?.helmert2DWithZOffset); // moves elements to where src helmert transform would move them at render time
|
|
1026
526
|
const targetTransformInv = IModelTransformer.convertHelmertToTransform(this.targetDb.geographicCoordinateSystem.additionalTransform
|
|
1027
527
|
?.helmert2DWithZOffset).inverse(); // negates target helmert transform that is applied at render time
|
|
1028
528
|
if (!targetTransformInv) {
|
|
1029
|
-
|
|
529
|
+
core_bentley_1.ITwinError.throwError({
|
|
530
|
+
iTwinErrorId: {
|
|
531
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
532
|
+
key: IModelTransformerError_1.IModelTransformerError.GeolocationUnavailable,
|
|
533
|
+
},
|
|
534
|
+
message: "Failed to invert target Helmert transform.",
|
|
535
|
+
});
|
|
1030
536
|
}
|
|
1031
537
|
const combinedTransform = targetTransformInv.multiplyTransformTransform(srcTransform);
|
|
1032
538
|
return combinedTransform;
|
|
@@ -1036,6 +542,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1036
542
|
/** Returns true if a change within sourceElement is detected.
|
|
1037
543
|
* @param sourceElement The Element from the source iModel
|
|
1038
544
|
* @note A subclass can override this method to provide custom change detection behavior.
|
|
545
|
+
* @note During change processing, elements not present in the changeset are short-circuited
|
|
546
|
+
* in the exporter before reaching this method. To add elements to the changeset programmatically,
|
|
547
|
+
* use [[addCustomChanges]] rather than overriding this method to expand processing.
|
|
1039
548
|
*/
|
|
1040
549
|
hasElementChanged(sourceElement) {
|
|
1041
550
|
const sourceDbChanges = this.exporter.sourceDbChanges;
|
|
@@ -1055,7 +564,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1055
564
|
throw new Error(`source-target element mapping not found for element "${sourceElementId}" when completing partially committed elements. This is a bug.`);
|
|
1056
565
|
}
|
|
1057
566
|
const targetProps = await this.onTransformElement(sourceElement);
|
|
1058
|
-
this.
|
|
567
|
+
this._targetEditTxn.updateElement({ ...targetProps, id: targetId });
|
|
1059
568
|
}
|
|
1060
569
|
}
|
|
1061
570
|
async completePartiallyCommittedAspects() {
|
|
@@ -1066,14 +575,23 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1066
575
|
throw new Error(`source-target aspect mapping not found for aspect "${sourceAspectId}" when completing partially committed aspects. This is a bug.`);
|
|
1067
576
|
}
|
|
1068
577
|
const targetAspectProps = await this.onTransformElementAspect(sourceAspect);
|
|
1069
|
-
this.
|
|
578
|
+
this._targetEditTxn.updateAspect({
|
|
1070
579
|
...targetAspectProps,
|
|
1071
580
|
id: targetAspectId,
|
|
1072
581
|
});
|
|
1073
582
|
}
|
|
1074
583
|
}
|
|
584
|
+
/** An exported source element or model exists, so later references to it need no source query.
|
|
585
|
+
* Aspects are not recorded: they are rarely referenced, and they are usually the largest group.
|
|
586
|
+
*/
|
|
587
|
+
markSourceEntityExists(entity) {
|
|
588
|
+
if (this._options.danglingReferencesBehavior !== "reject")
|
|
589
|
+
return;
|
|
590
|
+
this._cloneContext.existenceCache.markExists(this.sourceDb, core_backend_1.EntityReferences.from(entity));
|
|
591
|
+
}
|
|
1075
592
|
async doAllReferencesExistInTarget(entity) {
|
|
1076
593
|
let allReferencesExist = true;
|
|
594
|
+
const checkedReferences = [];
|
|
1077
595
|
for (const referenceId of entity.getReferenceIds()) {
|
|
1078
596
|
const referencedEntityId = core_backend_1.EntityReferences.toId64(referenceId);
|
|
1079
597
|
if (referencedEntityId === core_common_1.IModel.repositoryModelId ||
|
|
@@ -1090,47 +608,70 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1090
608
|
allReferencesExist = false;
|
|
1091
609
|
}
|
|
1092
610
|
if (this._options.danglingReferencesBehavior === "reject") {
|
|
1093
|
-
|
|
611
|
+
checkedReferences.push(referenceId);
|
|
1094
612
|
}
|
|
1095
613
|
}
|
|
614
|
+
if (checkedReferences.length > 0) {
|
|
615
|
+
await this.assertReferencesExistInSource(checkedReferences, entity);
|
|
616
|
+
}
|
|
1096
617
|
return allReferencesExist;
|
|
1097
618
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
619
|
+
/** Assert that all `referenceIds` exist in the source iModel, batching the existence
|
|
620
|
+
* queries by entity type and caching positive results for the rest of the run.
|
|
621
|
+
*/
|
|
622
|
+
async assertReferencesExistInSource(referenceIds, entity) {
|
|
623
|
+
const found = await this._cloneContext.existenceCache.existsAll(this.sourceDb, referenceIds);
|
|
624
|
+
for (const referenceId of referenceIds) {
|
|
625
|
+
if (!found.has(referenceId)) {
|
|
626
|
+
core_bentley_1.ITwinError.throwError({
|
|
627
|
+
iTwinErrorId: {
|
|
628
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
629
|
+
key: IModelTransformerError_1.IModelTransformerError.DanglingReference,
|
|
630
|
+
},
|
|
631
|
+
message: [
|
|
632
|
+
`Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
|
|
633
|
+
"This must have been caused by an upstream application that changed the iModel.",
|
|
634
|
+
"You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this,",
|
|
635
|
+
`and the referenceId found on "${entity.id}" will not be carried over to corresponding target element.`,
|
|
636
|
+
].join("\n"),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
1109
639
|
}
|
|
1110
640
|
}
|
|
1111
641
|
/** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
|
|
1112
642
|
* @param sourceElementId Identifies the Element from the source iModel to import.
|
|
1113
643
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
644
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1114
645
|
*/
|
|
1115
646
|
async processElement(sourceElementId) {
|
|
1116
647
|
await this.initialize();
|
|
1117
648
|
if (sourceElementId === core_common_1.IModel.rootSubjectId) {
|
|
1118
|
-
|
|
649
|
+
core_bentley_1.ITwinError.throwError({
|
|
650
|
+
iTwinErrorId: {
|
|
651
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
652
|
+
key: IModelTransformerError_1.IModelTransformerError.RootSubjectNotProcessable,
|
|
653
|
+
},
|
|
654
|
+
message: "The root Subject should not be directly imported",
|
|
655
|
+
});
|
|
1119
656
|
}
|
|
1120
|
-
return this.exporter.exportElement(sourceElementId);
|
|
657
|
+
return this.processScopedElementExport(async () => this.exporter.exportElement(sourceElementId));
|
|
1121
658
|
}
|
|
1122
659
|
/** Import child elements into the target IModelDb
|
|
1123
660
|
* @param sourceElementId Import the child elements of this element in the source IModelDb.
|
|
1124
661
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
662
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1125
663
|
*/
|
|
1126
664
|
async processChildElements(sourceElementId) {
|
|
1127
665
|
await this.initialize();
|
|
1128
|
-
return this.exporter.exportChildElements(sourceElementId);
|
|
666
|
+
return this.processScopedElementExport(async () => this.exporter.exportChildElements(sourceElementId));
|
|
667
|
+
}
|
|
668
|
+
async processScopedElementExport(exportElements) {
|
|
669
|
+
await this.exporter.elementAspectExportCoordinator.run(exportElements);
|
|
1129
670
|
}
|
|
1130
671
|
/** Override of [IModelExportHandler.shouldExportElement]($transformer) that is called to determine if an element should be exported from the source iModel.
|
|
1131
672
|
* @note Reaching this point means that the element has passed the standard exclusion checks in IModelExporter.
|
|
1132
673
|
*/
|
|
1133
|
-
shouldExportElement(_sourceElement) {
|
|
674
|
+
async shouldExportElement(_sourceElement) {
|
|
1134
675
|
return true;
|
|
1135
676
|
}
|
|
1136
677
|
/**
|
|
@@ -1145,7 +686,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1145
686
|
const elemClass = sourceElement.constructor;
|
|
1146
687
|
const unresolvedReferences = elemClass.requiredReferenceKeys
|
|
1147
688
|
.map((referenceKey) => {
|
|
1148
|
-
const idContainer = sourceElement[referenceKey];
|
|
689
|
+
const idContainer = sourceElement.asAny[referenceKey];
|
|
1149
690
|
const referenceType = elemClass.requiredReferenceKeyTypeMap[referenceKey];
|
|
1150
691
|
// For now we just consider all required references to be elements (as they are in biscore), and do not support
|
|
1151
692
|
// entities that refuse to be inserted without a different kind of entity (e.g. aspect or relationship) first being inserted
|
|
@@ -1176,31 +717,138 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1176
717
|
.flat();
|
|
1177
718
|
if (unresolvedReferences.length > 0) {
|
|
1178
719
|
for (const reference of unresolvedReferences) {
|
|
1179
|
-
const processState = this.getElemTransformState(reference);
|
|
1180
|
-
// must export element first
|
|
720
|
+
const processState = await this.getElemTransformState(reference);
|
|
1181
721
|
if (processState.needsElemImport)
|
|
1182
|
-
await this.
|
|
722
|
+
await this.resolveRequiredElement(sourceElement.id, reference);
|
|
1183
723
|
if (processState.needsModelImport)
|
|
1184
724
|
await this.exporter.exportModel(reference);
|
|
1185
725
|
}
|
|
1186
726
|
}
|
|
1187
727
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
728
|
+
/** Makes sure an element that a changed element requires is mapped in the target before the changed element is imported.
|
|
729
|
+
* A changed required element is exported. An unchanged one is only looked up in the target: change processing does not insert it, and exporting it would visit its children, which can include the element that requires it.
|
|
730
|
+
* @throws [[IModelTransformerError.DependencyMappingMissing]] if the required element is still not mapped.
|
|
731
|
+
*/
|
|
732
|
+
async resolveRequiredElement(elementId, referenceId) {
|
|
733
|
+
const changes = this.exporter.sourceDbChanges;
|
|
734
|
+
const referenceChanged = changes === undefined ||
|
|
735
|
+
changes.element.insertIds.has(referenceId) ||
|
|
736
|
+
changes.element.updateIds.has(referenceId);
|
|
737
|
+
let accepted = true;
|
|
738
|
+
if (referenceChanged) {
|
|
739
|
+
await this.exporter.exportElement(referenceId);
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
const reference = this.sourceDb.elements.getElement({
|
|
743
|
+
id: referenceId,
|
|
744
|
+
wantGeometry: this.exporter.wantGeometry,
|
|
745
|
+
wantBRepData: this.exporter.wantGeometry,
|
|
746
|
+
});
|
|
747
|
+
// A required parent reached by the traversal has already been filtered.
|
|
748
|
+
accepted =
|
|
749
|
+
this.exporter.getUnchangedAncestorFilterResult(referenceId) ??
|
|
750
|
+
(await this.exporter.shouldExportElement(reference));
|
|
751
|
+
if (accepted)
|
|
752
|
+
await this.findExistingTargetElement(reference, await this.onTransformElement(reference));
|
|
753
|
+
}
|
|
754
|
+
if (core_bentley_1.Id64.isValid(this.context.findTargetElementId(referenceId)))
|
|
755
|
+
return;
|
|
756
|
+
let message;
|
|
757
|
+
if (referenceChanged)
|
|
758
|
+
message = `Element ${elementId} requires element ${referenceId}, which was not exported because the export filter rejects it or one of its ancestors. Accept element ${referenceId} and its ancestors, or reject element ${elementId}.`;
|
|
759
|
+
else if (accepted)
|
|
760
|
+
message = `Element ${elementId} requires unchanged element ${referenceId}, which is not in the target iModel. Change processing does not insert unchanged elements; to insert element ${referenceId}, add it in addCustomChanges.`;
|
|
761
|
+
else
|
|
762
|
+
message = `Element ${elementId} requires element ${referenceId}, which the export filter rejects. Accept element ${referenceId}, or reject element ${elementId} and every element that requires it.`;
|
|
763
|
+
core_bentley_1.ITwinError.throwError({
|
|
764
|
+
iTwinErrorId: {
|
|
765
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
766
|
+
key: IModelTransformerError_1.IModelTransformerError.DependencyMappingMissing,
|
|
767
|
+
},
|
|
768
|
+
message,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
/** Finds the existing target element for a source element by FederationGuid, then by Code, and records the mapping.
|
|
772
|
+
* @note Updates `targetElementProps.code` as the Code lookup requires.
|
|
773
|
+
* @returns the target element's id, or `Id64.invalid` if none is found.
|
|
774
|
+
*/
|
|
775
|
+
async findExistingTargetElement(sourceElement, targetElementProps) {
|
|
776
|
+
if (this.context.isBetweenIModels &&
|
|
777
|
+
sourceElement.federationGuid !== undefined) {
|
|
778
|
+
const targetElementId = this.targetDb.elements.getIdFromFederationGuid(sourceElement.federationGuid);
|
|
779
|
+
if (targetElementId !== undefined && core_bentley_1.Id64.isValid(targetElementId)) {
|
|
780
|
+
this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found
|
|
781
|
+
return targetElementId;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
// check by Code as long as the CodeScope is valid (invalid means a missing reference so not worth checking)
|
|
785
|
+
if (!core_bentley_1.Id64.isValidId64(targetElementProps.code.scope))
|
|
786
|
+
return core_bentley_1.Id64.invalid;
|
|
787
|
+
// respond the same way to undefined code value as the @see Code class, but don't use that class because it trims
|
|
788
|
+
// whitespace from the value, and there are iModels out there with untrimmed whitespace that we ought not to trim
|
|
789
|
+
targetElementProps.code.value = targetElementProps.code.value ?? "";
|
|
790
|
+
const maybeTargetElementId = await this.queryElementIdByCode(this.targetDb, targetElementProps.code);
|
|
791
|
+
if (maybeTargetElementId === undefined)
|
|
792
|
+
return core_bentley_1.Id64.invalid;
|
|
793
|
+
const maybeTargetElem = this.targetDb.elements.getElement(maybeTargetElementId);
|
|
794
|
+
if (maybeTargetElem.classFullName !== targetElementProps.classFullName) {
|
|
795
|
+
targetElementProps.code = core_common_1.Code.createEmpty(); // clear out invalid code
|
|
796
|
+
return core_bentley_1.Id64.invalid;
|
|
797
|
+
}
|
|
798
|
+
// ensure code remapping doesn't change the target class
|
|
799
|
+
this.context.remapElement(sourceElement.id, maybeTargetElementId); // record that the targetElement was found by Code
|
|
800
|
+
this._targetElementIdsRemappedByCode.add(maybeTargetElementId);
|
|
801
|
+
return maybeTargetElementId;
|
|
802
|
+
}
|
|
803
|
+
async getElemTransformState(elementId) {
|
|
804
|
+
const dbHasModel = async (db, id) => {
|
|
1190
805
|
const maybeModelId = core_backend_1.EntityReferences.fromEntityType(id, core_common_1.ConcreteEntityTypes.Model);
|
|
1191
|
-
return
|
|
806
|
+
return this._cloneContext.existenceCache.exists(db, maybeModelId);
|
|
1192
807
|
};
|
|
1193
|
-
const isSubModeled = dbHasModel(this.sourceDb, elementId);
|
|
808
|
+
const isSubModeled = await dbHasModel(this.sourceDb, elementId);
|
|
1194
809
|
const idOfElemInTarget = this.context.findTargetElementId(elementId);
|
|
1195
810
|
const isElemInTarget = core_bentley_1.Id64.invalid !== idOfElemInTarget;
|
|
1196
811
|
const needsModelImport = isSubModeled &&
|
|
1197
|
-
(!isElemInTarget || !dbHasModel(this.targetDb, idOfElemInTarget));
|
|
812
|
+
(!isElemInTarget || !(await dbHasModel(this.targetDb, idOfElemInTarget)));
|
|
1198
813
|
return { needsElemImport: !isElemInTarget, needsModelImport };
|
|
1199
814
|
}
|
|
815
|
+
// In iTwin js 5.x Elements.queryElementIdByCode() uses Code class to query id:
|
|
816
|
+
// https://github.com/iTwin/itwinjs-core/blob/master/core/backend/src/IModelDb.ts#L2779
|
|
817
|
+
// Code class constructor trims white spaces from code value.
|
|
818
|
+
// Custom implementation of queryElementIdByCode() was added to support querying elements with code values that have trailing whitespaces.
|
|
819
|
+
// It mimicks 4.x implementation: https://github.com/iTwin/itwinjs-core/blob/9c8b394ec3878a39764be81f928fd8b0b9115d31/core/backend/src/IModelDb.ts#L1882
|
|
820
|
+
async queryElementIdByCode(iModel, code) {
|
|
821
|
+
if (core_bentley_1.Id64.isInvalid(code.spec))
|
|
822
|
+
core_bentley_1.ITwinError.throwError({
|
|
823
|
+
iTwinErrorId: {
|
|
824
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
825
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidCode,
|
|
826
|
+
},
|
|
827
|
+
message: "Invalid CodeSpec",
|
|
828
|
+
});
|
|
829
|
+
if (code.value === undefined)
|
|
830
|
+
core_bentley_1.ITwinError.throwError({
|
|
831
|
+
iTwinErrorId: {
|
|
832
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
833
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidCode,
|
|
834
|
+
},
|
|
835
|
+
message: "Invalid Code",
|
|
836
|
+
});
|
|
837
|
+
const query = "SELECT ECInstanceId FROM BisCore:Element WHERE CodeSpec.Id=? AND CodeScope.Id=? AND CodeValue=?";
|
|
838
|
+
const queryBinder = new core_common_1.QueryBinder()
|
|
839
|
+
.bindId(1, code.spec)
|
|
840
|
+
.bindId(2, core_bentley_1.Id64.fromString(code.scope))
|
|
841
|
+
.bindString(3, code.value);
|
|
842
|
+
const queryReader = iModel.createQueryReader(query, queryBinder, {
|
|
843
|
+
usePrimaryConn: true,
|
|
844
|
+
});
|
|
845
|
+
return (await queryReader.step()) ? queryReader.current[0] : undefined;
|
|
846
|
+
}
|
|
1200
847
|
/** Override of [IModelExportHandler.onExportElement]($transformer) that imports an element into the target iModel when it is exported from the source iModel.
|
|
1201
848
|
* This override calls [[onTransformElement]] and then [IModelImporter.importElement]($transformer) to update the target iModel.
|
|
1202
849
|
*/
|
|
1203
850
|
async onExportElement(sourceElement) {
|
|
851
|
+
this.markSourceEntityExists(sourceElement);
|
|
1204
852
|
let targetElementId = core_bentley_1.Id64.invalid;
|
|
1205
853
|
let targetElementProps;
|
|
1206
854
|
if (this._options.wasSourceIModelCopiedToTarget) {
|
|
@@ -1212,34 +860,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1212
860
|
targetElementId = this.context.findTargetElementId(sourceElement.id);
|
|
1213
861
|
targetElementProps = await this.onTransformElement(sourceElement);
|
|
1214
862
|
}
|
|
1215
|
-
// if an existing remapping was not yet found, check by FederationGuid
|
|
1216
|
-
if (
|
|
1217
|
-
|
|
1218
|
-
sourceElement.federationGuid !== undefined) {
|
|
1219
|
-
targetElementId =
|
|
1220
|
-
this.targetDb.elements.getIdFromFederationGuid(sourceElement.federationGuid) ?? core_bentley_1.Id64.invalid;
|
|
1221
|
-
if (core_bentley_1.Id64.isValid(targetElementId))
|
|
1222
|
-
this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found
|
|
1223
|
-
}
|
|
1224
|
-
// if an existing remapping was not yet found, check by Code as long as the CodeScope is valid (invalid means a missing reference so not worth checking)
|
|
1225
|
-
if (!core_bentley_1.Id64.isValidId64(targetElementId) &&
|
|
1226
|
-
core_bentley_1.Id64.isValidId64(targetElementProps.code.scope)) {
|
|
1227
|
-
// respond the same way to undefined code value as the @see Code class, but don't use that class because it trims
|
|
1228
|
-
// whitespace from the value, and there are iModels out there with untrimmed whitespace that we ought not to trim
|
|
1229
|
-
targetElementProps.code.value = targetElementProps.code.value ?? "";
|
|
1230
|
-
const maybeTargetElementId = this.targetDb.elements.queryElementIdByCode(targetElementProps.code);
|
|
1231
|
-
if (undefined !== maybeTargetElementId) {
|
|
1232
|
-
const maybeTargetElem = this.targetDb.elements.getElement(maybeTargetElementId);
|
|
1233
|
-
if (maybeTargetElem.classFullName === targetElementProps.classFullName) {
|
|
1234
|
-
// ensure code remapping doesn't change the target class
|
|
1235
|
-
targetElementId = maybeTargetElementId;
|
|
1236
|
-
this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found by Code
|
|
1237
|
-
}
|
|
1238
|
-
else {
|
|
1239
|
-
targetElementProps.code = core_common_1.Code.createEmpty(); // clear out invalid code
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
863
|
+
// if an existing remapping was not yet found, check by FederationGuid and then by Code
|
|
864
|
+
if (!core_bentley_1.Id64.isValid(targetElementId))
|
|
865
|
+
targetElementId = await this.findExistingTargetElement(sourceElement, targetElementProps);
|
|
1243
866
|
if (!this.hasElementChanged(sourceElement)) {
|
|
1244
867
|
core_bentley_1.Logger.logTrace(loggerCategory, `Skipping unchanged element (${sourceElement.id}, ${sourceElement.getDisplayLabel()}).`);
|
|
1245
868
|
return;
|
|
@@ -1255,7 +878,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1255
878
|
const isValid = core_bentley_1.Id64.isValid(targetElementId);
|
|
1256
879
|
if (isValid && targetElementId !== sourceElement.id) {
|
|
1257
880
|
// Element found with different id
|
|
1258
|
-
|
|
881
|
+
core_bentley_1.ITwinError.throwError({
|
|
882
|
+
iTwinErrorId: {
|
|
883
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
884
|
+
key: IModelTransformerError_1.IModelTransformerError.ElementIdNotPreservable,
|
|
885
|
+
},
|
|
886
|
+
message: `Element id(${sourceElement.id}) cannot be preserved. Found a different mapping(${targetElementId}) from source element`,
|
|
887
|
+
});
|
|
1259
888
|
}
|
|
1260
889
|
else if (isValid && targetElementId === sourceElement.id) {
|
|
1261
890
|
// targetElementId is valid (indicating update)
|
|
@@ -1266,7 +895,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1266
895
|
// if we don't find mapping for source element in target(invalid) but another element with source id exists in target
|
|
1267
896
|
if (sourceInTargetElemProps) {
|
|
1268
897
|
// Element id is already taken by another element
|
|
1269
|
-
|
|
898
|
+
core_bentley_1.ITwinError.throwError({
|
|
899
|
+
iTwinErrorId: {
|
|
900
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
901
|
+
key: IModelTransformerError_1.IModelTransformerError.ElementIdNotPreservable,
|
|
902
|
+
},
|
|
903
|
+
message: `Element id(${sourceElement.id}) cannot be preserved. An unrelated element in the target already uses id: ${sourceElement.id}`,
|
|
904
|
+
});
|
|
1270
905
|
}
|
|
1271
906
|
else {
|
|
1272
907
|
// Element id in target is available to be remapped
|
|
@@ -1275,12 +910,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1275
910
|
}
|
|
1276
911
|
}
|
|
1277
912
|
if (!this._options.wasSourceIModelCopiedToTarget) {
|
|
1278
|
-
this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
|
|
913
|
+
await this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
|
|
1279
914
|
}
|
|
1280
915
|
if (targetElementProps.id === undefined) {
|
|
1281
|
-
throw new
|
|
916
|
+
throw new Error("targetElementProps.id should be assigned by importElement");
|
|
1282
917
|
}
|
|
1283
918
|
this.context.remapElement(sourceElement.id, targetElementProps.id);
|
|
919
|
+
if (this.sourceDb === this.targetDb) {
|
|
920
|
+
this._cloneContext.existenceCache.markExists(this.targetDb, core_backend_1.EntityReferences.fromEntityType(targetElementProps.id, core_common_1.ConcreteEntityTypes.Element));
|
|
921
|
+
}
|
|
1284
922
|
// the transformer does not currently 'split' or 'join' any elements, therefore, it does not
|
|
1285
923
|
// insert external source aspects because federation guids are sufficient for this.
|
|
1286
924
|
// Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
|
|
@@ -1288,39 +926,43 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1288
926
|
// physical consolidation is an example of a 'joining' transform
|
|
1289
927
|
// FIXME: verify at finalization time that we don't lose provenance on new elements
|
|
1290
928
|
// FIXME: make public and improve `initElementProvenance` API for usage by consolidators
|
|
929
|
+
const provenanceDb = await this.getProvenanceDb();
|
|
930
|
+
const provenanceEditTxn = await this._provenanceManager.getProvenanceEditTxn();
|
|
1291
931
|
if (!this._options.noProvenance) {
|
|
1292
|
-
|
|
932
|
+
const provenance = this._options.forceExternalSourceAspectProvenance ||
|
|
1293
933
|
this._elementsWithExplicitlyTrackedProvenance.has(sourceElement.id)
|
|
1294
934
|
? undefined
|
|
1295
935
|
: sourceElement.federationGuid;
|
|
1296
936
|
if (!provenance) {
|
|
1297
|
-
const aspectProps = this.initElementProvenance(sourceElement.id, targetElementProps.id);
|
|
1298
|
-
const foundEsaProps =
|
|
937
|
+
const aspectProps = await this.initElementProvenance(sourceElement.id, targetElementProps.id);
|
|
938
|
+
const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
|
|
1299
939
|
if (foundEsaProps === undefined)
|
|
1300
|
-
aspectProps.id =
|
|
940
|
+
aspectProps.id = provenanceEditTxn.insertAspect(aspectProps);
|
|
1301
941
|
else {
|
|
1302
942
|
// Since initElementProvenance sets a property 'version' on the aspectProps that we wish to persist in the provenanceDb, only grab the id from the foundEsaProps.
|
|
1303
943
|
aspectProps.id = foundEsaProps.aspectId;
|
|
1304
|
-
|
|
944
|
+
provenanceEditTxn.updateAspect(aspectProps);
|
|
1305
945
|
}
|
|
1306
|
-
provenance = aspectProps;
|
|
1307
946
|
}
|
|
1308
|
-
this.markLastProvenance(provenance, { isRelationship: false });
|
|
1309
947
|
}
|
|
1310
948
|
}
|
|
1311
|
-
/**
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
949
|
+
/** Maps the deleted source element IDs and passes the target IDs to the importer as one set. */
|
|
950
|
+
async onDeleteElements(sourceElementIds) {
|
|
951
|
+
const targetElementIds = new Set();
|
|
952
|
+
for (const sourceElementId of sourceElementIds) {
|
|
953
|
+
const targetElementId = this.context.findTargetElementId(sourceElementId);
|
|
954
|
+
if (core_bentley_1.Id64.isValidId64(targetElementId) &&
|
|
955
|
+
!this._targetElementIdsRemappedByCode.has(targetElementId)) {
|
|
956
|
+
targetElementIds.add(targetElementId);
|
|
957
|
+
}
|
|
1318
958
|
}
|
|
959
|
+
await this.importer.deleteElements(targetElementIds);
|
|
1319
960
|
}
|
|
1320
961
|
/** Override of [IModelExportHandler.onExportModel]($transformer) that is called when a Model should be exported from the source iModel.
|
|
1321
962
|
* This override calls [[onTransformModel]] and then [IModelImporter.importModel]($transformer) to update the target iModel.
|
|
1322
963
|
*/
|
|
1323
|
-
onExportModel(sourceModel) {
|
|
964
|
+
async onExportModel(sourceModel) {
|
|
965
|
+
this.markSourceEntityExists(sourceModel);
|
|
1324
966
|
if (this._options.skipPropagateChangesToRootElements &&
|
|
1325
967
|
core_common_1.IModel.repositoryModelId === sourceModel.id)
|
|
1326
968
|
return; // The RepositoryModel should not be directly imported
|
|
@@ -1331,7 +973,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1331
973
|
if (isRemappedRootSubject)
|
|
1332
974
|
return;
|
|
1333
975
|
const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
|
|
1334
|
-
this.importer.importModel(targetModelProps);
|
|
976
|
+
await this.importer.importModel(targetModelProps);
|
|
977
|
+
if (targetModelProps.id === undefined) {
|
|
978
|
+
throw new Error("targetModelProps.id should be assigned by now");
|
|
979
|
+
}
|
|
980
|
+
this._cloneContext.existenceCache.markExists(this.targetDb, core_backend_1.EntityReferences.fromEntityType(targetModelProps.id, core_common_1.ConcreteEntityTypes.Model));
|
|
981
|
+
this._targetModelsImportedInCurrentTransform.add(targetModelProps.id);
|
|
1335
982
|
}
|
|
1336
983
|
/** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
|
|
1337
984
|
async onDeleteModel(sourceModelId) {
|
|
@@ -1341,6 +988,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1341
988
|
const targetModelId = this.context.findTargetElementId(sourceModelId);
|
|
1342
989
|
if (!core_bentley_1.Id64.isValidId64(targetModelId))
|
|
1343
990
|
return;
|
|
991
|
+
// This handles cases where model with a partition element was remapped to
|
|
992
|
+
// new element by code value after recreation.
|
|
993
|
+
if (this._targetElementIdsRemappedByCode.has(targetModelId) &&
|
|
994
|
+
this._targetModelsImportedInCurrentTransform.has(targetModelId)) {
|
|
995
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `Skipping delete operation for model (source id: ${sourceModelId}) because the target model (id: ${targetModelId}) was remapped by Code and re-imported during this transformation pass.`);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
1344
998
|
const sql = `
|
|
1345
999
|
SELECT 1
|
|
1346
1000
|
FROM bis.DefinitionPartition
|
|
@@ -1352,7 +1006,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1352
1006
|
`;
|
|
1353
1007
|
if (this.exporter.sourceDbChanges?.element.deleteIds.has(sourceModelId)) {
|
|
1354
1008
|
const params = new core_common_1.QueryBinder().bindId("targetModelId", targetModelId);
|
|
1355
|
-
const reader = this.targetDb.createQueryReader(sql, params
|
|
1009
|
+
const reader = this.targetDb.createQueryReader(sql, params, {
|
|
1010
|
+
usePrimaryConn: true,
|
|
1011
|
+
});
|
|
1356
1012
|
const isDefinitionPartition = await reader.step();
|
|
1357
1013
|
if (isDefinitionPartition) {
|
|
1358
1014
|
// Skipping model deletion because model's partition will also be deleted.
|
|
@@ -1370,9 +1026,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1370
1026
|
error.errorNumber === core_bentley_1.IModelStatus.ForeignKeyConstraint);
|
|
1371
1027
|
if (!isDeletionProhibitedErr)
|
|
1372
1028
|
throw error;
|
|
1373
|
-
//
|
|
1374
|
-
//
|
|
1375
|
-
// The `OnDeleteElement` calls `DeleteElementTree` Which deletes the model together with its partition after deleting all of the modeled elements.
|
|
1029
|
+
// Models are processed before elements, but a definition model cannot be deleted while it contains elements.
|
|
1030
|
+
// Add its partition to the element deletion set. Bulk cascade deletion will remove the contents, model, and partition together.
|
|
1376
1031
|
this.scheduleModeledPartitionDeletion(sourceModelId);
|
|
1377
1032
|
}
|
|
1378
1033
|
}
|
|
@@ -1387,22 +1042,24 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1387
1042
|
/** Cause the model container, contents, and sub-models to be exported from the source iModel and imported into the target iModel.
|
|
1388
1043
|
* @param sourceModeledElementId Import this [Model]($backend) from the source IModelDb.
|
|
1389
1044
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1045
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1390
1046
|
*/
|
|
1391
1047
|
async processModel(sourceModeledElementId) {
|
|
1392
1048
|
await this.initialize();
|
|
1393
|
-
return this.exporter.exportModel(sourceModeledElementId);
|
|
1049
|
+
return this.processScopedElementExport(async () => this.exporter.exportModel(sourceModeledElementId));
|
|
1394
1050
|
}
|
|
1395
1051
|
/** Cause the model contents to be exported from the source iModel and imported into the target iModel.
|
|
1396
1052
|
* @param sourceModelId Import the contents of this model from the source IModelDb.
|
|
1397
1053
|
* @param targetModelId Import into this model in the target IModelDb. The target model must exist prior to this call.
|
|
1398
1054
|
* @param elementClassFullName Optional classFullName of an element subclass to limit import query against the source model.
|
|
1399
1055
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1056
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1400
1057
|
*/
|
|
1401
1058
|
async processModelContents(sourceModelId, targetModelId, elementClassFullName = core_backend_1.Element.classFullName) {
|
|
1402
1059
|
await this.initialize();
|
|
1403
1060
|
this.targetDb.models.getModel(targetModelId); // throws if Model does not exist
|
|
1404
1061
|
this.context.remapElement(sourceModelId, targetModelId); // set remapping in case importModelContents is called directly
|
|
1405
|
-
return this.exporter.exportModelContents(sourceModelId, elementClassFullName);
|
|
1062
|
+
return this.processScopedElementExport(async () => this.exporter.exportModelContents(sourceModelId, elementClassFullName));
|
|
1406
1063
|
}
|
|
1407
1064
|
/** Cause all sub-models that recursively descend from the specified Subject to be exported from the source iModel and imported into the target iModel. */
|
|
1408
1065
|
async processSubjectSubModels(sourceSubjectId) {
|
|
@@ -1410,12 +1067,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1410
1067
|
// import DefinitionModels first
|
|
1411
1068
|
const childDefinitionPartitionSql = `SELECT ECInstanceId FROM ${core_backend_1.DefinitionPartition.classFullName} WHERE Parent.Id=:subjectId`;
|
|
1412
1069
|
const params = new core_common_1.QueryBinder().bindId("subjectId", sourceSubjectId);
|
|
1413
|
-
for await (const row of this.sourceDb.createQueryReader(childDefinitionPartitionSql, params)) {
|
|
1070
|
+
for await (const row of this.sourceDb.createQueryReader(childDefinitionPartitionSql, params, { usePrimaryConn: true })) {
|
|
1414
1071
|
await this.processModel(row.id);
|
|
1415
1072
|
}
|
|
1416
1073
|
// import other partitions next
|
|
1417
1074
|
const childPartitionSql = `SELECT ECInstanceId FROM ${core_backend_1.InformationPartitionElement.classFullName} WHERE Parent.Id=:subjectId`;
|
|
1418
|
-
for await (const row of this.sourceDb.createQueryReader(childPartitionSql, params)) {
|
|
1075
|
+
for await (const row of this.sourceDb.createQueryReader(childPartitionSql, params, { usePrimaryConn: true })) {
|
|
1419
1076
|
const modelId = row.id;
|
|
1420
1077
|
const model = this.sourceDb.models.getModel(modelId);
|
|
1421
1078
|
if (!(model instanceof core_backend_1.DefinitionModel)) {
|
|
@@ -1424,7 +1081,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1424
1081
|
}
|
|
1425
1082
|
// recurse into child Subjects
|
|
1426
1083
|
const childSubjectSql = `SELECT ECInstanceId FROM ${core_backend_1.Subject.classFullName} WHERE Parent.Id=:subjectId`;
|
|
1427
|
-
for await (const row of this.sourceDb.createQueryReader(childSubjectSql, params)) {
|
|
1084
|
+
for await (const row of this.sourceDb.createQueryReader(childSubjectSql, params, { usePrimaryConn: true })) {
|
|
1428
1085
|
await this.processSubjectSubModels(row.id);
|
|
1429
1086
|
}
|
|
1430
1087
|
}
|
|
@@ -1443,104 +1100,22 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1443
1100
|
};
|
|
1444
1101
|
targetModelProps.id = targetModeledElementId;
|
|
1445
1102
|
if (targetModelProps.parentModel === undefined) {
|
|
1446
|
-
|
|
1103
|
+
core_bentley_1.ITwinError.throwError({
|
|
1104
|
+
iTwinErrorId: {
|
|
1105
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1106
|
+
key: IModelTransformerError_1.IModelTransformerError.ParentModelRequired,
|
|
1107
|
+
},
|
|
1108
|
+
message: "targetElementProps must have a defined parentModel",
|
|
1109
|
+
});
|
|
1447
1110
|
}
|
|
1448
1111
|
targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
|
|
1449
1112
|
return targetModelProps;
|
|
1450
1113
|
}
|
|
1451
|
-
/**
|
|
1452
|
-
|
|
1453
|
-
* updates the target scope element to say that transformation up through the
|
|
1454
|
-
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1455
|
-
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
1456
|
-
*
|
|
1457
|
-
* You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
|
|
1458
|
-
* It is public for unsupported use cases of custom synchronization transforms.
|
|
1459
|
-
* @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
|
|
1460
|
-
* unless the `initializeReverseSyncVersion` option is set to `true`
|
|
1461
|
-
*
|
|
1462
|
-
* The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
|
|
1463
|
-
* When set to `true`, it saves the reverse sync version as the current changeset of the targetDb. This is typically used for the first transformation between a master and branch iModel.
|
|
1464
|
-
* Setting `initializeReverseSyncVersion` to `true` has the effect of making it so any changesets in the branch iModel at the time of the first transformation will be ignored during any future reverse synchronizations from the branch to the master iModel.
|
|
1465
|
-
*
|
|
1466
|
-
* Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
|
|
1467
|
-
* Setting initializeReverseSyncVersion to true during a forward transformation could overwrite this correct reverseSyncVersion and should only be done during the first transformation between a master and branch iModel.
|
|
1468
|
-
*/
|
|
1469
|
-
updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
|
|
1470
|
-
const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
|
|
1471
|
-
this._sourceChangeDataState !== "has-changes";
|
|
1472
|
-
if (shouldSkipSyncVersionUpdate)
|
|
1473
|
-
return;
|
|
1474
|
-
nodeAssert(this._targetScopeProvenanceProps);
|
|
1475
|
-
const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
|
|
1476
|
-
const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
|
|
1477
|
-
if (this.isReverseSynchronization) {
|
|
1478
|
-
const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
|
|
1479
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
|
|
1480
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1481
|
-
sourceVersion;
|
|
1482
|
-
}
|
|
1483
|
-
else {
|
|
1484
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
|
|
1485
|
-
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1486
|
-
// save reverse sync version
|
|
1487
|
-
if (initializeReverseSyncVersion) {
|
|
1488
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
|
|
1489
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1490
|
-
targetVersion;
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
if (this._options.argsForProcessChanges ||
|
|
1494
|
-
(this._startingChangesetIndices && initializeReverseSyncVersion)) {
|
|
1495
|
-
nodeAssert(this.targetDb.changeset.index !== undefined &&
|
|
1496
|
-
this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
|
|
1497
|
-
// Store in a local variable, so typescript knows it's defined (due to the assert above)
|
|
1498
|
-
const startingChangesetIndices = this._startingChangesetIndices;
|
|
1499
|
-
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
1500
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1501
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1502
|
-
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1503
|
-
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1504
|
-
// Determine which keys to clear and update based on the synchronization direction
|
|
1505
|
-
let syncChangesetsToClearKey;
|
|
1506
|
-
let syncChangesetsToUpdateKey;
|
|
1507
|
-
if (this.isReverseSynchronization) {
|
|
1508
|
-
syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
|
|
1509
|
-
syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
|
|
1510
|
-
}
|
|
1511
|
-
else {
|
|
1512
|
-
syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
|
|
1513
|
-
syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
|
|
1514
|
-
}
|
|
1515
|
-
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1516
|
-
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1517
|
-
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1518
|
-
// stuff to it which would break future synchronizations
|
|
1519
|
-
for (let i = startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1520
|
-
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1521
|
-
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
1522
|
-
jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
|
|
1523
|
-
return csIndex > startingChangesetIndices.source;
|
|
1524
|
-
});
|
|
1525
|
-
// if reverse sync then we may have received provenance changes which should be marked as sync changes
|
|
1526
|
-
if (this.isReverseSynchronization) {
|
|
1527
|
-
nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
|
|
1528
|
-
for (let i = startingChangesetIndices.source + 1; i <= this.sourceDb.changeset.index + 1; i++)
|
|
1529
|
-
jsonProps.pendingReverseSyncChangesetIndices.push(i);
|
|
1530
|
-
}
|
|
1531
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `new pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1532
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `new pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1533
|
-
}
|
|
1534
|
-
this.provenanceDb.elements.updateAspect({
|
|
1535
|
-
...this._targetScopeProvenanceProps,
|
|
1536
|
-
jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
|
|
1537
|
-
});
|
|
1538
|
-
this.clearCachedSynchronizationVersion();
|
|
1539
|
-
}
|
|
1540
|
-
// FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
|
|
1541
|
-
finalizeTransformation() {
|
|
1114
|
+
/** Complete a high-level transformation after all export operations finish. */
|
|
1115
|
+
async finalizeTransformation() {
|
|
1542
1116
|
this.importer.finalize();
|
|
1543
|
-
this.
|
|
1117
|
+
this._cloneContext.existenceCache.clear();
|
|
1118
|
+
await this.updateSynchronizationVersion({
|
|
1544
1119
|
initializeReverseSyncVersion: this._isProvenanceInitTransform,
|
|
1545
1120
|
});
|
|
1546
1121
|
// TODO: ignore if we remove change cache usage
|
|
@@ -1548,17 +1123,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1548
1123
|
if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
|
|
1549
1124
|
core_backend_1.ChangeSummaryManager.detachChangeCache(this.sourceDb);
|
|
1550
1125
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
}
|
|
1557
|
-
/* eslint-enable @itwin/no-internal */
|
|
1126
|
+
this.setCodeValueBehavior("trim-unicode-whitespace");
|
|
1127
|
+
}
|
|
1128
|
+
setCodeValueBehavior(behavior) {
|
|
1129
|
+
this.sourceDb.codeValueBehavior = behavior;
|
|
1130
|
+
this.targetDb.codeValueBehavior = behavior;
|
|
1558
1131
|
}
|
|
1559
1132
|
/** Imports all relationships that subclass from the specified base class.
|
|
1560
1133
|
* @param baseRelClassFullName The specified base relationship class.
|
|
1561
1134
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1135
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1562
1136
|
*/
|
|
1563
1137
|
async processRelationships(baseRelClassFullName) {
|
|
1564
1138
|
await this.initialize();
|
|
@@ -1567,39 +1141,41 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1567
1141
|
/** Override of [IModelExportHandler.shouldExportRelationship]($transformer) that is called to determine if a [Relationship]($backend) should be exported.
|
|
1568
1142
|
* @note Reaching this point means that the relationship has passed the standard exclusion checks in [IModelExporter]($transformer).
|
|
1569
1143
|
*/
|
|
1570
|
-
shouldExportRelationship(_sourceRelationship) {
|
|
1144
|
+
async shouldExportRelationship(_sourceRelationship) {
|
|
1571
1145
|
return true;
|
|
1572
1146
|
}
|
|
1573
1147
|
/** Override of [IModelExportHandler.onExportRelationship]($transformer) that imports a relationship into the target iModel when it is exported from the source iModel.
|
|
1574
1148
|
* This override calls [[onTransformRelationship]] and then [IModelImporter.importRelationship]($transformer) to update the target iModel.
|
|
1575
1149
|
*/
|
|
1576
|
-
async onExportRelationship(sourceRelationship) {
|
|
1577
|
-
const sourceFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.sourceId);
|
|
1578
|
-
const targetFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.targetId);
|
|
1150
|
+
async onExportRelationship(sourceRelationship, _isUpdate, sourceFedGuid, targetFedGuid) {
|
|
1579
1151
|
const targetRelationshipProps = this.onTransformRelationship(sourceRelationship);
|
|
1580
|
-
const targetRelationshipInstanceId = this.importer.importRelationship(targetRelationshipProps);
|
|
1152
|
+
const targetRelationshipInstanceId = await this.importer.importRelationship(targetRelationshipProps);
|
|
1153
|
+
const provenanceDb = await this.getProvenanceDb();
|
|
1154
|
+
const provenanceEditTxn = await this._provenanceManager.getProvenanceEditTxn();
|
|
1581
1155
|
if (!this._options.noProvenance &&
|
|
1582
1156
|
core_bentley_1.Id64.isValid(targetRelationshipInstanceId)) {
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
const
|
|
1157
|
+
const needsEsaProvenance = !this._options
|
|
1158
|
+
.forceExternalSourceAspectProvenance
|
|
1159
|
+
? !(sourceFedGuid && targetFedGuid)
|
|
1160
|
+
: true;
|
|
1161
|
+
if (needsEsaProvenance) {
|
|
1162
|
+
const aspectProps = await this._provenanceManager.initRelationshipProvenance(sourceRelationship.id, targetRelationshipInstanceId, this._forceOldRelationshipProvenanceMethod, {
|
|
1163
|
+
sourceRelSourceElementId: sourceRelationship.sourceId,
|
|
1164
|
+
targetRelSourceElementId: targetRelationshipProps.sourceId,
|
|
1165
|
+
});
|
|
1166
|
+
const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
|
|
1589
1167
|
// onExportRelationship doesn't need to call updateAspect if esaProps were found, because relationship provenance doesn't have the same concept of a version as element provenance (which uses last mod time on the elements).
|
|
1590
1168
|
if (undefined === foundEsaProps) {
|
|
1591
|
-
aspectProps.id =
|
|
1169
|
+
aspectProps.id = provenanceEditTxn.insertAspect(aspectProps);
|
|
1592
1170
|
}
|
|
1593
|
-
provenance = aspectProps;
|
|
1594
1171
|
}
|
|
1595
|
-
this.markLastProvenance(provenance, { isRelationship: true });
|
|
1596
1172
|
}
|
|
1597
1173
|
}
|
|
1598
1174
|
/** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
|
|
1599
1175
|
* This override propagates the delete to the target iModel via [IModelImporter.deleteRelationship]($transformer).
|
|
1600
1176
|
*/
|
|
1601
|
-
onDeleteRelationship(sourceRelInstanceId) {
|
|
1602
|
-
|
|
1177
|
+
async onDeleteRelationship(sourceRelInstanceId) {
|
|
1178
|
+
(0, node_assert_1.strict)(this._deletedSourceRelationshipData, "should be defined at initialization by now");
|
|
1603
1179
|
const deletedRelData = this._deletedSourceRelationshipData.get(sourceRelInstanceId);
|
|
1604
1180
|
if (!deletedRelData) {
|
|
1605
1181
|
// this can occur if both the source and target deleted it
|
|
@@ -1612,14 +1188,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1612
1188
|
targetId: deletedRelData.targetIdInTarget,
|
|
1613
1189
|
})?.id;
|
|
1614
1190
|
if (id) {
|
|
1615
|
-
this.importer.deleteRelationship({
|
|
1191
|
+
await this.importer.deleteRelationship({
|
|
1616
1192
|
id,
|
|
1617
1193
|
classFullName: deletedRelData.classFullName,
|
|
1618
1194
|
});
|
|
1619
1195
|
}
|
|
1620
1196
|
if (deletedRelData.provenanceAspectId) {
|
|
1621
1197
|
try {
|
|
1622
|
-
this.
|
|
1198
|
+
(await this._provenanceManager.getProvenanceEditTxn()).deleteAspect(deletedRelData.provenanceAspectId);
|
|
1623
1199
|
}
|
|
1624
1200
|
catch (error) {
|
|
1625
1201
|
// This aspect may no longer exist if it was deleted at some other point during the transformation. This is fine.
|
|
@@ -1630,46 +1206,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1630
1206
|
}
|
|
1631
1207
|
}
|
|
1632
1208
|
_yieldManager = new core_bentley_1.YieldManager();
|
|
1633
|
-
/** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
|
|
1634
|
-
* @deprecated in 1.x. Don't use this anymore
|
|
1635
|
-
* @see [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided.
|
|
1636
|
-
* @note This method is called from [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are undefined, so it only needs to be called directly when processing a subset of an iModel.
|
|
1637
|
-
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
1638
|
-
*/
|
|
1639
|
-
async detectRelationshipDeletes() {
|
|
1640
|
-
if (this.isReverseSynchronization) {
|
|
1641
|
-
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot detect deletes when isReverseSynchronization=true");
|
|
1642
|
-
}
|
|
1643
|
-
const aspectDeleteIds = [];
|
|
1644
|
-
const sql = `
|
|
1645
|
-
SELECT ECInstanceId, Identifier, JsonProperties
|
|
1646
|
-
FROM ${core_backend_1.ExternalSourceAspect.classFullName} aspect
|
|
1647
|
-
WHERE aspect.Scope.Id=:scopeId
|
|
1648
|
-
AND aspect.Kind=:kind
|
|
1649
|
-
`;
|
|
1650
|
-
const params = new core_common_1.QueryBinder().bindId("scopeId", this.targetScopeElementId);
|
|
1651
|
-
params.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
1652
|
-
for await (const row of this.targetDb.createQueryReader(sql, params)) {
|
|
1653
|
-
const sourceRelInstanceId = core_bentley_1.Id64.fromJSON(row[1]);
|
|
1654
|
-
if (undefined ===
|
|
1655
|
-
this.sourceDb.relationships.tryGetInstanceProps(core_backend_1.ElementRefersToElements.classFullName, sourceRelInstanceId)) {
|
|
1656
|
-
// this function exists only to support some in-imodel transformations, which must
|
|
1657
|
-
// use the old (external source aspect) provenance method anyway so we don't need to support
|
|
1658
|
-
// new provenance
|
|
1659
|
-
const json = JSON.parse(row[2]);
|
|
1660
|
-
const targetRelInstanceId = json.targetRelInstanceId ?? json.provenanceRelInstanceId;
|
|
1661
|
-
if (targetRelInstanceId) {
|
|
1662
|
-
this.importer.deleteRelationship({
|
|
1663
|
-
id: targetRelInstanceId,
|
|
1664
|
-
classFullName: core_backend_1.ElementRefersToElements.classFullName,
|
|
1665
|
-
});
|
|
1666
|
-
}
|
|
1667
|
-
aspectDeleteIds.push(row.id);
|
|
1668
|
-
}
|
|
1669
|
-
await this._yieldManager.allowYield();
|
|
1670
|
-
}
|
|
1671
|
-
this.targetDb.elements.deleteAspect(aspectDeleteIds);
|
|
1672
|
-
}
|
|
1673
1209
|
/** Transform the specified sourceRelationship into RelationshipProps for the target iModel.
|
|
1674
1210
|
* @param sourceRelationship The Relationship from the source iModel to be transformed.
|
|
1675
1211
|
* @returns RelationshipProps for the target iModel.
|
|
@@ -1680,19 +1216,36 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1680
1216
|
targetRelationshipProps.sourceId = this.context.findTargetElementId(sourceRelationship.sourceId);
|
|
1681
1217
|
targetRelationshipProps.targetId = this.context.findTargetElementId(sourceRelationship.targetId);
|
|
1682
1218
|
// TODO: move to cloneRelationship in IModelCloneContext
|
|
1683
|
-
sourceRelationship.
|
|
1219
|
+
const relationshipClass = sourceRelationship.iModel.schemaContext.getSchemaItemSync(sourceRelationship.schemaItemKey, ECSchemaMetaData.RelationshipClass);
|
|
1220
|
+
(0, core_bentley_1.assert)(relationshipClass !== undefined, `Cannot get metadata for ${sourceRelationship.classFullName}.`);
|
|
1221
|
+
for (const property of relationshipClass.getPropertiesSync()) {
|
|
1222
|
+
const propertyName = property.name;
|
|
1684
1223
|
if (property.isPrimitive() && "Id" === property.extendedTypeName) {
|
|
1685
1224
|
targetRelationshipProps[core_common_1.ECJsNames.toJsName(propertyName)] =
|
|
1686
1225
|
this.context.findTargetElementId(sourceRelationship.asAny[core_common_1.ECJsNames.toJsName(propertyName)]);
|
|
1687
1226
|
}
|
|
1688
|
-
}
|
|
1227
|
+
}
|
|
1689
1228
|
return targetRelationshipProps;
|
|
1690
1229
|
}
|
|
1691
|
-
shouldExportElementAspect(aspect) {
|
|
1692
|
-
//
|
|
1693
|
-
//
|
|
1230
|
+
async shouldExportElementAspect(aspect) {
|
|
1231
|
+
// An aspect cannot be exported when its owner has no source-to-target
|
|
1232
|
+
// mapping.
|
|
1694
1233
|
return this.context.findTargetElementId(aspect.element.id) !== core_bentley_1.Id64.invalid;
|
|
1695
1234
|
}
|
|
1235
|
+
async prepareElementAspects(excludedElementAspectClassFullNames, elementIds) {
|
|
1236
|
+
if (!this.exporter.visitElements)
|
|
1237
|
+
return;
|
|
1238
|
+
if (elementIds === undefined)
|
|
1239
|
+
return;
|
|
1240
|
+
const targetElementIds = new Set();
|
|
1241
|
+
for (const sourceElementId of elementIds) {
|
|
1242
|
+
const targetElementId = this.context.findTargetElementId(sourceElementId);
|
|
1243
|
+
if (core_bentley_1.Id64.isValid(targetElementId)) {
|
|
1244
|
+
targetElementIds.add(targetElementId);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
await this.importer.elementAspectCleanup.delete(targetElementIds, excludedElementAspectClassFullNames, this.targetScopeElementId);
|
|
1248
|
+
}
|
|
1696
1249
|
/** Override of [IModelExportHandler.onExportElementUniqueAspect]($transformer) that imports an ElementUniqueAspect into the target iModel when it is exported from the source iModel.
|
|
1697
1250
|
* This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
|
|
1698
1251
|
*/
|
|
@@ -1701,7 +1254,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1701
1254
|
if (!(await this.doAllReferencesExistInTarget(sourceAspect))) {
|
|
1702
1255
|
this._partiallyCommittedAspectIds.add(sourceAspect.id);
|
|
1703
1256
|
}
|
|
1704
|
-
const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
|
|
1257
|
+
const targetId = await this.importer.importElementUniqueAspect(targetAspectProps);
|
|
1705
1258
|
this.context.remapElementAspect(sourceAspect.id, targetId);
|
|
1706
1259
|
}
|
|
1707
1260
|
/** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
|
|
@@ -1710,14 +1263,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1710
1263
|
*/
|
|
1711
1264
|
async onExportElementMultiAspects(sourceAspects) {
|
|
1712
1265
|
// Transform source ElementMultiAspects into target ElementAspectProps
|
|
1713
|
-
const targetAspectPropsArray = sourceAspects.map(async (srcA) =>
|
|
1714
|
-
|
|
1266
|
+
const targetAspectPropsArray = sourceAspects.map(async (srcA) => this.onTransformElementAspect(srcA));
|
|
1267
|
+
for (const a of sourceAspects) {
|
|
1715
1268
|
if (!(await this.doAllReferencesExistInTarget(a))) {
|
|
1716
1269
|
this._partiallyCommittedAspectIds.add(a.id);
|
|
1717
1270
|
}
|
|
1718
|
-
}
|
|
1271
|
+
}
|
|
1719
1272
|
// const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
|
|
1720
|
-
const targetIds = this.importer.importElementMultiAspects(await Promise.all(targetAspectPropsArray), (a) => {
|
|
1273
|
+
const targetIds = await this.importer.importElementMultiAspects(await Promise.all(targetAspectPropsArray), (a) => {
|
|
1721
1274
|
const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
|
|
1722
1275
|
a.scope?.id === this.targetScopeElementId;
|
|
1723
1276
|
return (!this._options.includeSourceProvenance ||
|
|
@@ -1733,7 +1286,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1733
1286
|
* @note A subclass can override this method to provide custom transform behavior.
|
|
1734
1287
|
*/
|
|
1735
1288
|
async onTransformElementAspect(sourceElementAspect) {
|
|
1736
|
-
const targetElementAspectProps = await this.
|
|
1289
|
+
const targetElementAspectProps = await this._cloneContext.cloneElementAspect(sourceElementAspect);
|
|
1737
1290
|
return targetElementAspectProps;
|
|
1738
1291
|
}
|
|
1739
1292
|
/** The directory where schemas will be exported, a random temporary directory */
|
|
@@ -1741,82 +1294,27 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1741
1294
|
/** Override of [IModelExportHandler.shouldExportSchema]($transformer) that is called to determine if a schema should be exported
|
|
1742
1295
|
* @note the default behavior doesn't import schemas older than those already in the target
|
|
1743
1296
|
*/
|
|
1744
|
-
shouldExportSchema(schemaKey) {
|
|
1297
|
+
async shouldExportSchema(schemaKey) {
|
|
1745
1298
|
const versionInTarget = this.targetDb.querySchemaVersion(schemaKey.name);
|
|
1746
1299
|
if (versionInTarget === undefined)
|
|
1747
1300
|
return true;
|
|
1748
1301
|
return Semver.gt(`${schemaKey.version.read}.${schemaKey.version.write}.${schemaKey.version.minor}`, core_backend_1.Schema.toSemverString(versionInTarget));
|
|
1749
1302
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
*
|
|
1753
|
-
* @returns {Promise<ExportSchemaResult>} Although the type is possibly void for backwards compatibility of subclasses,
|
|
1754
|
-
* `IModelTransformer.onExportSchema` always returns an[[IModelExportHandler.ExportSchemaResult]]
|
|
1755
|
-
* with a defined `schemaPath` property, for subclasses to know where the schema was written.
|
|
1756
|
-
* Schemas are *not* guaranteed to be written to [[IModelTransformer._schemaExportDir]] by a
|
|
1757
|
-
* known pattern derivable from the schema's name, so you must use this to find it.
|
|
1303
|
+
/** Serialize a source schema to the temporary directory used by [[processSchemas]].
|
|
1304
|
+
* @returns A path to the serialized schema file.
|
|
1305
|
+
* @note The file name is not derived reliably from the schema name. Use the returned path when a subclass calls `super`.
|
|
1758
1306
|
*/
|
|
1759
1307
|
async onExportSchema(schema) {
|
|
1760
|
-
|
|
1761
|
-
let schemaFileName = schema.name + ext;
|
|
1762
|
-
// many file systems have a max file-name/path-segment size of 255, so we workaround that on all systems
|
|
1763
|
-
const systemMaxPathSegmentSize = 255;
|
|
1764
|
-
// windows usually has a limit for the total path length of 260
|
|
1765
|
-
const windowsMaxPathLimit = 260;
|
|
1766
|
-
if (schemaFileName.length > systemMaxPathSegmentSize ||
|
|
1767
|
-
path.join(this._schemaExportDir, schemaFileName).length >=
|
|
1768
|
-
windowsMaxPathLimit) {
|
|
1769
|
-
// this name should be well under 255 bytes
|
|
1770
|
-
// ( 100 + (Number.MAX_SAFE_INTEGER.toString().length = 16) + (ext.length = 13) ) = 129 which is less than 255
|
|
1771
|
-
// You'd have to be past 2**53-1 (Number.MAX_SAFE_INTEGER) long named schemas in order to hit decimal formatting,
|
|
1772
|
-
// and that's on the scale of at least petabytes. `Map.prototype.size` shouldn't return floating points, and even
|
|
1773
|
-
// if they do they're in scientific notation, size bound and contain no invalid windows path chars
|
|
1774
|
-
schemaFileName = `${schema.name.slice(0, 100)}${this._longNamedSchemasMap.size}${ext}`;
|
|
1775
|
-
nodeAssert(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
|
|
1776
|
-
this._longNamedSchemasMap.set(schema.name, schemaFileName);
|
|
1777
|
-
}
|
|
1778
|
-
this.sourceDb.exportSchema({
|
|
1779
|
-
schemaName: schema.name,
|
|
1780
|
-
outputDirectory: this._schemaExportDir,
|
|
1781
|
-
outputFileName: schemaFileName,
|
|
1782
|
-
});
|
|
1783
|
-
return { schemaPath: path.join(this._schemaExportDir, schemaFileName) };
|
|
1784
|
-
}
|
|
1785
|
-
_makeLongNameResolvingSchemaCtx() {
|
|
1786
|
-
const result = new core_backend_1.ECSchemaXmlContext();
|
|
1787
|
-
result.setSchemaLocater((key) => {
|
|
1788
|
-
const match = this._longNamedSchemasMap.get(key.name);
|
|
1789
|
-
if (match !== undefined)
|
|
1790
|
-
return path.join(this._schemaExportDir, match);
|
|
1791
|
-
return undefined;
|
|
1792
|
-
});
|
|
1793
|
-
return result;
|
|
1308
|
+
return this._schemaProcessingCoordinator.serializeSourceSchema(schema);
|
|
1794
1309
|
}
|
|
1795
|
-
/**
|
|
1310
|
+
/** Process source schemas and import selected or generated definitions into the target iModel.
|
|
1311
|
+
* @param options Selects the schema processing strategy.
|
|
1312
|
+
* @note When no strategy is supplied, [[NewerVersionSchemaImportStrategy]] is used.
|
|
1796
1313
|
* @note For performance reasons, it is recommended that [IModelDb.saveChanges]($backend) be called after `processSchemas` is complete.
|
|
1797
1314
|
* It is more efficient to process *data* changes after the schema changes have been saved.
|
|
1798
1315
|
*/
|
|
1799
|
-
async processSchemas() {
|
|
1800
|
-
|
|
1801
|
-
try {
|
|
1802
|
-
core_backend_1.IModelJsFs.mkdirSync(this._schemaExportDir);
|
|
1803
|
-
this._longNamedSchemasMap.clear();
|
|
1804
|
-
await this.exporter.exportSchemas();
|
|
1805
|
-
const exportedSchemaFiles = core_backend_1.IModelJsFs.readdirSync(this._schemaExportDir);
|
|
1806
|
-
if (exportedSchemaFiles.length === 0)
|
|
1807
|
-
return;
|
|
1808
|
-
const schemaFullPaths = exportedSchemaFiles.map((s) => path.join(this._schemaExportDir, s));
|
|
1809
|
-
const maybeLongNameResolvingSchemaCtx = this._longNamedSchemasMap.size > 0
|
|
1810
|
-
? this._makeLongNameResolvingSchemaCtx()
|
|
1811
|
-
: undefined;
|
|
1812
|
-
return await this.targetDb.importSchemas(schemaFullPaths, {
|
|
1813
|
-
ecSchemaXmlContext: maybeLongNameResolvingSchemaCtx,
|
|
1814
|
-
});
|
|
1815
|
-
}
|
|
1816
|
-
finally {
|
|
1817
|
-
core_backend_1.IModelJsFs.removeSync(this._schemaExportDir);
|
|
1818
|
-
this._longNamedSchemasMap.clear();
|
|
1819
|
-
}
|
|
1316
|
+
async processSchemas(options) {
|
|
1317
|
+
return this._schemaProcessingCoordinator.process(options?.strategy ?? new SchemaProcessingStrategy_1.NewerVersionSchemaImportStrategy());
|
|
1820
1318
|
}
|
|
1821
1319
|
/** Cause all fonts to be exported from the source iModel and imported into the target iModel.
|
|
1822
1320
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
@@ -1827,8 +1325,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1827
1325
|
return this.exporter.exportFonts();
|
|
1828
1326
|
}
|
|
1829
1327
|
/** Override of [IModelExportHandler.onExportFont]($transformer) that imports a font into the target iModel when it is exported from the source iModel. */
|
|
1830
|
-
onExportFont(font, _isUpdate) {
|
|
1831
|
-
this.
|
|
1328
|
+
async onExportFont(font, _isUpdate) {
|
|
1329
|
+
this._cloneContext.importFont(font.id);
|
|
1832
1330
|
}
|
|
1833
1331
|
/** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
|
|
1834
1332
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
@@ -1847,21 +1345,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1847
1345
|
/** Override of [IModelExportHandler.shouldExportCodeSpec]($transformer) that is called to determine if a CodeSpec should be exported from the source iModel.
|
|
1848
1346
|
* @note Reaching this point means that the CodeSpec has passed the standard exclusion checks in [IModelExporter]($transformer).
|
|
1849
1347
|
*/
|
|
1850
|
-
shouldExportCodeSpec(_sourceCodeSpec) {
|
|
1348
|
+
async shouldExportCodeSpec(_sourceCodeSpec) {
|
|
1851
1349
|
return true;
|
|
1852
1350
|
}
|
|
1853
1351
|
/** Override of [IModelExportHandler.onExportCodeSpec]($transformer) that imports a CodeSpec into the target iModel when it is exported from the source iModel. */
|
|
1854
|
-
onExportCodeSpec(sourceCodeSpec) {
|
|
1855
|
-
this.
|
|
1352
|
+
async onExportCodeSpec(sourceCodeSpec) {
|
|
1353
|
+
this._cloneContext.importCodeSpec(sourceCodeSpec.id);
|
|
1856
1354
|
}
|
|
1857
|
-
/** Recursively import all Elements and sub-Models that descend from the specified Subject
|
|
1355
|
+
/** Recursively import all Elements and sub-Models that descend from the specified Subject.
|
|
1356
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1357
|
+
*/
|
|
1858
1358
|
async processSubject(sourceSubjectId, targetSubjectId) {
|
|
1859
1359
|
await this.initialize();
|
|
1860
1360
|
this.sourceDb.elements.getElement(sourceSubjectId, core_backend_1.Subject); // throws if sourceSubjectId is not a Subject
|
|
1861
1361
|
this.targetDb.elements.getElement(targetSubjectId, core_backend_1.Subject); // throws if targetSubjectId is not a Subject
|
|
1862
1362
|
this.context.remapElement(sourceSubjectId, targetSubjectId);
|
|
1863
|
-
await this.
|
|
1864
|
-
|
|
1363
|
+
await this.processScopedElementExport(async () => {
|
|
1364
|
+
await this.processChildElements(sourceSubjectId);
|
|
1365
|
+
await this.processSubjectSubModels(sourceSubjectId);
|
|
1366
|
+
});
|
|
1865
1367
|
await this.completePartiallyCommittedElements();
|
|
1866
1368
|
await this.completePartiallyCommittedAspects();
|
|
1867
1369
|
}
|
|
@@ -1870,6 +1372,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1870
1372
|
_sourceChangeDataState = "uninited";
|
|
1871
1373
|
/** length === 0 when _changeDataState = "no-change", length > 0 means "has-changes", otherwise undefined */
|
|
1872
1374
|
_csFileProps = undefined;
|
|
1375
|
+
_deletionRecordsByChangeset;
|
|
1873
1376
|
/**
|
|
1874
1377
|
* Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
|
|
1875
1378
|
* are intending to process changes. Callers may wish to explicitly call initialize if they need to execute code after initialize but before [[process]] is called.
|
|
@@ -1879,15 +1382,37 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1879
1382
|
async initialize() {
|
|
1880
1383
|
if (this._initialized)
|
|
1881
1384
|
return;
|
|
1385
|
+
this.assertEditTxnActive();
|
|
1882
1386
|
await this.initScopeProvenance();
|
|
1883
1387
|
await this._tryInitChangesetData(this._options.argsForProcessChanges);
|
|
1884
|
-
await this.
|
|
1885
|
-
|
|
1886
|
-
await this.
|
|
1388
|
+
await this._cloneContext.initialize();
|
|
1389
|
+
const exporterInitOptions = await this.getExportInitOpts(this._options.argsForProcessChanges ?? {});
|
|
1390
|
+
await this.initializeChangesetScanAndExporter(exporterInitOptions);
|
|
1887
1391
|
// Exporter must be initialized prior to processing changesets in order to properly handle entity recreations (an entity delete followed by an insert of that same entity).
|
|
1888
1392
|
await this.processChangesets();
|
|
1889
1393
|
this._initialized = true;
|
|
1890
1394
|
}
|
|
1395
|
+
async initializeChangesetScanAndExporter(exporterInitOptions) {
|
|
1396
|
+
if (this._csFileProps !== undefined &&
|
|
1397
|
+
this._csFileProps.length > 0 &&
|
|
1398
|
+
this.sourceDb.isBriefcaseDb()) {
|
|
1399
|
+
const changedInstanceIds = this.exporter.sourceDbChanges ??
|
|
1400
|
+
("changedInstanceIds" in exporterInitOptions
|
|
1401
|
+
? exporterInitOptions.changedInstanceIds
|
|
1402
|
+
: new IModelExporter_1.ChangedInstanceIds(this.sourceDb));
|
|
1403
|
+
this._deletionRecordsByChangeset = await ChangesetScanner_1.ChangesetScanner.scan(this.sourceDb, this._csFileProps, changedInstanceIds, {
|
|
1404
|
+
populateChangedInstanceIds: this.exporter.sourceDbChanges === undefined &&
|
|
1405
|
+
!("changedInstanceIds" in exporterInitOptions),
|
|
1406
|
+
});
|
|
1407
|
+
await this.exporter.initialize({
|
|
1408
|
+
changedInstanceIds,
|
|
1409
|
+
skipPropagateChangesToRootElements: exporterInitOptions.skipPropagateChangesToRootElements,
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
else {
|
|
1413
|
+
await this.exporter.initialize(exporterInitOptions);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1891
1416
|
/**
|
|
1892
1417
|
* Reads all the changeset files in the private member of the transformer: _csFileProps
|
|
1893
1418
|
* and finds the corresponding target entity for any deleted source entities and remaps the sourceId to the targetId.
|
|
@@ -1895,12 +1420,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1895
1420
|
* @returns void
|
|
1896
1421
|
*/
|
|
1897
1422
|
async processChangesets() {
|
|
1898
|
-
await this.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1423
|
+
await this._provenanceManager.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1899
1424
|
this.context.remapElement(sourceElementId, targetElementId);
|
|
1900
1425
|
});
|
|
1901
1426
|
if (this.exporter.sourceDbChanges)
|
|
1902
1427
|
await this.addCustomChanges(this.exporter.sourceDbChanges);
|
|
1903
|
-
|
|
1428
|
+
const csFileProps = this._csFileProps;
|
|
1429
|
+
if (csFileProps === undefined || csFileProps.length === 0) {
|
|
1904
1430
|
if (this.exporter.sourceDbChanges === undefined ||
|
|
1905
1431
|
!this.exporter.sourceDbChanges.hasChanges)
|
|
1906
1432
|
return;
|
|
@@ -1908,14 +1434,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1908
1434
|
if (this._sourceChangeDataState === "no-changes")
|
|
1909
1435
|
this._sourceChangeDataState = "has-changes";
|
|
1910
1436
|
}
|
|
1437
|
+
const deletionRecordsByChangeset = this._deletionRecordsByChangeset;
|
|
1438
|
+
if (deletionRecordsByChangeset === undefined)
|
|
1439
|
+
return;
|
|
1911
1440
|
const relationshipECClassIdsToSkip = new Set();
|
|
1912
|
-
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)")) {
|
|
1441
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)", undefined, { usePrimaryConn: true })) {
|
|
1913
1442
|
relationshipECClassIdsToSkip.add(row.ECInstanceId);
|
|
1914
1443
|
}
|
|
1915
1444
|
const relationshipECClassIds = new Set();
|
|
1916
|
-
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)")) {
|
|
1445
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)", undefined, { usePrimaryConn: true })) {
|
|
1917
1446
|
relationshipECClassIds.add(row.ECInstanceId);
|
|
1918
1447
|
}
|
|
1448
|
+
const elementAspectECClassIds = new Set();
|
|
1449
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementAspect)", undefined, { usePrimaryConn: true })) {
|
|
1450
|
+
elementAspectECClassIds.add(row.ECInstanceId);
|
|
1451
|
+
}
|
|
1919
1452
|
// For later use when processing deletes.
|
|
1920
1453
|
const alreadyImportedElementInserts = new Set();
|
|
1921
1454
|
const alreadyImportedModelInserts = new Set();
|
|
@@ -1930,46 +1463,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1930
1463
|
alreadyImportedModelInserts.add(targetModelId);
|
|
1931
1464
|
});
|
|
1932
1465
|
this._deletedSourceRelationshipData = new Map();
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
fileName: csFile.pathname,
|
|
1936
|
-
db: this.sourceDb,
|
|
1937
|
-
disableSchemaCheck: true,
|
|
1938
|
-
});
|
|
1939
|
-
const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
|
|
1940
|
-
const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier(this.sourceDb);
|
|
1941
|
-
while (csAdaptor.step()) {
|
|
1942
|
-
ecChangeUnifier.appendFrom(csAdaptor);
|
|
1943
|
-
}
|
|
1944
|
-
const changes = [...ecChangeUnifier.instances];
|
|
1466
|
+
const isElementAspectDeletion = (change) => elementAspectECClassIds.has(change.ecClassId);
|
|
1467
|
+
for (const changes of deletionRecordsByChangeset) {
|
|
1945
1468
|
/** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
|
|
1946
1469
|
const elemIdToScopeEsa = new Map();
|
|
1947
1470
|
for (const change of changes) {
|
|
1948
|
-
if (change.
|
|
1949
|
-
relationshipECClassIdsToSkip.has(change.ECClassId))
|
|
1471
|
+
if (relationshipECClassIdsToSkip.has(change.ecClassId))
|
|
1950
1472
|
continue;
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
change
|
|
1954
|
-
change.
|
|
1955
|
-
change.
|
|
1956
|
-
elemIdToScopeEsa.set(change.Element.Id, change);
|
|
1473
|
+
if (change.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
|
|
1474
|
+
change.scopeId === this.targetScopeElementId &&
|
|
1475
|
+
change.kind === core_backend_1.ExternalSourceAspect.Kind.Element &&
|
|
1476
|
+
change.elementId !== undefined) {
|
|
1477
|
+
elemIdToScopeEsa.set(change.elementId, change);
|
|
1957
1478
|
}
|
|
1958
1479
|
}
|
|
1959
1480
|
// Loop to process deletes.
|
|
1960
1481
|
for (const change of changes) {
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
if (
|
|
1964
|
-
throw new Error(`ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change?.$meta?.tables}`);
|
|
1965
|
-
if (changeType === undefined)
|
|
1966
|
-
throw new Error(`ChangeType was undefined for id: ${change.ECInstanceId}.`);
|
|
1967
|
-
if (changeType !== "Deleted" ||
|
|
1968
|
-
relationshipECClassIdsToSkip.has(ecClassId))
|
|
1482
|
+
if (relationshipECClassIdsToSkip.has(change.ecClassId))
|
|
1483
|
+
continue;
|
|
1484
|
+
if (isElementAspectDeletion(change))
|
|
1969
1485
|
continue;
|
|
1970
|
-
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId
|
|
1486
|
+
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(change.ecClassId), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1971
1487
|
}
|
|
1972
|
-
csReader.close();
|
|
1973
1488
|
}
|
|
1974
1489
|
return;
|
|
1975
1490
|
}
|
|
@@ -1995,16 +1510,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1995
1510
|
async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
|
|
1996
1511
|
// we need a connected iModel with changes to remap elements with deletions
|
|
1997
1512
|
const notConnectedModel = this.sourceDb.iTwinId === undefined;
|
|
1998
|
-
const noChanges = this.
|
|
1513
|
+
const noChanges = (await this.getSynchronizationVersion()).index ===
|
|
1514
|
+
this.sourceDb.changeset.index &&
|
|
1999
1515
|
(this.exporter.sourceDbChanges === undefined ||
|
|
2000
1516
|
!this.exporter.sourceDbChanges.hasChanges);
|
|
2001
1517
|
if (notConnectedModel || noChanges)
|
|
2002
1518
|
return;
|
|
2003
1519
|
/**
|
|
2004
|
-
*
|
|
1520
|
+
* If the deleted source entity is in the provenanceDb, then we can use its ids to query for ESAs.
|
|
2005
1521
|
* This is because the ESAs are stored on an element Id thats present in the provenanceDb.
|
|
2006
1522
|
*/
|
|
2007
|
-
const changeDataInProvenanceDb = this.sourceDb === this.
|
|
1523
|
+
const changeDataInProvenanceDb = this.sourceDb === (await this.getProvenanceDb());
|
|
2008
1524
|
const getTargetIdFromSourceId = async (id) => {
|
|
2009
1525
|
let identifierValue;
|
|
2010
1526
|
let element;
|
|
@@ -2013,18 +1529,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2013
1529
|
}
|
|
2014
1530
|
const fedGuid = isRelationship
|
|
2015
1531
|
? element?.federationGuid
|
|
2016
|
-
: change.
|
|
1532
|
+
: change.federationGuid;
|
|
2017
1533
|
if (changeDataInProvenanceDb) {
|
|
2018
1534
|
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
2019
1535
|
for await (const row of this.sourceDb.createQueryReader("SELECT esa.Identifier FROM bis.ExternalSourceAspect esa WHERE Scope.Id=:scopeId AND Kind=:kind AND Element.Id=:relatedElementId LIMIT 1", core_common_1.QueryBinder.from([
|
|
2020
1536
|
this.targetScopeElementId,
|
|
2021
1537
|
core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
2022
1538
|
id,
|
|
2023
|
-
]))) {
|
|
1539
|
+
]), { usePrimaryConn: true })) {
|
|
2024
1540
|
identifierValue = row.Identifier;
|
|
2025
1541
|
}
|
|
2026
1542
|
identifierValue =
|
|
2027
|
-
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.
|
|
1543
|
+
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.identifier;
|
|
2028
1544
|
}
|
|
2029
1545
|
// Check for targetId by an esa first
|
|
2030
1546
|
if (changeDataInProvenanceDb && identifierValue) {
|
|
@@ -2038,11 +1554,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2038
1554
|
}
|
|
2039
1555
|
return undefined;
|
|
2040
1556
|
};
|
|
2041
|
-
const changedInstanceId = change.
|
|
1557
|
+
const changedInstanceId = change.ecInstanceId;
|
|
2042
1558
|
if (isRelationship) {
|
|
2043
|
-
const sourceIdOfRelationshipInSource = change.
|
|
2044
|
-
const targetIdOfRelationshipInSource = change.
|
|
2045
|
-
const classFullName = change
|
|
1559
|
+
const sourceIdOfRelationshipInSource = change.sourceECInstanceId;
|
|
1560
|
+
const targetIdOfRelationshipInSource = change.targetECInstanceId;
|
|
1561
|
+
const classFullName = change.classFullName;
|
|
1562
|
+
if (sourceIdOfRelationshipInSource === undefined ||
|
|
1563
|
+
targetIdOfRelationshipInSource === undefined) {
|
|
1564
|
+
core_bentley_1.ITwinError.throwError({
|
|
1565
|
+
iTwinErrorId: {
|
|
1566
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1567
|
+
key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
|
|
1568
|
+
},
|
|
1569
|
+
message: `Relationship deletion ${changedInstanceId} is missing an endpoint.`,
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
2046
1572
|
const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
|
|
2047
1573
|
const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
|
|
2048
1574
|
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
@@ -2052,8 +1578,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2052
1578
|
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
2053
1579
|
});
|
|
2054
1580
|
}
|
|
2055
|
-
else if (this.sourceDb ===
|
|
2056
|
-
|
|
1581
|
+
else if (this.sourceDb ===
|
|
1582
|
+
(await this._provenanceManager.getProvenanceSourceDb())) {
|
|
1583
|
+
const relProvenance = await this._provenanceManager.queryProvenanceForRelationship(changedInstanceId, {
|
|
2057
1584
|
classFullName: classFullName ?? "",
|
|
2058
1585
|
sourceId: sourceIdOfRelationshipInSource,
|
|
2059
1586
|
targetId: targetIdOfRelationshipInSource,
|
|
@@ -2068,8 +1595,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2068
1595
|
}
|
|
2069
1596
|
else {
|
|
2070
1597
|
let targetId = await getTargetIdFromSourceId(changedInstanceId);
|
|
2071
|
-
if (targetId === undefined &&
|
|
2072
|
-
|
|
1598
|
+
if (targetId === undefined &&
|
|
1599
|
+
this.sourceDb ===
|
|
1600
|
+
(await this._provenanceManager.getProvenanceSourceDb())) {
|
|
1601
|
+
const contextTargetId = this.context.findTargetElementId(changedInstanceId);
|
|
1602
|
+
if (core_bentley_1.Id64.isValidId64(contextTargetId))
|
|
1603
|
+
targetId = contextTargetId;
|
|
2073
1604
|
}
|
|
2074
1605
|
// since we are processing one changeset at a time, we can see local source deletes
|
|
2075
1606
|
// of entities that were never synced and can be safely ignored
|
|
@@ -2077,7 +1608,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2077
1608
|
if (deletionNotInTarget)
|
|
2078
1609
|
return;
|
|
2079
1610
|
if (targetId === undefined) {
|
|
2080
|
-
throw new
|
|
1611
|
+
throw new Error("targetId should be acquired from source id or transformation context");
|
|
2081
1612
|
}
|
|
2082
1613
|
this.context.remapElement(changedInstanceId, targetId);
|
|
2083
1614
|
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
@@ -2097,7 +1628,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2097
1628
|
this._sourceChangeDataState = "unconnected";
|
|
2098
1629
|
return;
|
|
2099
1630
|
}
|
|
2100
|
-
const
|
|
1631
|
+
const syncVersion = await this.getSynchronizationVersion();
|
|
1632
|
+
const noChanges = syncVersion.index === this.sourceDb.changeset.index;
|
|
2101
1633
|
if (noChanges) {
|
|
2102
1634
|
this._sourceChangeDataState = "no-changes";
|
|
2103
1635
|
this._csFileProps = [];
|
|
@@ -2106,9 +1638,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2106
1638
|
const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
|
|
2107
1639
|
// NOTE: that we do NOT download the changesummary for the last transformed version, we want
|
|
2108
1640
|
// to ignore those already processed changes
|
|
2109
|
-
const startChangesetIndexOrId = startChangeset?.index ??
|
|
2110
|
-
startChangeset?.id ??
|
|
2111
|
-
this.synchronizationVersion.index + 1;
|
|
1641
|
+
const startChangesetIndexOrId = startChangeset?.index ?? startChangeset?.id ?? syncVersion.index + 1;
|
|
2112
1642
|
const endChangesetId = this.sourceDb.changeset.id;
|
|
2113
1643
|
const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
|
|
2114
1644
|
? indexOrId
|
|
@@ -2116,25 +1646,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2116
1646
|
iModelId: this.sourceDb.iModelId,
|
|
2117
1647
|
changeset: { id: indexOrId },
|
|
2118
1648
|
}).then((changeset) => changeset.index)));
|
|
2119
|
-
const missingChangesets = startChangesetIndex >
|
|
1649
|
+
const missingChangesets = startChangesetIndex > syncVersion.index + 1;
|
|
2120
1650
|
if (!this._options.argsForProcessChanges
|
|
2121
1651
|
?.ignoreMissingChangesetsInSynchronizations &&
|
|
2122
|
-
startChangesetIndex !==
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
`
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
1652
|
+
startChangesetIndex !== syncVersion.index + 1 &&
|
|
1653
|
+
syncVersion.index !== -1) {
|
|
1654
|
+
core_bentley_1.ITwinError.throwError({
|
|
1655
|
+
iTwinErrorId: {
|
|
1656
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1657
|
+
key: IModelTransformerError_1.IModelTransformerError.SynchronizationRangeInvalid,
|
|
1658
|
+
},
|
|
1659
|
+
message: `synchronization is ${missingChangesets ? "missing changesets" : ""},` +
|
|
1660
|
+
" startChangesetId should be" +
|
|
1661
|
+
" exactly the first changeset *after* the previous synchronization to not miss data." +
|
|
1662
|
+
` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
|
|
1663
|
+
` but the previous synchronization for this targetScopeElem ${syncVersion.id}'` +
|
|
1664
|
+
` which is changeset #${syncVersion.index}. The transformer expected` +
|
|
1665
|
+
` #${syncVersion.index + 1}.`,
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
const changesetsToSkip = await this._provenanceManager.getChangesetsToSkip();
|
|
2138
1669
|
core_bentley_1.Logger.logTrace(loggerCategory, `changesets to skip: ${changesetsToSkip}`);
|
|
2139
1670
|
this._changesetRanges = (0, Algo_1.rangesFromRangeAndSkipped)(startChangesetIndex, endChangesetIndex, changesetsToSkip);
|
|
2140
1671
|
core_bentley_1.Logger.logTrace(loggerCategory, `ranges: ${this._changesetRanges}`);
|
|
@@ -2153,6 +1684,27 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2153
1684
|
this._sourceChangeDataState =
|
|
2154
1685
|
this._csFileProps.length === 0 ? "no-changes" : "has-changes";
|
|
2155
1686
|
}
|
|
1687
|
+
/** Asserts that the EditTxn is active before any write operations. */
|
|
1688
|
+
assertEditTxnActive() {
|
|
1689
|
+
if (!this._targetEditTxn.isActive) {
|
|
1690
|
+
core_bentley_1.ITwinError.throwError({
|
|
1691
|
+
iTwinErrorId: {
|
|
1692
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1693
|
+
key: IModelTransformerError_1.IModelTransformerError.EditTxnNotActive,
|
|
1694
|
+
},
|
|
1695
|
+
message: "The target EditTxn must be started before calling process(). Call targetEditTxn.start() before invoking the transformer.",
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
if (this._sourceEditTxn && !this._sourceEditTxn.isActive) {
|
|
1699
|
+
core_bentley_1.ITwinError.throwError({
|
|
1700
|
+
iTwinErrorId: {
|
|
1701
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1702
|
+
key: IModelTransformerError_1.IModelTransformerError.EditTxnNotActive,
|
|
1703
|
+
},
|
|
1704
|
+
message: "The source EditTxn was provided but is not active. Call sourceEditTxn.start() before invoking the transformer.",
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
2156
1708
|
/**
|
|
2157
1709
|
* The behavior of process is influenced by [[IModelTransformOptions.argsForProcessChanges]] being defined or not defined during construction passed of the IModelTransformer.
|
|
2158
1710
|
* @section When argsForProcessChanges are defined:
|
|
@@ -2187,47 +1739,34 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2187
1739
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2188
1740
|
*/
|
|
2189
1741
|
async processAll() {
|
|
1742
|
+
this._targetElementIdsRemappedByCode.clear();
|
|
1743
|
+
this._targetModelsImportedInCurrentTransform.clear();
|
|
1744
|
+
// processAll always has changes to process, so mark it as such for version tracking
|
|
1745
|
+
this._sourceChangeDataState = "has-changes";
|
|
2190
1746
|
await this.exporter.exportCodeSpecs();
|
|
2191
1747
|
await this.exporter.exportFonts();
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
1748
|
+
await this.exporter.elementAspectExportCoordinator.run(async () => {
|
|
1749
|
+
if (this._options.skipPropagateChangesToRootElements) {
|
|
1750
|
+
// The RepositoryModel and root Subject of the target iModel should not be transformed.
|
|
1751
|
+
await this.exporter.exportChildElements(core_common_1.IModel.rootSubjectId); // start below the root Subject
|
|
1752
|
+
await this.exporter.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true); // after the Subject hierarchy, process the other elements of the RepositoryModel
|
|
1753
|
+
await this.exporter.exportSubModels(core_common_1.IModel.repositoryModelId); // start below the RepositoryModel
|
|
1754
|
+
}
|
|
1755
|
+
else {
|
|
1756
|
+
await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
|
|
1757
|
+
}
|
|
1758
|
+
await this.completePartiallyCommittedElements();
|
|
1759
|
+
});
|
|
2203
1760
|
await this.completePartiallyCommittedAspects();
|
|
2204
1761
|
await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
2205
1762
|
if (this._options.forceExternalSourceAspectProvenance &&
|
|
2206
|
-
this.shouldDetectDeletes()) {
|
|
2207
|
-
|
|
2208
|
-
await this.detectElementDeletes();
|
|
2209
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
2210
|
-
await this.detectRelationshipDeletes();
|
|
1763
|
+
(await this.shouldDetectDeletes())) {
|
|
1764
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "This workflows was deprecated in v1 and is no longer supported");
|
|
2211
1765
|
}
|
|
2212
1766
|
if (this._options.optimizeGeometry)
|
|
2213
1767
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2214
1768
|
this.importer.computeProjectExtents();
|
|
2215
|
-
this.finalizeTransformation();
|
|
2216
|
-
}
|
|
2217
|
-
/** previous provenance, either a federation guid, a `${sourceFedGuid}/${targetFedGuid}` pair, or required aspect props */
|
|
2218
|
-
_lastProvenanceEntityInfo = nullLastProvenanceEntityInfo;
|
|
2219
|
-
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
2220
|
-
this._lastProvenanceEntityInfo =
|
|
2221
|
-
typeof sourceAspect === "string"
|
|
2222
|
-
? sourceAspect
|
|
2223
|
-
: {
|
|
2224
|
-
entityId: sourceAspect.element.id,
|
|
2225
|
-
aspectId: sourceAspect.id,
|
|
2226
|
-
aspectVersion: sourceAspect.version ?? "",
|
|
2227
|
-
aspectKind: isRelationship
|
|
2228
|
-
? core_backend_1.ExternalSourceAspect.Kind.Relationship
|
|
2229
|
-
: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
2230
|
-
};
|
|
1769
|
+
await this.finalizeTransformation();
|
|
2231
1770
|
}
|
|
2232
1771
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2233
1772
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
@@ -2239,25 +1778,33 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2239
1778
|
* @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range.
|
|
2240
1779
|
*/
|
|
2241
1780
|
async processChanges(options) {
|
|
1781
|
+
this._targetElementIdsRemappedByCode.clear();
|
|
1782
|
+
this._targetModelsImportedInCurrentTransform.clear();
|
|
2242
1783
|
// must wait for initialization of synchronization provenance data
|
|
2243
|
-
await this.exporter.exportChanges(this.getExportInitOpts(options));
|
|
1784
|
+
await this.exporter.exportChanges(await this.getExportInitOpts(options));
|
|
2244
1785
|
await this.completePartiallyCommittedElements();
|
|
2245
1786
|
await this.completePartiallyCommittedAspects();
|
|
2246
1787
|
if (this._options.optimizeGeometry)
|
|
2247
1788
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2248
1789
|
this.importer.computeProjectExtents();
|
|
2249
|
-
this.finalizeTransformation();
|
|
1790
|
+
await this.finalizeTransformation();
|
|
2250
1791
|
const defaultSaveTargetChanges = () => {
|
|
2251
|
-
this.
|
|
1792
|
+
this._targetEditTxn.saveChanges();
|
|
2252
1793
|
};
|
|
2253
1794
|
await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
2254
1795
|
}
|
|
2255
1796
|
/** Changeset data must be initialized in order to build correct changeOptions.
|
|
2256
1797
|
* Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
|
|
2257
1798
|
*/
|
|
2258
|
-
getExportInitOpts(opts) {
|
|
1799
|
+
async getExportInitOpts(opts) {
|
|
2259
1800
|
if (!this._options.argsForProcessChanges)
|
|
2260
1801
|
return {};
|
|
1802
|
+
if ("changedInstanceIds" in opts) {
|
|
1803
|
+
return {
|
|
1804
|
+
changedInstanceIds: opts.changedInstanceIds,
|
|
1805
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
2261
1808
|
const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
|
|
2262
1809
|
return {
|
|
2263
1810
|
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
@@ -2269,7 +1816,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2269
1816
|
? { startChangeset }
|
|
2270
1817
|
: {
|
|
2271
1818
|
startChangeset: {
|
|
2272
|
-
index: this.
|
|
1819
|
+
index: (await this.getSynchronizationVersion()).index + 1,
|
|
2273
1820
|
},
|
|
2274
1821
|
}),
|
|
2275
1822
|
};
|
|
@@ -2296,16 +1843,14 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2296
1843
|
/** Accumulates the mapping of sourceElementIds to targetElementIds from the elements in the template model that were cloned. */
|
|
2297
1844
|
_sourceIdToTargetIdMap;
|
|
2298
1845
|
/** Construct a new TemplateModelCloner
|
|
2299
|
-
* @param
|
|
2300
|
-
* @param targetDb Optionally specify the target IModelDb where the cloned template will be inserted.
|
|
2301
|
-
* Typically this is left unspecified, and the default is to use the sourceDb as the target
|
|
1846
|
+
* @param editTxn The [[EditTxn]] to use for all read and write operations. The source and target db are both derived from `editTxn.iModel`. Must be started before constructing.
|
|
2302
1847
|
* @note The expectation is that the template definitions are within the same iModel where instances will be placed.
|
|
2303
1848
|
*/
|
|
2304
|
-
constructor(
|
|
2305
|
-
const target = new IModelImporter_1.IModelImporter(
|
|
1849
|
+
constructor(editTxn) {
|
|
1850
|
+
const target = new IModelImporter_1.IModelImporter(editTxn, {
|
|
2306
1851
|
autoExtendProjectExtents: false, // autoExtendProjectExtents is intended for transformation service use cases, not template --> instance cloning
|
|
2307
1852
|
});
|
|
2308
|
-
super(
|
|
1853
|
+
super({ source: editTxn.iModel, target }, { noProvenance: true }); // WIP: need to decide the proper way to handle provenance
|
|
2309
1854
|
}
|
|
2310
1855
|
/** Place a template from the sourceDb at the specified placement in the target model within the targetDb.
|
|
2311
1856
|
* @param sourceTemplateModelId The Id of the template model in the sourceDb
|
|
@@ -2356,7 +1901,13 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2356
1901
|
// TODO: consider going through all definition elements at once and remapping them to themselves
|
|
2357
1902
|
if (!core_backend_1.EntityReferences.isValid(await this.context.findTargetEntityId(referenceId))) {
|
|
2358
1903
|
if (this.context.isBetweenIModels) {
|
|
2359
|
-
|
|
1904
|
+
core_bentley_1.ITwinError.throwError({
|
|
1905
|
+
iTwinErrorId: {
|
|
1906
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1907
|
+
key: IModelTransformerError_1.IModelTransformerError.DependencyMappingMissing,
|
|
1908
|
+
},
|
|
1909
|
+
message: `Remapping for source dependency ${referenceId} not found for target iModel`,
|
|
1910
|
+
});
|
|
2360
1911
|
}
|
|
2361
1912
|
else {
|
|
2362
1913
|
const definitionElement = this.sourceDb.elements.tryGetElement(referenceId, core_backend_1.DefinitionElement);
|
|
@@ -2365,7 +1916,13 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2365
1916
|
this.context.remapElement(referenceId, referenceId); // when in the same iModel, can use existing DefinitionElements without remapping
|
|
2366
1917
|
}
|
|
2367
1918
|
else {
|
|
2368
|
-
|
|
1919
|
+
core_bentley_1.ITwinError.throwError({
|
|
1920
|
+
iTwinErrorId: {
|
|
1921
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1922
|
+
key: IModelTransformerError_1.IModelTransformerError.DependencyMappingMissing,
|
|
1923
|
+
},
|
|
1924
|
+
message: `Remapping for dependency ${referenceId} not found`,
|
|
1925
|
+
});
|
|
2369
1926
|
}
|
|
2370
1927
|
}
|
|
2371
1928
|
}
|
|
@@ -2378,7 +1935,7 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2378
1935
|
const placementClass = is3d ? core_common_1.Placement3d : core_common_1.Placement2d;
|
|
2379
1936
|
const placement = placementClass.fromJSON(targetElementProps.placement);
|
|
2380
1937
|
if (placement.isValid) {
|
|
2381
|
-
|
|
1938
|
+
(0, node_assert_1.strict)(this._transform3d);
|
|
2382
1939
|
placement.multiplyTransform(this._transform3d);
|
|
2383
1940
|
targetElementProps.placement = placement;
|
|
2384
1941
|
}
|
|
@@ -2388,19 +1945,4 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2388
1945
|
}
|
|
2389
1946
|
}
|
|
2390
1947
|
exports.TemplateModelCloner = TemplateModelCloner;
|
|
2391
|
-
//Deprecate in preference of imodeldb.elements.getFederationGuidFromId()?
|
|
2392
|
-
function queryElemFedGuid(db, elemId) {
|
|
2393
|
-
// eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
|
|
2394
|
-
return db.withPreparedStatement(`
|
|
2395
|
-
SELECT FederationGuid
|
|
2396
|
-
FROM bis.Element
|
|
2397
|
-
WHERE ECInstanceId=?
|
|
2398
|
-
`, (stmt) => {
|
|
2399
|
-
stmt.bindId(1, elemId);
|
|
2400
|
-
(0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW);
|
|
2401
|
-
const result = stmt.getValue(0).getGuid();
|
|
2402
|
-
(0, core_bentley_1.assert)(stmt.step() === core_bentley_1.DbResult.BE_SQLITE_DONE);
|
|
2403
|
-
return result;
|
|
2404
|
-
});
|
|
2405
|
-
}
|
|
2406
1948
|
//# sourceMappingURL=IModelTransformer.js.map
|