@itwin/imodel-transformer 2.0.0-dev.8 → 2.0.0-experimental.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -27
- package/lib/cjs/Algo.d.ts +0 -4
- package/lib/cjs/Algo.d.ts.map +1 -1
- package/lib/cjs/Algo.js +0 -11
- package/lib/cjs/Algo.js.map +1 -1
- package/lib/cjs/BigMap.d.ts.map +1 -1
- package/lib/cjs/BigMap.js.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +26 -26
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/ChangedElementForest.d.ts +14 -0
- package/lib/cjs/ChangedElementForest.d.ts.map +1 -0
- package/lib/cjs/ChangedElementForest.js +86 -0
- package/lib/cjs/ChangedElementForest.js.map +1 -0
- package/lib/cjs/ChangesetScanner.d.ts +61 -0
- package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
- package/lib/cjs/ChangesetScanner.js +104 -0
- package/lib/cjs/ChangesetScanner.js.map +1 -0
- package/lib/cjs/ECReferenceTypesCache.d.ts +0 -4
- package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
- package/lib/cjs/ECReferenceTypesCache.js +54 -102
- package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts.map +1 -1
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
- package/lib/cjs/ElementAspectCleanup.d.ts +56 -0
- package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
- package/lib/cjs/ElementAspectCleanup.js +175 -0
- package/lib/cjs/ElementAspectCleanup.js.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts +47 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.js +160 -0
- package/lib/cjs/ElementAspectExportCoordinator.js.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts +44 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.js +152 -0
- package/lib/cjs/ElementAspectExportProcessor.js.map +1 -0
- package/lib/cjs/ElementBulkDelete.d.ts +30 -0
- package/lib/cjs/ElementBulkDelete.d.ts.map +1 -0
- package/lib/cjs/ElementBulkDelete.js +358 -0
- package/lib/cjs/ElementBulkDelete.js.map +1 -0
- package/lib/cjs/EntityExistenceCache.d.ts +43 -0
- package/lib/cjs/EntityExistenceCache.d.ts.map +1 -0
- package/lib/cjs/EntityExistenceCache.js +91 -0
- package/lib/cjs/EntityExistenceCache.js.map +1 -0
- package/lib/cjs/EntityUnifier.d.ts +7 -7
- package/lib/cjs/EntityUnifier.d.ts.map +1 -1
- package/lib/cjs/EntityUnifier.js +55 -24
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts +8 -2
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +42 -12
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +143 -30
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +871 -172
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +87 -31
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +361 -139
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformContext.d.ts +54 -0
- package/lib/cjs/IModelTransformContext.d.ts.map +1 -0
- package/lib/cjs/IModelTransformContext.js +3 -0
- package/lib/cjs/IModelTransformContext.js.map +1 -0
- package/lib/cjs/IModelTransformer.d.ts +132 -174
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +679 -1025
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/IModelTransformerError.d.ts +84 -0
- package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
- package/lib/cjs/IModelTransformerError.js +93 -0
- package/lib/cjs/IModelTransformerError.js.map +1 -0
- package/lib/cjs/ProvenanceManager.d.ts +194 -0
- package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
- package/lib/cjs/ProvenanceManager.js +771 -0
- package/lib/cjs/ProvenanceManager.js.map +1 -0
- package/lib/cjs/SyncTypeResolver.d.ts +32 -0
- package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
- package/lib/cjs/SyncTypeResolver.js +92 -0
- package/lib/cjs/SyncTypeResolver.js.map +1 -0
- package/lib/cjs/TransformerLoggerCategory.d.ts +1 -1
- package/lib/cjs/TransformerLoggerCategory.js +1 -1
- package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
- package/lib/cjs/TransformerPackageMetadata.d.ts +8 -0
- package/lib/cjs/TransformerPackageMetadata.d.ts.map +1 -0
- package/lib/cjs/TransformerPackageMetadata.js +47 -0
- package/lib/cjs/TransformerPackageMetadata.js.map +1 -0
- package/lib/cjs/imodel-transformer.d.ts +27 -0
- package/lib/cjs/imodel-transformer.d.ts.map +1 -1
- package/lib/cjs/imodel-transformer.js +61 -34
- package/lib/cjs/imodel-transformer.js.map +1 -1
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts +29 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts.map +1 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js +212 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts +25 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js +196 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts +59 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.js +28 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.js.map +1 -0
- package/lib/cjs/schema-processing.d.ts +7 -0
- package/lib/cjs/schema-processing.d.ts.map +1 -0
- package/lib/cjs/schema-processing.js +27 -0
- package/lib/cjs/schema-processing.js.map +1 -0
- package/package.json +70 -57
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +0 -16
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +0 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.js +0 -106
- package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +0 -1
- package/lib/cjs/ElementCascadingDeleter.d.ts +0 -22
- package/lib/cjs/ElementCascadingDeleter.d.ts.map +0 -1
- package/lib/cjs/ElementCascadingDeleter.js +0 -57
- package/lib/cjs/ElementCascadingDeleter.js.map +0 -1
- package/lib/cjs/EntityMap.d.ts +0 -26
- package/lib/cjs/EntityMap.d.ts.map +0 -1
- package/lib/cjs/EntityMap.js +0 -54
- package/lib/cjs/EntityMap.js.map +0 -1
- package/lib/cjs/ExportElementAspectsStrategy.d.ts +0 -35
- package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +0 -1
- package/lib/cjs/ExportElementAspectsStrategy.js +0 -52
- package/lib/cjs/ExportElementAspectsStrategy.js.map +0 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +0 -12
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +0 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +0 -47
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +0 -1
|
@@ -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,132 +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 async 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();
|
|
125
|
-
// params.bindId("elementId", aspectProps.element.id);
|
|
126
|
-
// params.bindId("scopeId", aspectProps.scope.id);
|
|
127
|
-
// params.bindString("kind", aspectProps.kind);
|
|
128
|
-
// params.bindString("identifier", aspectProps.identifier);
|
|
129
|
-
// const reader = dbToQuery.createQueryReader(sql, params, {usePrimaryConn: true});
|
|
130
|
-
// if (await reader.step()) {
|
|
131
|
-
// const aspectId = reader.current.id;
|
|
132
|
-
// const version = reader.current.version as string | undefined;
|
|
133
|
-
// const jsonProperties = reader.current.jsonProperties as string | undefined;
|
|
134
|
-
// return { aspectId, version, jsonProperties };
|
|
135
|
-
// }
|
|
136
|
-
// return undefined;
|
|
137
|
-
// eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
|
|
138
|
-
return dbToQuery.withPreparedStatement(sql, (statement) => {
|
|
139
|
-
statement.bindId("elementId", aspectProps.element.id);
|
|
140
|
-
if (aspectProps.scope === undefined)
|
|
141
|
-
return undefined; // return instead of binding an invalid id
|
|
142
|
-
statement.bindId("scopeId", aspectProps.scope.id);
|
|
143
|
-
statement.bindString("kind", aspectProps.kind);
|
|
144
|
-
statement.bindString("identifier", aspectProps.identifier);
|
|
145
|
-
if (core_bentley_1.DbResult.BE_SQLITE_ROW !== statement.step())
|
|
146
|
-
return undefined;
|
|
147
|
-
const aspectId = statement.getValue(0).getId();
|
|
148
|
-
const versionValue = statement.getValue(1);
|
|
149
|
-
const version = versionValue.isNull
|
|
150
|
-
? undefined
|
|
151
|
-
: versionValue.getString();
|
|
152
|
-
const jsonPropsValue = statement.getValue(2);
|
|
153
|
-
const jsonProperties = jsonPropsValue.isNull
|
|
154
|
-
? undefined
|
|
155
|
-
: jsonPropsValue.getString();
|
|
156
|
-
return { aspectId, version, jsonProperties };
|
|
157
|
-
});
|
|
158
|
-
}
|
|
158
|
+
_targetElementIdsRemappedByCode = new Set();
|
|
159
159
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* @throws if no scoping ESA can be found in either the sourceDb or targetDb which describes a master branch relationship between the two databases.
|
|
164
|
-
* @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).
|
|
165
163
|
*/
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
-
const aspectProps = {
|
|
170
|
-
id: undefined,
|
|
171
|
-
version: undefined,
|
|
172
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
173
|
-
element: {
|
|
174
|
-
id: targetScopeElementId,
|
|
175
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
176
|
-
},
|
|
177
|
-
scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
|
|
178
|
-
identifier: sourceDb.iModelId,
|
|
179
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
180
|
-
jsonProperties: undefined,
|
|
181
|
-
};
|
|
182
|
-
/** First check if the targetDb is the branch (branch is the @see provenanceDb) */
|
|
183
|
-
const esaPropsFromTargetDb = await this.queryScopeExternalSourceAspect(targetDb, aspectProps);
|
|
184
|
-
if (esaPropsFromTargetDb !== undefined) {
|
|
185
|
-
return "forward"; // we found an esa assuming targetDb is the provenanceDb/branch so this is a forward sync.
|
|
186
|
-
}
|
|
187
|
-
// Now check if the sourceDb is the branch
|
|
188
|
-
aspectProps.identifier = targetDb.iModelId;
|
|
189
|
-
const esaPropsFromSourceDb = await this.queryScopeExternalSourceAspect(sourceDb, aspectProps);
|
|
190
|
-
if (esaPropsFromSourceDb !== undefined) {
|
|
191
|
-
return "reverse"; // we found an esa assuming sourceDb is the provenanceDb/branch so this is a reverse sync.
|
|
192
|
-
}
|
|
193
|
-
throw new Error(this.noEsaSyncDirectionErrorMessage);
|
|
194
|
-
}
|
|
195
|
-
async determineSyncType() {
|
|
196
|
-
if (this._isProvenanceInitTransform) {
|
|
197
|
-
return "forward";
|
|
198
|
-
}
|
|
199
|
-
if (!this._options.argsForProcessChanges) {
|
|
200
|
-
return "not-sync";
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
return await IModelTransformer.determineSyncType(this.sourceDb, this.targetDb, this.targetScopeElementId);
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
if (err instanceof Error &&
|
|
207
|
-
err.message === IModelTransformer.noEsaSyncDirectionErrorMessage &&
|
|
208
|
-
this._allowNoScopingESA) {
|
|
209
|
-
return "forward";
|
|
210
|
-
}
|
|
211
|
-
throw err;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
async getIsReverseSynchronization() {
|
|
215
|
-
if (this._syncType === undefined)
|
|
216
|
-
this._syncType = await this.determineSyncType();
|
|
217
|
-
return this._syncType === "reverse";
|
|
218
|
-
}
|
|
219
|
-
async getIsForwardSynchronization() {
|
|
220
|
-
if (this._syncType === undefined)
|
|
221
|
-
this._syncType = await this.determineSyncType();
|
|
222
|
-
return this._syncType === "forward";
|
|
223
|
-
}
|
|
164
|
+
_targetModelsImportedInCurrentTransform = new Set();
|
|
165
|
+
/** the options that were used to initialize this transformer */
|
|
166
|
+
_options;
|
|
224
167
|
_changesetRanges = undefined;
|
|
225
168
|
/**
|
|
226
169
|
* Set if the transformer is being used to perform the provenance initialization step of a fork initialization.
|
|
@@ -229,24 +172,19 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
229
172
|
_isProvenanceInitTransform;
|
|
230
173
|
/** The element classes that are considered to define provenance in the iModel */
|
|
231
174
|
static get provenanceElementClasses() {
|
|
232
|
-
return
|
|
233
|
-
core_backend_1.FolderLink,
|
|
234
|
-
core_backend_1.SynchronizationConfigLink,
|
|
235
|
-
core_backend_1.ExternalSource,
|
|
236
|
-
core_backend_1.ExternalSourceAttachment,
|
|
237
|
-
];
|
|
175
|
+
return ProvenanceManager_1.ProvenanceManager.provenanceElementClasses;
|
|
238
176
|
}
|
|
239
177
|
/** The element aspect classes that are considered to define provenance in the iModel */
|
|
240
178
|
static get provenanceElementAspectClasses() {
|
|
241
|
-
return
|
|
179
|
+
return ProvenanceManager_1.ProvenanceManager.provenanceElementAspectClasses;
|
|
242
180
|
}
|
|
243
181
|
/** Construct a new IModelTransformer
|
|
244
|
-
* @param
|
|
245
|
-
* @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.
|
|
246
183
|
* @param options The options that specify how the transformation should be done.
|
|
247
184
|
*/
|
|
248
|
-
constructor(
|
|
185
|
+
constructor(args, options) {
|
|
249
186
|
super();
|
|
187
|
+
const { source, target } = args;
|
|
250
188
|
// initialize IModelTransformOptions
|
|
251
189
|
this._options = {
|
|
252
190
|
...options,
|
|
@@ -284,7 +222,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
284
222
|
this.exporter.excludeElementAspectClass(core_backend_1.ChannelRootAspect.classFullName); // Channel boundaries within the source iModel are not relevant to the target iModel
|
|
285
223
|
this.exporter.excludeElementAspectClass("BisCore:TextAnnotationData"); // This ElementAspect is auto-created by the BisCore:TextAnnotation2d/3d element handlers
|
|
286
224
|
// initialize importer and targetDb
|
|
287
|
-
if (target instanceof core_backend_1.
|
|
225
|
+
if (target instanceof core_backend_1.EditTxn) {
|
|
288
226
|
this.importer = new IModelImporter_1.IModelImporter(target, {
|
|
289
227
|
preserveElementIdsForFiltering: this._options.preserveElementIdsForFiltering,
|
|
290
228
|
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
@@ -295,23 +233,22 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
295
233
|
this.validateSharedOptionsMatch();
|
|
296
234
|
}
|
|
297
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));
|
|
298
246
|
// create the IModelCloneContext, it must be initialized later
|
|
299
|
-
this.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
target: this.targetDb.changeset.index,
|
|
305
|
-
source: this.sourceDb.changeset.index,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
// this internal is guaranteed stable for just transformer usage
|
|
309
|
-
/* eslint-disable @itwin/no-internal */
|
|
310
|
-
if (("codeValueBehavior" in this.sourceDb)) {
|
|
311
|
-
this.sourceDb.codeValueBehavior = "exact";
|
|
312
|
-
this.targetDb.codeValueBehavior = "exact";
|
|
313
|
-
}
|
|
314
|
-
/* 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);
|
|
315
252
|
if (this._options.tryAlignGeolocation) {
|
|
316
253
|
if (this.sourceDb.geographicCoordinateSystem ||
|
|
317
254
|
this.targetDb.geographicCoordinateSystem) {
|
|
@@ -333,19 +270,31 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
333
270
|
validateSharedOptionsMatch() {
|
|
334
271
|
if (Boolean(this._options.preserveElementIdsForFiltering) !==
|
|
335
272
|
this.importer.options.preserveElementIdsForFiltering) {
|
|
336
|
-
|
|
337
|
-
|
|
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
|
+
});
|
|
338
280
|
}
|
|
339
281
|
if (Boolean(this._options.skipPropagateChangesToRootElements) !==
|
|
340
282
|
this.importer.options.skipPropagateChangesToRootElements) {
|
|
341
|
-
|
|
342
|
-
|
|
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
|
+
});
|
|
343
290
|
}
|
|
344
291
|
}
|
|
345
292
|
/** Dispose any native resources associated with this IModelTransformer. */
|
|
346
293
|
dispose() {
|
|
347
294
|
core_bentley_1.Logger.logTrace(loggerCategory, "dispose()");
|
|
348
|
-
this.
|
|
295
|
+
this.importer.unregisterEntityExistenceCache(this._cloneContext.existenceCache);
|
|
296
|
+
this._cloneContext.existenceCache.clear();
|
|
297
|
+
this._cloneContext[Symbol.dispose]();
|
|
349
298
|
}
|
|
350
299
|
/** Log current settings that affect IModelTransformer's behavior. */
|
|
351
300
|
logSettings() {
|
|
@@ -362,79 +311,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
362
311
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
363
312
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
364
313
|
}
|
|
365
|
-
/** Return the IModelDb where IModelTransformer will store its provenance.
|
|
366
|
-
* @note This will be [[targetDb]] except when it is a reverse synchronization. In that case it be [[sourceDb]].
|
|
367
|
-
*/
|
|
368
|
-
async getProvenanceDb() {
|
|
369
|
-
return (await this.getIsReverseSynchronization())
|
|
370
|
-
? this.sourceDb
|
|
371
|
-
: this.targetDb;
|
|
372
|
-
}
|
|
373
|
-
/** Return the IModelDb where IModelTransformer looks for entities referred to by stored provenance.
|
|
374
|
-
* @note This will be [[sourceDb]] except when it is a reverse synchronization. In that case it be [[targetDb]].
|
|
375
|
-
*/
|
|
376
|
-
async getProvenanceSourceDb() {
|
|
377
|
-
return (await this.getIsReverseSynchronization())
|
|
378
|
-
? this.targetDb
|
|
379
|
-
: this.sourceDb;
|
|
380
|
-
}
|
|
381
|
-
/** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
|
|
382
|
-
static initElementProvenanceOptions(sourceElementId, targetElementId, args) {
|
|
383
|
-
const elementId = args.isReverseSynchronization
|
|
384
|
-
? sourceElementId
|
|
385
|
-
: targetElementId;
|
|
386
|
-
const version = args.isReverseSynchronization
|
|
387
|
-
? args.targetDb.elements.queryLastModifiedTime(targetElementId)
|
|
388
|
-
: args.sourceDb.elements.queryLastModifiedTime(sourceElementId);
|
|
389
|
-
const aspectIdentifier = args.isReverseSynchronization
|
|
390
|
-
? targetElementId
|
|
391
|
-
: sourceElementId;
|
|
392
|
-
const aspectProps = {
|
|
393
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
394
|
-
element: {
|
|
395
|
-
id: elementId,
|
|
396
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
397
|
-
},
|
|
398
|
-
scope: { id: args.targetScopeElementId },
|
|
399
|
-
identifier: aspectIdentifier,
|
|
400
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
401
|
-
version,
|
|
402
|
-
};
|
|
403
|
-
return aspectProps;
|
|
404
|
-
}
|
|
405
|
-
static async initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, args) {
|
|
406
|
-
const provenanceDb = args.isReverseSynchronization
|
|
407
|
-
? args.sourceDb
|
|
408
|
-
: args.targetDb;
|
|
409
|
-
const aspectIdentifier = args.isReverseSynchronization
|
|
410
|
-
? targetRelInstanceId
|
|
411
|
-
: sourceRelInstanceId;
|
|
412
|
-
const provenanceRelInstanceId = args.isReverseSynchronization
|
|
413
|
-
? sourceRelInstanceId
|
|
414
|
-
: targetRelInstanceId;
|
|
415
|
-
const sql = "SELECT SourceECInstanceId FROM bis.ElementRefersToElements WHERE ECInstanceId=?";
|
|
416
|
-
const params = new core_common_1.QueryBinder().bindId(1, provenanceRelInstanceId);
|
|
417
|
-
const reader = provenanceDb.createQueryReader(sql, params, {
|
|
418
|
-
usePrimaryConn: true,
|
|
419
|
-
});
|
|
420
|
-
nodeAssert(await reader.step(), "relationship provenance query returned no rows");
|
|
421
|
-
const elementId = reader.current[0];
|
|
422
|
-
const jsonProperties = args.forceOldRelationshipProvenanceMethod
|
|
423
|
-
? { targetRelInstanceId }
|
|
424
|
-
: { provenanceRelInstanceId };
|
|
425
|
-
const aspectProps = {
|
|
426
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
427
|
-
element: {
|
|
428
|
-
id: elementId,
|
|
429
|
-
relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
|
|
430
|
-
},
|
|
431
|
-
scope: { id: args.targetScopeElementId },
|
|
432
|
-
identifier: aspectIdentifier,
|
|
433
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
|
|
434
|
-
jsonProperties: JSON.stringify(jsonProperties),
|
|
435
|
-
};
|
|
436
|
-
return aspectProps;
|
|
437
|
-
}
|
|
438
314
|
/**
|
|
439
315
|
* Previously the transformer would insert provenance always pointing to the "target" relationship.
|
|
440
316
|
* It should (and now by default does) instead insert provenance pointing to the provenanceSource
|
|
@@ -444,42 +320,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
444
320
|
_forceOldRelationshipProvenanceMethod = false;
|
|
445
321
|
/** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
|
|
446
322
|
async initElementProvenance(sourceElementId, targetElementId) {
|
|
447
|
-
return
|
|
448
|
-
isReverseSynchronization: await this.getIsReverseSynchronization(),
|
|
449
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
450
|
-
sourceDb: this.sourceDb,
|
|
451
|
-
targetDb: this.targetDb,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
/** Create an ExternalSourceAspectProps in a standard way for a Relationship in an iModel --> iModel transformations.
|
|
455
|
-
* The ExternalSourceAspect is meant to be owned by the Element in the target iModel that is the `sourceId` of transformed relationship.
|
|
456
|
-
* The `identifier` property of the ExternalSourceAspect will be the ECInstanceId of the relationship in the master iModel.
|
|
457
|
-
* The ECInstanceId of the relationship in the branch iModel will be stored in the JsonProperties of the ExternalSourceAspect.
|
|
458
|
-
*/
|
|
459
|
-
async initRelationshipProvenance(sourceRelationship, targetRelInstanceId) {
|
|
460
|
-
return IModelTransformer.initRelationshipProvenanceOptions(sourceRelationship.id, targetRelInstanceId, {
|
|
461
|
-
sourceDb: this.sourceDb,
|
|
462
|
-
targetDb: this.targetDb,
|
|
463
|
-
isReverseSynchronization: await this.getIsReverseSynchronization(),
|
|
464
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
465
|
-
forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
/** NOTE: the json properties must be converted to string before insertion */
|
|
469
|
-
_targetScopeProvenanceProps = undefined;
|
|
470
|
-
/**
|
|
471
|
-
* Index of the changeset that the transformer was at when the transformation begins (was constructed).
|
|
472
|
-
* Used to determine at the end which changesets were part of a synchronization.
|
|
473
|
-
*/
|
|
474
|
-
_startingChangesetIndices = undefined;
|
|
475
|
-
_cachedSynchronizationVersion = undefined;
|
|
476
|
-
/**
|
|
477
|
-
* We cache the synchronization version to avoid querying the target scoping ESA multiple times.
|
|
478
|
-
* If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
|
|
479
|
-
* Sets this._cachedSynchronizationVersion to undefined.
|
|
480
|
-
*/
|
|
481
|
-
clearCachedSynchronizationVersion() {
|
|
482
|
-
this._cachedSynchronizationVersion = undefined;
|
|
323
|
+
return this._provenanceManager.initElementProvenance(sourceElementId, targetElementId);
|
|
483
324
|
}
|
|
484
325
|
/** the changeset in the scoping element's source version found for this transformation
|
|
485
326
|
* @note the version depends on whether this is a reverse synchronization or not, as
|
|
@@ -489,394 +330,58 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
489
330
|
* @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
|
|
490
331
|
*/
|
|
491
332
|
async getSynchronizationVersion() {
|
|
492
|
-
|
|
493
|
-
const provenanceScopeAspect = await this.tryGetProvenanceScopeAspect();
|
|
494
|
-
if (!provenanceScopeAspect) {
|
|
495
|
-
return { index: -1, id: "" }; // first synchronization.
|
|
496
|
-
}
|
|
497
|
-
const version = (await this.getIsReverseSynchronization())
|
|
498
|
-
? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
|
|
499
|
-
: provenanceScopeAspect.version;
|
|
500
|
-
if (!version &&
|
|
501
|
-
this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
|
|
502
|
-
return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
|
|
503
|
-
}
|
|
504
|
-
if (version === undefined) {
|
|
505
|
-
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.
|
|
506
|
-
Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
|
|
507
|
-
}
|
|
508
|
-
const [id, index] = version === "" ? ["", -1] : version.split(";");
|
|
509
|
-
if (Number.isNaN(Number(index)))
|
|
510
|
-
throw new Error("Could not parse version data from scope aspect");
|
|
511
|
-
this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
|
|
512
|
-
}
|
|
513
|
-
return this._cachedSynchronizationVersion;
|
|
333
|
+
return this._provenanceManager.getSynchronizationVersion();
|
|
514
334
|
}
|
|
515
335
|
/**
|
|
516
336
|
* @returns provenance scope aspect if it exists in the provenanceDb.
|
|
517
337
|
* Provenance scope aspect is created and inserted into provenanceDb when [[initScopeProvenance]] is invoked.
|
|
518
338
|
*/
|
|
519
339
|
async tryGetProvenanceScopeAspect() {
|
|
520
|
-
|
|
521
|
-
id: undefined,
|
|
522
|
-
classFullName: core_backend_1.ExternalSourceAspect.classFullName,
|
|
523
|
-
scope: { id: core_common_1.IModel.rootSubjectId },
|
|
524
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
525
|
-
element: { id: this.targetScopeElementId ?? core_common_1.IModel.rootSubjectId },
|
|
526
|
-
identifier: (await this.getProvenanceSourceDb()).iModelId,
|
|
527
|
-
});
|
|
528
|
-
return scopeProvenanceAspectProps !== undefined
|
|
529
|
-
? (await this.getProvenanceDb()).elements.getAspect(scopeProvenanceAspectProps.aspectId)
|
|
530
|
-
: undefined;
|
|
340
|
+
return this._provenanceManager.tryGetProvenanceScopeAspect();
|
|
531
341
|
}
|
|
532
342
|
/**
|
|
533
343
|
* Make sure there are no conflicting other scope-type external source aspects on the *target scope element*,
|
|
534
344
|
* If there are none at all, insert one, then this must be a first synchronization.
|
|
535
|
-
* @returns the last synced version (changesetId) on the target scope's external source aspect,
|
|
536
|
-
* if this was a [BriefcaseDb]($backend)
|
|
537
345
|
*/
|
|
538
346
|
async initScopeProvenance() {
|
|
539
|
-
|
|
540
|
-
const sourceProvenanceDb = await this.getProvenanceSourceDb();
|
|
541
|
-
const aspectProps = {
|
|
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: sourceProvenanceDb.iModelId,
|
|
551
|
-
kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
|
|
552
|
-
jsonProperties: undefined,
|
|
553
|
-
};
|
|
554
|
-
const foundEsaProps = await IModelTransformer.queryScopeExternalSourceAspect(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 = 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 = 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
|
-
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, undefined, { usePrimaryConn: true });
|
|
696
|
-
const targetReader = targetDb.createQueryReader(elementIdByFedGuidQuery, undefined, { usePrimaryConn: true });
|
|
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, { usePrimaryConn: true });
|
|
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 IModelTransformer.forEachTrackedElement({
|
|
743
|
-
provenanceSourceDb: await this.getProvenanceSourceDb(),
|
|
744
|
-
provenanceDb: await this.getProvenanceDb(),
|
|
745
|
-
targetScopeElementId: this.targetScopeElementId,
|
|
746
|
-
isReverseSynchronization: await this.getIsReverseSynchronization(),
|
|
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
|
-
SELECT esa.Element.Id
|
|
761
|
-
FROM Bis.ExternalSourceAspect esa
|
|
762
|
-
WHERE esa.Kind=?
|
|
763
|
-
AND esa.Scope.Id=?
|
|
764
|
-
AND esa.Identifier=?
|
|
765
|
-
`;
|
|
766
|
-
const params = new core_common_1.QueryBinder();
|
|
767
|
-
params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Element);
|
|
768
|
-
params.bindId(2, this.targetScopeElementId);
|
|
769
|
-
params.bindString(3, entityInProvenanceSourceId);
|
|
770
|
-
const result = (await this.getProvenanceDb()).createQueryReader(sql, params, {
|
|
771
|
-
usePrimaryConn: true,
|
|
772
|
-
});
|
|
773
|
-
if (await result.step()) {
|
|
774
|
-
return result.current.id;
|
|
775
|
-
}
|
|
776
|
-
else
|
|
777
|
-
return undefined;
|
|
361
|
+
async getIsReverseSynchronization() {
|
|
362
|
+
return (await this._syncTypeResolver.getSyncType()) === "reverse";
|
|
778
363
|
}
|
|
779
364
|
/**
|
|
780
|
-
*
|
|
781
|
-
* The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
|
|
782
|
-
* Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
|
|
783
|
-
* @param entityInProvenanceSourceId
|
|
784
|
-
* @returns
|
|
365
|
+
* Get whether this is a forward synchronization.
|
|
785
366
|
*/
|
|
786
|
-
async
|
|
787
|
-
|
|
788
|
-
SELECT
|
|
789
|
-
ECInstanceId,
|
|
790
|
-
JSON_EXTRACT(JsonProperties, '$.provenanceRelInstanceId') AS provenanceRelInstId
|
|
791
|
-
FROM Bis.ExternalSourceAspect
|
|
792
|
-
WHERE Kind=?
|
|
793
|
-
AND Scope.Id=?
|
|
794
|
-
AND Identifier=?
|
|
795
|
-
`;
|
|
796
|
-
const params = new core_common_1.QueryBinder();
|
|
797
|
-
params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Relationship);
|
|
798
|
-
params.bindId(2, this.targetScopeElementId);
|
|
799
|
-
params.bindString(3, entityInProvenanceSourceId);
|
|
800
|
-
const result = (await this.getProvenanceDb()).createQueryReader(sql, params, {
|
|
801
|
-
usePrimaryConn: true,
|
|
802
|
-
});
|
|
803
|
-
if (await result.step()) {
|
|
804
|
-
const aspectId = result.current.id;
|
|
805
|
-
const provenanceRelInstId = result.current.provenanceRelInstId;
|
|
806
|
-
const provenanceRelInstanceId = provenanceRelInstId !== undefined
|
|
807
|
-
? provenanceRelInstId
|
|
808
|
-
: await this._queryTargetRelId(sourceRelInfo);
|
|
809
|
-
return {
|
|
810
|
-
aspectId,
|
|
811
|
-
relationshipId: provenanceRelInstanceId,
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
else
|
|
815
|
-
return undefined;
|
|
367
|
+
async getIsForwardSynchronization() {
|
|
368
|
+
return (await this._syncTypeResolver.getSyncType()) === "forward";
|
|
816
369
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return undefined; // couldn't find an element, rel is invalid or deleted
|
|
825
|
-
const sql = `
|
|
826
|
-
select ecinstanceid
|
|
827
|
-
from bis.elementreferstoelements
|
|
828
|
-
where sourceecinstanceid=?
|
|
829
|
-
and targetecinstanceid=?
|
|
830
|
-
and ecclassid=?
|
|
831
|
-
`;
|
|
832
|
-
const params = new core_common_1.QueryBinder();
|
|
833
|
-
params.bindId(1, targetRelInfo.sourceId);
|
|
834
|
-
params.bindId(2, targetRelInfo.targetId);
|
|
835
|
-
params.bindId(3, await this._targetClassNameToClassId(sourceRelInfo.classFullName));
|
|
836
|
-
const result = this.targetDb.createQueryReader(sql, params, {
|
|
837
|
-
usePrimaryConn: true,
|
|
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,
|
|
838
377
|
});
|
|
839
|
-
if (await result.step())
|
|
840
|
-
return result.current.id;
|
|
841
|
-
else
|
|
842
|
-
return undefined;
|
|
843
|
-
}
|
|
844
|
-
_targetClassNameToClassIdCache = new Map();
|
|
845
|
-
async _targetClassNameToClassId(classFullName) {
|
|
846
|
-
let classId = this._targetClassNameToClassIdCache.get(classFullName);
|
|
847
|
-
if (classId === undefined) {
|
|
848
|
-
classId = await this._getRelClassId(this.targetDb, classFullName);
|
|
849
|
-
this._targetClassNameToClassIdCache.set(classFullName, classId);
|
|
850
|
-
}
|
|
851
|
-
return classId;
|
|
852
|
-
}
|
|
853
|
-
// NOTE: this doesn't handle remapped element classes,
|
|
854
|
-
// but is only used for relationships rn
|
|
855
|
-
async _getRelClassId(db, classFullName) {
|
|
856
|
-
const sql = `
|
|
857
|
-
SELECT c.ECInstanceId
|
|
858
|
-
FROM ECDbMeta.ECClassDef c
|
|
859
|
-
JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
|
|
860
|
-
WHERE s.Name=? AND c.Name=?
|
|
861
|
-
`;
|
|
862
|
-
const params = new core_common_1.QueryBinder();
|
|
863
|
-
const [schemaName, className] = classFullName.indexOf(".") !== -1
|
|
864
|
-
? classFullName.split(".")
|
|
865
|
-
: classFullName.split(":");
|
|
866
|
-
params.bindString(1, schemaName);
|
|
867
|
-
params.bindString(2, className);
|
|
868
|
-
const result = db.createQueryReader(sql, params, { usePrimaryConn: true });
|
|
869
|
-
if (await result.step())
|
|
870
|
-
return result.current.id;
|
|
871
|
-
(0, core_bentley_1.assert)(false, "relationship was not found");
|
|
872
378
|
}
|
|
873
379
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
874
380
|
* @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
|
|
875
381
|
* @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
|
|
876
382
|
*/
|
|
877
|
-
shouldDetectDeletes() {
|
|
878
|
-
|
|
879
|
-
return this._syncType === "not-sync";
|
|
383
|
+
async shouldDetectDeletes() {
|
|
384
|
+
return (await this._syncTypeResolver.getSyncType()) === "not-sync";
|
|
880
385
|
}
|
|
881
386
|
/** Transform the specified sourceElement into ElementProps for the target iModel.
|
|
882
387
|
* @param sourceElement The Element from the source iModel to transform.
|
|
@@ -886,7 +391,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
886
391
|
*/
|
|
887
392
|
async onTransformElement(sourceElement) {
|
|
888
393
|
core_bentley_1.Logger.logTrace(loggerCategory, `onTransformElement(${sourceElement.id}) "${sourceElement.getDisplayLabel()}"`);
|
|
889
|
-
const targetElementProps = await this.
|
|
394
|
+
const targetElementProps = await this._cloneContext.cloneElement(sourceElement, {
|
|
395
|
+
binaryGeometry: this._options.cloneUsingBinaryGeometry,
|
|
396
|
+
});
|
|
890
397
|
// Special case: source element is the root subject
|
|
891
398
|
if (sourceElement.id === core_common_1.IModel.rootSubjectId) {
|
|
892
399
|
const targetElementId = this.context.findTargetElementId(sourceElement.id);
|
|
@@ -929,7 +436,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
929
436
|
const srcEcefLoc = this.sourceDb.ecefLocation;
|
|
930
437
|
const targetEcefLoc = this.targetDb.ecefLocation;
|
|
931
438
|
if (srcEcefLoc === undefined || targetEcefLoc === undefined) {
|
|
932
|
-
|
|
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
|
+
});
|
|
933
446
|
}
|
|
934
447
|
if (srcEcefLoc.getTransform().isAlmostEqual(targetEcefLoc.getTransform())) {
|
|
935
448
|
core_bentley_1.Logger.logTrace(loggerCategory, "ECEF data is already aligned. No spatial transforms needed.");
|
|
@@ -938,7 +451,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
938
451
|
const srcSpatialToECEF = srcEcefLoc.getTransform(); // converts relative to ECEF in relation to source
|
|
939
452
|
const targetECEFToSpatial = targetEcefLoc.getTransform().inverse(); // converts ECEF to relative in relation to target
|
|
940
453
|
if (!targetECEFToSpatial) {
|
|
941
|
-
|
|
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
|
+
});
|
|
942
461
|
}
|
|
943
462
|
const ecefTransform = targetECEFToSpatial.multiplyTransformTransform(srcSpatialToECEF); // chain both transforms
|
|
944
463
|
return ecefTransform;
|
|
@@ -956,15 +475,33 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
956
475
|
calculateTransformFromHelmertTransforms() {
|
|
957
476
|
if (this.sourceDb.geographicCoordinateSystem?.horizontalCRS === undefined ||
|
|
958
477
|
this.sourceDb.geographicCoordinateSystem?.verticalCRS === undefined) {
|
|
959
|
-
|
|
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
|
+
});
|
|
960
485
|
}
|
|
961
486
|
if (this.targetDb.geographicCoordinateSystem?.horizontalCRS === undefined ||
|
|
962
487
|
this.targetDb.geographicCoordinateSystem.verticalCRS === undefined) {
|
|
963
|
-
|
|
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
|
+
});
|
|
964
495
|
}
|
|
965
496
|
if (!this.sourceDb.geographicCoordinateSystem.horizontalCRS.equals(this.targetDb.geographicCoordinateSystem.horizontalCRS) ||
|
|
966
497
|
!this.sourceDb.geographicCoordinateSystem.verticalCRS.equals(this.targetDb.geographicCoordinateSystem.verticalCRS)) {
|
|
967
|
-
|
|
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
|
+
});
|
|
968
505
|
}
|
|
969
506
|
if (this.sourceDb.geographicCoordinateSystem.additionalTransform ===
|
|
970
507
|
this.targetDb.geographicCoordinateSystem.additionalTransform) {
|
|
@@ -976,14 +513,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
976
513
|
const targetScale = this.targetDb.geographicCoordinateSystem.additionalTransform
|
|
977
514
|
?.helmert2DWithZOffset?.scale ?? 1;
|
|
978
515
|
if (srcScale !== targetScale) {
|
|
979
|
-
|
|
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
|
+
});
|
|
980
523
|
}
|
|
981
524
|
const srcTransform = IModelTransformer.convertHelmertToTransform(this.sourceDb.geographicCoordinateSystem.additionalTransform
|
|
982
525
|
?.helmert2DWithZOffset); // moves elements to where src helmert transform would move them at render time
|
|
983
526
|
const targetTransformInv = IModelTransformer.convertHelmertToTransform(this.targetDb.geographicCoordinateSystem.additionalTransform
|
|
984
527
|
?.helmert2DWithZOffset).inverse(); // negates target helmert transform that is applied at render time
|
|
985
528
|
if (!targetTransformInv) {
|
|
986
|
-
|
|
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
|
+
});
|
|
987
536
|
}
|
|
988
537
|
const combinedTransform = targetTransformInv.multiplyTransformTransform(srcTransform);
|
|
989
538
|
return combinedTransform;
|
|
@@ -993,6 +542,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
993
542
|
/** Returns true if a change within sourceElement is detected.
|
|
994
543
|
* @param sourceElement The Element from the source iModel
|
|
995
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.
|
|
996
548
|
*/
|
|
997
549
|
hasElementChanged(sourceElement) {
|
|
998
550
|
const sourceDbChanges = this.exporter.sourceDbChanges;
|
|
@@ -1012,7 +564,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1012
564
|
throw new Error(`source-target element mapping not found for element "${sourceElementId}" when completing partially committed elements. This is a bug.`);
|
|
1013
565
|
}
|
|
1014
566
|
const targetProps = await this.onTransformElement(sourceElement);
|
|
1015
|
-
this.
|
|
567
|
+
this._targetEditTxn.updateElement({ ...targetProps, id: targetId });
|
|
1016
568
|
}
|
|
1017
569
|
}
|
|
1018
570
|
async completePartiallyCommittedAspects() {
|
|
@@ -1023,14 +575,23 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1023
575
|
throw new Error(`source-target aspect mapping not found for aspect "${sourceAspectId}" when completing partially committed aspects. This is a bug.`);
|
|
1024
576
|
}
|
|
1025
577
|
const targetAspectProps = await this.onTransformElementAspect(sourceAspect);
|
|
1026
|
-
this.
|
|
578
|
+
this._targetEditTxn.updateAspect({
|
|
1027
579
|
...targetAspectProps,
|
|
1028
580
|
id: targetAspectId,
|
|
1029
581
|
});
|
|
1030
582
|
}
|
|
1031
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
|
+
}
|
|
1032
592
|
async doAllReferencesExistInTarget(entity) {
|
|
1033
593
|
let allReferencesExist = true;
|
|
594
|
+
const checkedReferences = [];
|
|
1034
595
|
for (const referenceId of entity.getReferenceIds()) {
|
|
1035
596
|
const referencedEntityId = core_backend_1.EntityReferences.toId64(referenceId);
|
|
1036
597
|
if (referencedEntityId === core_common_1.IModel.repositoryModelId ||
|
|
@@ -1047,47 +608,70 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1047
608
|
allReferencesExist = false;
|
|
1048
609
|
}
|
|
1049
610
|
if (this._options.danglingReferencesBehavior === "reject") {
|
|
1050
|
-
|
|
611
|
+
checkedReferences.push(referenceId);
|
|
1051
612
|
}
|
|
1052
613
|
}
|
|
614
|
+
if (checkedReferences.length > 0) {
|
|
615
|
+
await this.assertReferencesExistInSource(checkedReferences, entity);
|
|
616
|
+
}
|
|
1053
617
|
return allReferencesExist;
|
|
1054
618
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
+
}
|
|
1066
639
|
}
|
|
1067
640
|
}
|
|
1068
641
|
/** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
|
|
1069
642
|
* @param sourceElementId Identifies the Element from the source iModel to import.
|
|
1070
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.
|
|
1071
645
|
*/
|
|
1072
646
|
async processElement(sourceElementId) {
|
|
1073
647
|
await this.initialize();
|
|
1074
648
|
if (sourceElementId === core_common_1.IModel.rootSubjectId) {
|
|
1075
|
-
|
|
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
|
+
});
|
|
1076
656
|
}
|
|
1077
|
-
return this.exporter.exportElement(sourceElementId);
|
|
657
|
+
return this.processScopedElementExport(async () => this.exporter.exportElement(sourceElementId));
|
|
1078
658
|
}
|
|
1079
659
|
/** Import child elements into the target IModelDb
|
|
1080
660
|
* @param sourceElementId Import the child elements of this element in the source IModelDb.
|
|
1081
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.
|
|
1082
663
|
*/
|
|
1083
664
|
async processChildElements(sourceElementId) {
|
|
1084
665
|
await this.initialize();
|
|
1085
|
-
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);
|
|
1086
670
|
}
|
|
1087
671
|
/** Override of [IModelExportHandler.shouldExportElement]($transformer) that is called to determine if an element should be exported from the source iModel.
|
|
1088
672
|
* @note Reaching this point means that the element has passed the standard exclusion checks in IModelExporter.
|
|
1089
673
|
*/
|
|
1090
|
-
shouldExportElement(_sourceElement) {
|
|
674
|
+
async shouldExportElement(_sourceElement) {
|
|
1091
675
|
return true;
|
|
1092
676
|
}
|
|
1093
677
|
/**
|
|
@@ -1102,7 +686,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1102
686
|
const elemClass = sourceElement.constructor;
|
|
1103
687
|
const unresolvedReferences = elemClass.requiredReferenceKeys
|
|
1104
688
|
.map((referenceKey) => {
|
|
1105
|
-
const idContainer = sourceElement[referenceKey];
|
|
689
|
+
const idContainer = sourceElement.asAny[referenceKey];
|
|
1106
690
|
const referenceType = elemClass.requiredReferenceKeyTypeMap[referenceKey];
|
|
1107
691
|
// For now we just consider all required references to be elements (as they are in biscore), and do not support
|
|
1108
692
|
// entities that refuse to be inserted without a different kind of entity (e.g. aspect or relationship) first being inserted
|
|
@@ -1134,18 +718,95 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1134
718
|
if (unresolvedReferences.length > 0) {
|
|
1135
719
|
for (const reference of unresolvedReferences) {
|
|
1136
720
|
const processState = await this.getElemTransformState(reference);
|
|
1137
|
-
// must export element first
|
|
1138
721
|
if (processState.needsElemImport)
|
|
1139
|
-
await this.
|
|
722
|
+
await this.resolveRequiredElement(sourceElement.id, reference);
|
|
1140
723
|
if (processState.needsModelImport)
|
|
1141
724
|
await this.exporter.exportModel(reference);
|
|
1142
725
|
}
|
|
1143
726
|
}
|
|
1144
727
|
}
|
|
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
|
+
// empty code values are stored as NULL and are never unique, so they cannot identify an existing target element
|
|
791
|
+
if (core_common_1.Code.isEmpty(targetElementProps.code))
|
|
792
|
+
return core_bentley_1.Id64.invalid;
|
|
793
|
+
const maybeTargetElementId = this.queryElementIdByCode(this.targetDb, targetElementProps.code);
|
|
794
|
+
if (maybeTargetElementId === undefined)
|
|
795
|
+
return core_bentley_1.Id64.invalid;
|
|
796
|
+
const maybeTargetElem = this.targetDb.elements.getElement(maybeTargetElementId);
|
|
797
|
+
if (maybeTargetElem.classFullName !== targetElementProps.classFullName) {
|
|
798
|
+
targetElementProps.code = core_common_1.Code.createEmpty(); // clear out invalid code
|
|
799
|
+
return core_bentley_1.Id64.invalid;
|
|
800
|
+
}
|
|
801
|
+
// ensure code remapping doesn't change the target class
|
|
802
|
+
this.context.remapElement(sourceElement.id, maybeTargetElementId); // record that the targetElement was found by Code
|
|
803
|
+
this._targetElementIdsRemappedByCode.add(maybeTargetElementId);
|
|
804
|
+
return maybeTargetElementId;
|
|
805
|
+
}
|
|
1145
806
|
async getElemTransformState(elementId) {
|
|
1146
807
|
const dbHasModel = async (db, id) => {
|
|
1147
808
|
const maybeModelId = core_backend_1.EntityReferences.fromEntityType(id, core_common_1.ConcreteEntityTypes.Model);
|
|
1148
|
-
return
|
|
809
|
+
return this._cloneContext.existenceCache.exists(db, maybeModelId);
|
|
1149
810
|
};
|
|
1150
811
|
const isSubModeled = await dbHasModel(this.sourceDb, elementId);
|
|
1151
812
|
const idOfElemInTarget = this.context.findTargetElementId(elementId);
|
|
@@ -1154,10 +815,42 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1154
815
|
(!isElemInTarget || !(await dbHasModel(this.targetDb, idOfElemInTarget)));
|
|
1155
816
|
return { needsElemImport: !isElemInTarget, needsModelImport };
|
|
1156
817
|
}
|
|
818
|
+
// In iTwin js 5.x Elements.queryElementIdByCode() uses Code class to query id:
|
|
819
|
+
// https://github.com/iTwin/itwinjs-core/blob/master/core/backend/src/IModelDb.ts#L2779
|
|
820
|
+
// Code class constructor trims white spaces from code value.
|
|
821
|
+
// Custom implementation of queryElementIdByCode() was added to support querying elements with code values that have trailing whitespaces.
|
|
822
|
+
// It queries bis_Element through a cached SQLite statement, because creating an ECSQL reader per element is slow
|
|
823
|
+
// and the cached ECSQL API (withPreparedStatement) is deprecated. Results match the ECSQL query because CodeValue
|
|
824
|
+
// is COLLATE NOCASE, ix_bis_Element_Code is unique, and the primary connection sees uncommitted target inserts.
|
|
825
|
+
queryElementIdByCode(iModel, code) {
|
|
826
|
+
if (core_bentley_1.Id64.isInvalid(code.spec))
|
|
827
|
+
core_bentley_1.ITwinError.throwError({
|
|
828
|
+
iTwinErrorId: {
|
|
829
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
830
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidCode,
|
|
831
|
+
},
|
|
832
|
+
message: "Invalid CodeSpec",
|
|
833
|
+
});
|
|
834
|
+
if (code.value === undefined)
|
|
835
|
+
core_bentley_1.ITwinError.throwError({
|
|
836
|
+
iTwinErrorId: {
|
|
837
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
838
|
+
key: IModelTransformerError_1.IModelTransformerError.InvalidCode,
|
|
839
|
+
},
|
|
840
|
+
message: "Invalid Code",
|
|
841
|
+
});
|
|
842
|
+
return iModel.withPreparedSqliteStatement("SELECT Id FROM bis_Element WHERE CodeSpecId=? AND CodeScopeId=? AND CodeValue=?", (stmt) => {
|
|
843
|
+
stmt.bindId(1, code.spec);
|
|
844
|
+
stmt.bindId(2, core_bentley_1.Id64.fromString(code.scope));
|
|
845
|
+
stmt.bindString(3, code.value);
|
|
846
|
+
return stmt.nextRow() ? stmt.getValueId(0) : undefined;
|
|
847
|
+
});
|
|
848
|
+
}
|
|
1157
849
|
/** Override of [IModelExportHandler.onExportElement]($transformer) that imports an element into the target iModel when it is exported from the source iModel.
|
|
1158
850
|
* This override calls [[onTransformElement]] and then [IModelImporter.importElement]($transformer) to update the target iModel.
|
|
1159
851
|
*/
|
|
1160
852
|
async onExportElement(sourceElement) {
|
|
853
|
+
this.markSourceEntityExists(sourceElement);
|
|
1161
854
|
let targetElementId = core_bentley_1.Id64.invalid;
|
|
1162
855
|
let targetElementProps;
|
|
1163
856
|
if (this._options.wasSourceIModelCopiedToTarget) {
|
|
@@ -1169,34 +862,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1169
862
|
targetElementId = this.context.findTargetElementId(sourceElement.id);
|
|
1170
863
|
targetElementProps = await this.onTransformElement(sourceElement);
|
|
1171
864
|
}
|
|
1172
|
-
// if an existing remapping was not yet found, check by FederationGuid
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1175
|
-
sourceElement.federationGuid !== undefined) {
|
|
1176
|
-
targetElementId =
|
|
1177
|
-
this.targetDb.elements.getIdFromFederationGuid(sourceElement.federationGuid) ?? core_bentley_1.Id64.invalid;
|
|
1178
|
-
if (core_bentley_1.Id64.isValid(targetElementId))
|
|
1179
|
-
this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found
|
|
1180
|
-
}
|
|
1181
|
-
// 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)
|
|
1182
|
-
if (!core_bentley_1.Id64.isValidId64(targetElementId) &&
|
|
1183
|
-
core_bentley_1.Id64.isValidId64(targetElementProps.code.scope)) {
|
|
1184
|
-
// respond the same way to undefined code value as the @see Code class, but don't use that class because it trims
|
|
1185
|
-
// whitespace from the value, and there are iModels out there with untrimmed whitespace that we ought not to trim
|
|
1186
|
-
targetElementProps.code.value = targetElementProps.code.value ?? "";
|
|
1187
|
-
const maybeTargetElementId = this.targetDb.elements.queryElementIdByCode(targetElementProps.code);
|
|
1188
|
-
if (undefined !== maybeTargetElementId) {
|
|
1189
|
-
const maybeTargetElem = this.targetDb.elements.getElement(maybeTargetElementId);
|
|
1190
|
-
if (maybeTargetElem.classFullName === targetElementProps.classFullName) {
|
|
1191
|
-
// ensure code remapping doesn't change the target class
|
|
1192
|
-
targetElementId = maybeTargetElementId;
|
|
1193
|
-
this.context.remapElement(sourceElement.id, targetElementId); // record that the targetElement was found by Code
|
|
1194
|
-
}
|
|
1195
|
-
else {
|
|
1196
|
-
targetElementProps.code = core_common_1.Code.createEmpty(); // clear out invalid code
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
865
|
+
// if an existing remapping was not yet found, check by FederationGuid and then by Code
|
|
866
|
+
if (!core_bentley_1.Id64.isValid(targetElementId))
|
|
867
|
+
targetElementId = await this.findExistingTargetElement(sourceElement, targetElementProps);
|
|
1200
868
|
if (!this.hasElementChanged(sourceElement)) {
|
|
1201
869
|
core_bentley_1.Logger.logTrace(loggerCategory, `Skipping unchanged element (${sourceElement.id}, ${sourceElement.getDisplayLabel()}).`);
|
|
1202
870
|
return;
|
|
@@ -1212,7 +880,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1212
880
|
const isValid = core_bentley_1.Id64.isValid(targetElementId);
|
|
1213
881
|
if (isValid && targetElementId !== sourceElement.id) {
|
|
1214
882
|
// Element found with different id
|
|
1215
|
-
|
|
883
|
+
core_bentley_1.ITwinError.throwError({
|
|
884
|
+
iTwinErrorId: {
|
|
885
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
886
|
+
key: IModelTransformerError_1.IModelTransformerError.ElementIdNotPreservable,
|
|
887
|
+
},
|
|
888
|
+
message: `Element id(${sourceElement.id}) cannot be preserved. Found a different mapping(${targetElementId}) from source element`,
|
|
889
|
+
});
|
|
1216
890
|
}
|
|
1217
891
|
else if (isValid && targetElementId === sourceElement.id) {
|
|
1218
892
|
// targetElementId is valid (indicating update)
|
|
@@ -1223,7 +897,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1223
897
|
// if we don't find mapping for source element in target(invalid) but another element with source id exists in target
|
|
1224
898
|
if (sourceInTargetElemProps) {
|
|
1225
899
|
// Element id is already taken by another element
|
|
1226
|
-
|
|
900
|
+
core_bentley_1.ITwinError.throwError({
|
|
901
|
+
iTwinErrorId: {
|
|
902
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
903
|
+
key: IModelTransformerError_1.IModelTransformerError.ElementIdNotPreservable,
|
|
904
|
+
},
|
|
905
|
+
message: `Element id(${sourceElement.id}) cannot be preserved. An unrelated element in the target already uses id: ${sourceElement.id}`,
|
|
906
|
+
});
|
|
1227
907
|
}
|
|
1228
908
|
else {
|
|
1229
909
|
// Element id in target is available to be remapped
|
|
@@ -1232,12 +912,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1232
912
|
}
|
|
1233
913
|
}
|
|
1234
914
|
if (!this._options.wasSourceIModelCopiedToTarget) {
|
|
1235
|
-
this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
|
|
915
|
+
await this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
|
|
1236
916
|
}
|
|
1237
917
|
if (targetElementProps.id === undefined) {
|
|
1238
|
-
throw new
|
|
918
|
+
throw new Error("targetElementProps.id should be assigned by importElement");
|
|
1239
919
|
}
|
|
1240
920
|
this.context.remapElement(sourceElement.id, targetElementProps.id);
|
|
921
|
+
if (this.sourceDb === this.targetDb) {
|
|
922
|
+
this._cloneContext.existenceCache.markExists(this.targetDb, core_backend_1.EntityReferences.fromEntityType(targetElementProps.id, core_common_1.ConcreteEntityTypes.Element));
|
|
923
|
+
}
|
|
1241
924
|
// the transformer does not currently 'split' or 'join' any elements, therefore, it does not
|
|
1242
925
|
// insert external source aspects because federation guids are sufficient for this.
|
|
1243
926
|
// Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
|
|
@@ -1246,39 +929,42 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1246
929
|
// FIXME: verify at finalization time that we don't lose provenance on new elements
|
|
1247
930
|
// FIXME: make public and improve `initElementProvenance` API for usage by consolidators
|
|
1248
931
|
const provenanceDb = await this.getProvenanceDb();
|
|
932
|
+
const provenanceEditTxn = await this._provenanceManager.getProvenanceEditTxn();
|
|
1249
933
|
if (!this._options.noProvenance) {
|
|
1250
|
-
|
|
934
|
+
const provenance = this._options.forceExternalSourceAspectProvenance ||
|
|
1251
935
|
this._elementsWithExplicitlyTrackedProvenance.has(sourceElement.id)
|
|
1252
936
|
? undefined
|
|
1253
937
|
: sourceElement.federationGuid;
|
|
1254
938
|
if (!provenance) {
|
|
1255
939
|
const aspectProps = await this.initElementProvenance(sourceElement.id, targetElementProps.id);
|
|
1256
|
-
const foundEsaProps = await
|
|
940
|
+
const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
|
|
1257
941
|
if (foundEsaProps === undefined)
|
|
1258
|
-
aspectProps.id =
|
|
942
|
+
aspectProps.id = provenanceEditTxn.insertAspect(aspectProps);
|
|
1259
943
|
else {
|
|
1260
944
|
// Since initElementProvenance sets a property 'version' on the aspectProps that we wish to persist in the provenanceDb, only grab the id from the foundEsaProps.
|
|
1261
945
|
aspectProps.id = foundEsaProps.aspectId;
|
|
1262
|
-
|
|
946
|
+
provenanceEditTxn.updateAspect(aspectProps);
|
|
1263
947
|
}
|
|
1264
|
-
provenance = aspectProps;
|
|
1265
948
|
}
|
|
1266
|
-
this.markLastProvenance(provenance, { isRelationship: false });
|
|
1267
949
|
}
|
|
1268
950
|
}
|
|
1269
|
-
/**
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
951
|
+
/** Maps the deleted source element IDs and passes the target IDs to the importer as one set. */
|
|
952
|
+
async onDeleteElements(sourceElementIds) {
|
|
953
|
+
const targetElementIds = new Set();
|
|
954
|
+
for (const sourceElementId of sourceElementIds) {
|
|
955
|
+
const targetElementId = this.context.findTargetElementId(sourceElementId);
|
|
956
|
+
if (core_bentley_1.Id64.isValidId64(targetElementId) &&
|
|
957
|
+
!this._targetElementIdsRemappedByCode.has(targetElementId)) {
|
|
958
|
+
targetElementIds.add(targetElementId);
|
|
959
|
+
}
|
|
1276
960
|
}
|
|
961
|
+
await this.importer.deleteElements(targetElementIds);
|
|
1277
962
|
}
|
|
1278
963
|
/** Override of [IModelExportHandler.onExportModel]($transformer) that is called when a Model should be exported from the source iModel.
|
|
1279
964
|
* This override calls [[onTransformModel]] and then [IModelImporter.importModel]($transformer) to update the target iModel.
|
|
1280
965
|
*/
|
|
1281
|
-
onExportModel(sourceModel) {
|
|
966
|
+
async onExportModel(sourceModel) {
|
|
967
|
+
this.markSourceEntityExists(sourceModel);
|
|
1282
968
|
if (this._options.skipPropagateChangesToRootElements &&
|
|
1283
969
|
core_common_1.IModel.repositoryModelId === sourceModel.id)
|
|
1284
970
|
return; // The RepositoryModel should not be directly imported
|
|
@@ -1289,7 +975,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1289
975
|
if (isRemappedRootSubject)
|
|
1290
976
|
return;
|
|
1291
977
|
const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
|
|
1292
|
-
this.importer.importModel(targetModelProps);
|
|
978
|
+
await this.importer.importModel(targetModelProps);
|
|
979
|
+
if (targetModelProps.id === undefined) {
|
|
980
|
+
throw new Error("targetModelProps.id should be assigned by now");
|
|
981
|
+
}
|
|
982
|
+
this._cloneContext.existenceCache.markExists(this.targetDb, core_backend_1.EntityReferences.fromEntityType(targetModelProps.id, core_common_1.ConcreteEntityTypes.Model));
|
|
983
|
+
this._targetModelsImportedInCurrentTransform.add(targetModelProps.id);
|
|
1293
984
|
}
|
|
1294
985
|
/** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
|
|
1295
986
|
async onDeleteModel(sourceModelId) {
|
|
@@ -1299,6 +990,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1299
990
|
const targetModelId = this.context.findTargetElementId(sourceModelId);
|
|
1300
991
|
if (!core_bentley_1.Id64.isValidId64(targetModelId))
|
|
1301
992
|
return;
|
|
993
|
+
// This handles cases where model with a partition element was remapped to
|
|
994
|
+
// new element by code value after recreation.
|
|
995
|
+
if (this._targetElementIdsRemappedByCode.has(targetModelId) &&
|
|
996
|
+
this._targetModelsImportedInCurrentTransform.has(targetModelId)) {
|
|
997
|
+
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.`);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1302
1000
|
const sql = `
|
|
1303
1001
|
SELECT 1
|
|
1304
1002
|
FROM bis.DefinitionPartition
|
|
@@ -1330,9 +1028,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1330
1028
|
error.errorNumber === core_bentley_1.IModelStatus.ForeignKeyConstraint);
|
|
1331
1029
|
if (!isDeletionProhibitedErr)
|
|
1332
1030
|
throw error;
|
|
1333
|
-
//
|
|
1334
|
-
//
|
|
1335
|
-
// The `OnDeleteElement` calls `DeleteElementTree` Which deletes the model together with its partition after deleting all of the modeled elements.
|
|
1031
|
+
// Models are processed before elements, but a definition model cannot be deleted while it contains elements.
|
|
1032
|
+
// Add its partition to the element deletion set. Bulk cascade deletion will remove the contents, model, and partition together.
|
|
1336
1033
|
this.scheduleModeledPartitionDeletion(sourceModelId);
|
|
1337
1034
|
}
|
|
1338
1035
|
}
|
|
@@ -1347,22 +1044,24 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1347
1044
|
/** Cause the model container, contents, and sub-models to be exported from the source iModel and imported into the target iModel.
|
|
1348
1045
|
* @param sourceModeledElementId Import this [Model]($backend) from the source IModelDb.
|
|
1349
1046
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1047
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1350
1048
|
*/
|
|
1351
1049
|
async processModel(sourceModeledElementId) {
|
|
1352
1050
|
await this.initialize();
|
|
1353
|
-
return this.exporter.exportModel(sourceModeledElementId);
|
|
1051
|
+
return this.processScopedElementExport(async () => this.exporter.exportModel(sourceModeledElementId));
|
|
1354
1052
|
}
|
|
1355
1053
|
/** Cause the model contents to be exported from the source iModel and imported into the target iModel.
|
|
1356
1054
|
* @param sourceModelId Import the contents of this model from the source IModelDb.
|
|
1357
1055
|
* @param targetModelId Import into this model in the target IModelDb. The target model must exist prior to this call.
|
|
1358
1056
|
* @param elementClassFullName Optional classFullName of an element subclass to limit import query against the source model.
|
|
1359
1057
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1058
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1360
1059
|
*/
|
|
1361
1060
|
async processModelContents(sourceModelId, targetModelId, elementClassFullName = core_backend_1.Element.classFullName) {
|
|
1362
1061
|
await this.initialize();
|
|
1363
1062
|
this.targetDb.models.getModel(targetModelId); // throws if Model does not exist
|
|
1364
1063
|
this.context.remapElement(sourceModelId, targetModelId); // set remapping in case importModelContents is called directly
|
|
1365
|
-
return this.exporter.exportModelContents(sourceModelId, elementClassFullName);
|
|
1064
|
+
return this.processScopedElementExport(async () => this.exporter.exportModelContents(sourceModelId, elementClassFullName));
|
|
1366
1065
|
}
|
|
1367
1066
|
/** Cause all sub-models that recursively descend from the specified Subject to be exported from the source iModel and imported into the target iModel. */
|
|
1368
1067
|
async processSubjectSubModels(sourceSubjectId) {
|
|
@@ -1403,103 +1102,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1403
1102
|
};
|
|
1404
1103
|
targetModelProps.id = targetModeledElementId;
|
|
1405
1104
|
if (targetModelProps.parentModel === undefined) {
|
|
1406
|
-
|
|
1105
|
+
core_bentley_1.ITwinError.throwError({
|
|
1106
|
+
iTwinErrorId: {
|
|
1107
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1108
|
+
key: IModelTransformerError_1.IModelTransformerError.ParentModelRequired,
|
|
1109
|
+
},
|
|
1110
|
+
message: "targetElementProps must have a defined parentModel",
|
|
1111
|
+
});
|
|
1407
1112
|
}
|
|
1408
1113
|
targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
|
|
1409
1114
|
return targetModelProps;
|
|
1410
1115
|
}
|
|
1411
|
-
/**
|
|
1412
|
-
* Called at the end of a transformation,
|
|
1413
|
-
* updates the target scope element to say that transformation up through the
|
|
1414
|
-
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1415
|
-
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
1416
|
-
*
|
|
1417
|
-
* You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
|
|
1418
|
-
* It is public for unsupported use cases of custom synchronization transforms.
|
|
1419
|
-
* @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
|
|
1420
|
-
* unless the `initializeReverseSyncVersion` option is set to `true`
|
|
1421
|
-
*
|
|
1422
|
-
* The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
|
|
1423
|
-
* 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.
|
|
1424
|
-
* 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.
|
|
1425
|
-
*
|
|
1426
|
-
* Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
|
|
1427
|
-
* 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.
|
|
1428
|
-
*/
|
|
1429
|
-
async updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
|
|
1430
|
-
const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
|
|
1431
|
-
this._sourceChangeDataState !== "has-changes";
|
|
1432
|
-
if (shouldSkipSyncVersionUpdate)
|
|
1433
|
-
return;
|
|
1434
|
-
nodeAssert(this._targetScopeProvenanceProps);
|
|
1435
|
-
const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
|
|
1436
|
-
const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
|
|
1437
|
-
if (await this.getIsReverseSynchronization()) {
|
|
1438
|
-
const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
|
|
1439
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
|
|
1440
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1441
|
-
sourceVersion;
|
|
1442
|
-
}
|
|
1443
|
-
else {
|
|
1444
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
|
|
1445
|
-
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1446
|
-
// save reverse sync version
|
|
1447
|
-
if (initializeReverseSyncVersion) {
|
|
1448
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
|
|
1449
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1450
|
-
targetVersion;
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
if (this._options.argsForProcessChanges ||
|
|
1454
|
-
(this._startingChangesetIndices && initializeReverseSyncVersion)) {
|
|
1455
|
-
nodeAssert(this.targetDb.changeset.index !== undefined &&
|
|
1456
|
-
this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
|
|
1457
|
-
// Store in a local variable, so typescript knows it's defined (due to the assert above)
|
|
1458
|
-
const startingChangesetIndices = this._startingChangesetIndices;
|
|
1459
|
-
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
1460
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1461
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1462
|
-
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1463
|
-
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1464
|
-
// Determine which keys to clear and update based on the synchronization direction
|
|
1465
|
-
let syncChangesetsToClearKey;
|
|
1466
|
-
let syncChangesetsToUpdateKey;
|
|
1467
|
-
if (await this.getIsReverseSynchronization()) {
|
|
1468
|
-
syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
|
|
1469
|
-
syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
|
|
1470
|
-
}
|
|
1471
|
-
else {
|
|
1472
|
-
syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
|
|
1473
|
-
syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
|
|
1474
|
-
}
|
|
1475
|
-
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1476
|
-
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1477
|
-
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1478
|
-
// stuff to it which would break future synchronizations
|
|
1479
|
-
for (let i = startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1480
|
-
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1481
|
-
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
1482
|
-
jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
|
|
1483
|
-
return csIndex > startingChangesetIndices.source;
|
|
1484
|
-
});
|
|
1485
|
-
// if reverse sync then we may have received provenance changes which should be marked as sync changes
|
|
1486
|
-
if (await this.getIsReverseSynchronization()) {
|
|
1487
|
-
nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
|
|
1488
|
-
for (let i = startingChangesetIndices.source + 1; i <= this.sourceDb.changeset.index + 1; i++)
|
|
1489
|
-
jsonProps.pendingReverseSyncChangesetIndices.push(i);
|
|
1490
|
-
}
|
|
1491
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `new pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
|
|
1492
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `new pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1493
|
-
}
|
|
1494
|
-
(await this.getProvenanceDb()).elements.updateAspect({
|
|
1495
|
-
...this._targetScopeProvenanceProps,
|
|
1496
|
-
jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
|
|
1497
|
-
});
|
|
1498
|
-
this.clearCachedSynchronizationVersion();
|
|
1499
|
-
}
|
|
1500
|
-
// FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
|
|
1116
|
+
/** Complete a high-level transformation after all export operations finish. */
|
|
1501
1117
|
async finalizeTransformation() {
|
|
1502
1118
|
this.importer.finalize();
|
|
1119
|
+
this._cloneContext.existenceCache.clear();
|
|
1503
1120
|
await this.updateSynchronizationVersion({
|
|
1504
1121
|
initializeReverseSyncVersion: this._isProvenanceInitTransform,
|
|
1505
1122
|
});
|
|
@@ -1508,17 +1125,16 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1508
1125
|
if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
|
|
1509
1126
|
core_backend_1.ChangeSummaryManager.detachChangeCache(this.sourceDb);
|
|
1510
1127
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
/* eslint-enable @itwin/no-internal */
|
|
1128
|
+
this.setCodeValueBehavior("trim-unicode-whitespace");
|
|
1129
|
+
}
|
|
1130
|
+
setCodeValueBehavior(behavior) {
|
|
1131
|
+
this.sourceDb.codeValueBehavior = behavior;
|
|
1132
|
+
this.targetDb.codeValueBehavior = behavior;
|
|
1518
1133
|
}
|
|
1519
1134
|
/** Imports all relationships that subclass from the specified base class.
|
|
1520
1135
|
* @param baseRelClassFullName The specified base relationship class.
|
|
1521
1136
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1137
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1522
1138
|
*/
|
|
1523
1139
|
async processRelationships(baseRelClassFullName) {
|
|
1524
1140
|
await this.initialize();
|
|
@@ -1527,40 +1143,41 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1527
1143
|
/** Override of [IModelExportHandler.shouldExportRelationship]($transformer) that is called to determine if a [Relationship]($backend) should be exported.
|
|
1528
1144
|
* @note Reaching this point means that the relationship has passed the standard exclusion checks in [IModelExporter]($transformer).
|
|
1529
1145
|
*/
|
|
1530
|
-
shouldExportRelationship(_sourceRelationship) {
|
|
1146
|
+
async shouldExportRelationship(_sourceRelationship) {
|
|
1531
1147
|
return true;
|
|
1532
1148
|
}
|
|
1533
1149
|
/** Override of [IModelExportHandler.onExportRelationship]($transformer) that imports a relationship into the target iModel when it is exported from the source iModel.
|
|
1534
1150
|
* This override calls [[onTransformRelationship]] and then [IModelImporter.importRelationship]($transformer) to update the target iModel.
|
|
1535
1151
|
*/
|
|
1536
|
-
async onExportRelationship(sourceRelationship) {
|
|
1537
|
-
const sourceFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.sourceId);
|
|
1538
|
-
const targetFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.targetId);
|
|
1152
|
+
async onExportRelationship(sourceRelationship, _isUpdate, sourceFedGuid, targetFedGuid) {
|
|
1539
1153
|
const targetRelationshipProps = this.onTransformRelationship(sourceRelationship);
|
|
1540
|
-
const targetRelationshipInstanceId = this.importer.importRelationship(targetRelationshipProps);
|
|
1154
|
+
const targetRelationshipInstanceId = await this.importer.importRelationship(targetRelationshipProps);
|
|
1541
1155
|
const provenanceDb = await this.getProvenanceDb();
|
|
1156
|
+
const provenanceEditTxn = await this._provenanceManager.getProvenanceEditTxn();
|
|
1542
1157
|
if (!this._options.noProvenance &&
|
|
1543
1158
|
core_bentley_1.Id64.isValid(targetRelationshipInstanceId)) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const
|
|
1159
|
+
const needsEsaProvenance = !this._options
|
|
1160
|
+
.forceExternalSourceAspectProvenance
|
|
1161
|
+
? !(sourceFedGuid && targetFedGuid)
|
|
1162
|
+
: true;
|
|
1163
|
+
if (needsEsaProvenance) {
|
|
1164
|
+
const aspectProps = await this._provenanceManager.initRelationshipProvenance(sourceRelationship.id, targetRelationshipInstanceId, this._forceOldRelationshipProvenanceMethod, {
|
|
1165
|
+
sourceRelSourceElementId: sourceRelationship.sourceId,
|
|
1166
|
+
targetRelSourceElementId: targetRelationshipProps.sourceId,
|
|
1167
|
+
});
|
|
1168
|
+
const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
|
|
1550
1169
|
// 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).
|
|
1551
1170
|
if (undefined === foundEsaProps) {
|
|
1552
|
-
aspectProps.id =
|
|
1171
|
+
aspectProps.id = provenanceEditTxn.insertAspect(aspectProps);
|
|
1553
1172
|
}
|
|
1554
|
-
provenance = aspectProps;
|
|
1555
1173
|
}
|
|
1556
|
-
this.markLastProvenance(provenance, { isRelationship: true });
|
|
1557
1174
|
}
|
|
1558
1175
|
}
|
|
1559
1176
|
/** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
|
|
1560
1177
|
* This override propagates the delete to the target iModel via [IModelImporter.deleteRelationship]($transformer).
|
|
1561
1178
|
*/
|
|
1562
1179
|
async onDeleteRelationship(sourceRelInstanceId) {
|
|
1563
|
-
|
|
1180
|
+
(0, node_assert_1.strict)(this._deletedSourceRelationshipData, "should be defined at initialization by now");
|
|
1564
1181
|
const deletedRelData = this._deletedSourceRelationshipData.get(sourceRelInstanceId);
|
|
1565
1182
|
if (!deletedRelData) {
|
|
1566
1183
|
// this can occur if both the source and target deleted it
|
|
@@ -1573,14 +1190,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1573
1190
|
targetId: deletedRelData.targetIdInTarget,
|
|
1574
1191
|
})?.id;
|
|
1575
1192
|
if (id) {
|
|
1576
|
-
this.importer.deleteRelationship({
|
|
1193
|
+
await this.importer.deleteRelationship({
|
|
1577
1194
|
id,
|
|
1578
1195
|
classFullName: deletedRelData.classFullName,
|
|
1579
1196
|
});
|
|
1580
1197
|
}
|
|
1581
1198
|
if (deletedRelData.provenanceAspectId) {
|
|
1582
1199
|
try {
|
|
1583
|
-
(await this.
|
|
1200
|
+
(await this._provenanceManager.getProvenanceEditTxn()).deleteAspect(deletedRelData.provenanceAspectId);
|
|
1584
1201
|
}
|
|
1585
1202
|
catch (error) {
|
|
1586
1203
|
// This aspect may no longer exist if it was deleted at some other point during the transformation. This is fine.
|
|
@@ -1590,7 +1207,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1590
1207
|
}
|
|
1591
1208
|
}
|
|
1592
1209
|
}
|
|
1593
|
-
_yieldManager = new core_bentley_1.YieldManager();
|
|
1594
1210
|
/** Transform the specified sourceRelationship into RelationshipProps for the target iModel.
|
|
1595
1211
|
* @param sourceRelationship The Relationship from the source iModel to be transformed.
|
|
1596
1212
|
* @returns RelationshipProps for the target iModel.
|
|
@@ -1601,19 +1217,47 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1601
1217
|
targetRelationshipProps.sourceId = this.context.findTargetElementId(sourceRelationship.sourceId);
|
|
1602
1218
|
targetRelationshipProps.targetId = this.context.findTargetElementId(sourceRelationship.targetId);
|
|
1603
1219
|
// TODO: move to cloneRelationship in IModelCloneContext
|
|
1604
|
-
sourceRelationship.
|
|
1220
|
+
const relationshipClass = sourceRelationship.iModel.schemaContext.getSchemaItemSync(sourceRelationship.schemaItemKey, ECSchemaMetaData.RelationshipClass);
|
|
1221
|
+
(0, core_bentley_1.assert)(relationshipClass !== undefined, `Cannot get metadata for ${sourceRelationship.classFullName}.`);
|
|
1222
|
+
for (const property of relationshipClass.getPropertiesSync()) {
|
|
1223
|
+
const propertyName = property.name;
|
|
1605
1224
|
if (property.isPrimitive() && "Id" === property.extendedTypeName) {
|
|
1606
1225
|
targetRelationshipProps[core_common_1.ECJsNames.toJsName(propertyName)] =
|
|
1607
1226
|
this.context.findTargetElementId(sourceRelationship.asAny[core_common_1.ECJsNames.toJsName(propertyName)]);
|
|
1608
1227
|
}
|
|
1609
|
-
}
|
|
1228
|
+
}
|
|
1610
1229
|
return targetRelationshipProps;
|
|
1611
1230
|
}
|
|
1612
|
-
shouldExportElementAspect(aspect) {
|
|
1613
|
-
//
|
|
1614
|
-
//
|
|
1231
|
+
async shouldExportElementAspect(aspect) {
|
|
1232
|
+
// An aspect cannot be exported when its owner has no source-to-target
|
|
1233
|
+
// mapping.
|
|
1615
1234
|
return this.context.findTargetElementId(aspect.element.id) !== core_bentley_1.Id64.invalid;
|
|
1616
1235
|
}
|
|
1236
|
+
/** Records the target aspects of an owner batch before its source aspects are imported.
|
|
1237
|
+
* The returned callback deletes the recorded replaceable aspects that the importer did not reuse, or discards the recorded state if the export failed.
|
|
1238
|
+
*/
|
|
1239
|
+
async prepareElementAspects(excludedElementAspectClassFullNames, elementIds) {
|
|
1240
|
+
if (!this.exporter.visitElements)
|
|
1241
|
+
return undefined;
|
|
1242
|
+
if (elementIds === undefined)
|
|
1243
|
+
return undefined;
|
|
1244
|
+
const targetElementIds = new Set();
|
|
1245
|
+
for (const sourceElementId of elementIds) {
|
|
1246
|
+
const targetElementId = this.context.findTargetElementId(sourceElementId);
|
|
1247
|
+
if (core_bentley_1.Id64.isValid(targetElementId)) {
|
|
1248
|
+
targetElementIds.add(targetElementId);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const cleanup = this.importer.elementAspectCleanup;
|
|
1252
|
+
const targetScopeElementIds = this.getTargetScopeElementIds();
|
|
1253
|
+
await cleanup.collect(targetElementIds, excludedElementAspectClassFullNames, (aspect) => (0, ProvenanceManager_1.isTransformerProvenanceAspect)(aspect, targetScopeElementIds));
|
|
1254
|
+
return async (exported) => {
|
|
1255
|
+
if (exported)
|
|
1256
|
+
await cleanup.deleteUnretained();
|
|
1257
|
+
else
|
|
1258
|
+
cleanup.discard();
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1617
1261
|
/** Override of [IModelExportHandler.onExportElementUniqueAspect]($transformer) that imports an ElementUniqueAspect into the target iModel when it is exported from the source iModel.
|
|
1618
1262
|
* This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
|
|
1619
1263
|
*/
|
|
@@ -1622,7 +1266,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1622
1266
|
if (!(await this.doAllReferencesExistInTarget(sourceAspect))) {
|
|
1623
1267
|
this._partiallyCommittedAspectIds.add(sourceAspect.id);
|
|
1624
1268
|
}
|
|
1625
|
-
const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
|
|
1269
|
+
const targetId = await this.importer.importElementUniqueAspect(targetAspectProps);
|
|
1626
1270
|
this.context.remapElementAspect(sourceAspect.id, targetId);
|
|
1627
1271
|
}
|
|
1628
1272
|
/** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
|
|
@@ -1632,29 +1276,32 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1632
1276
|
async onExportElementMultiAspects(sourceAspects) {
|
|
1633
1277
|
// Transform source ElementMultiAspects into target ElementAspectProps
|
|
1634
1278
|
const targetAspectPropsArray = sourceAspects.map(async (srcA) => this.onTransformElementAspect(srcA));
|
|
1635
|
-
|
|
1279
|
+
for (const a of sourceAspects) {
|
|
1636
1280
|
if (!(await this.doAllReferencesExistInTarget(a))) {
|
|
1637
1281
|
this._partiallyCommittedAspectIds.add(a.id);
|
|
1638
1282
|
}
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
const targetIds = this.importer.importElementMultiAspects(
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
return (!this._options.includeSourceProvenance ||
|
|
1645
|
-
!isExternalSourceAspectFromTransformer);
|
|
1646
|
-
});
|
|
1283
|
+
}
|
|
1284
|
+
const targetAspectProps = await Promise.all(targetAspectPropsArray);
|
|
1285
|
+
const targetIds = await this.importer.importElementMultiAspects(targetAspectProps, this._options.includeSourceProvenance
|
|
1286
|
+
? (0, ProvenanceManager_1.sourceProvenanceMatchFilter)(targetAspectProps, this.getTargetScopeElementIds(), this.targetScopeElementId)
|
|
1287
|
+
: undefined);
|
|
1647
1288
|
for (let i = 0; i < targetIds.length; ++i) {
|
|
1648
1289
|
this.context.remapElementAspect(sourceAspects[i].id, targetIds[i]);
|
|
1649
1290
|
}
|
|
1650
1291
|
}
|
|
1292
|
+
_targetScopeElementIds;
|
|
1293
|
+
/** The target scope elements, including the current one. Read by [[initialize]], after the current scope's provenance exists. */
|
|
1294
|
+
getTargetScopeElementIds() {
|
|
1295
|
+
(0, core_bentley_1.assert)(this._targetScopeElementIds !== undefined, "initialize() reads the target scope elements");
|
|
1296
|
+
return this._targetScopeElementIds;
|
|
1297
|
+
}
|
|
1651
1298
|
/** Transform the specified sourceElementAspect into ElementAspectProps for the target iModel.
|
|
1652
1299
|
* @param sourceElementAspect The ElementAspect from the source iModel to be transformed.
|
|
1653
1300
|
* @returns ElementAspectProps for the target iModel.
|
|
1654
1301
|
* @note A subclass can override this method to provide custom transform behavior.
|
|
1655
1302
|
*/
|
|
1656
1303
|
async onTransformElementAspect(sourceElementAspect) {
|
|
1657
|
-
const targetElementAspectProps = await this.
|
|
1304
|
+
const targetElementAspectProps = await this._cloneContext.cloneElementAspect(sourceElementAspect);
|
|
1658
1305
|
return targetElementAspectProps;
|
|
1659
1306
|
}
|
|
1660
1307
|
/** The directory where schemas will be exported, a random temporary directory */
|
|
@@ -1662,82 +1309,27 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1662
1309
|
/** Override of [IModelExportHandler.shouldExportSchema]($transformer) that is called to determine if a schema should be exported
|
|
1663
1310
|
* @note the default behavior doesn't import schemas older than those already in the target
|
|
1664
1311
|
*/
|
|
1665
|
-
shouldExportSchema(schemaKey) {
|
|
1312
|
+
async shouldExportSchema(schemaKey) {
|
|
1666
1313
|
const versionInTarget = this.targetDb.querySchemaVersion(schemaKey.name);
|
|
1667
1314
|
if (versionInTarget === undefined)
|
|
1668
1315
|
return true;
|
|
1669
1316
|
return Semver.gt(`${schemaKey.version.read}.${schemaKey.version.write}.${schemaKey.version.minor}`, core_backend_1.Schema.toSemverString(versionInTarget));
|
|
1670
1317
|
}
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
*
|
|
1674
|
-
* @returns {Promise<ExportSchemaResult>} Although the type is possibly void for backwards compatibility of subclasses,
|
|
1675
|
-
* `IModelTransformer.onExportSchema` always returns an[[IModelExportHandler.ExportSchemaResult]]
|
|
1676
|
-
* with a defined `schemaPath` property, for subclasses to know where the schema was written.
|
|
1677
|
-
* Schemas are *not* guaranteed to be written to [[IModelTransformer._schemaExportDir]] by a
|
|
1678
|
-
* known pattern derivable from the schema's name, so you must use this to find it.
|
|
1318
|
+
/** Serialize a source schema to the temporary directory used by [[processSchemas]].
|
|
1319
|
+
* @returns A path to the serialized schema file.
|
|
1320
|
+
* @note The file name is not derived reliably from the schema name. Use the returned path when a subclass calls `super`.
|
|
1679
1321
|
*/
|
|
1680
1322
|
async onExportSchema(schema) {
|
|
1681
|
-
|
|
1682
|
-
let schemaFileName = schema.name + ext;
|
|
1683
|
-
// many file systems have a max file-name/path-segment size of 255, so we workaround that on all systems
|
|
1684
|
-
const systemMaxPathSegmentSize = 255;
|
|
1685
|
-
// windows usually has a limit for the total path length of 260
|
|
1686
|
-
const windowsMaxPathLimit = 260;
|
|
1687
|
-
if (schemaFileName.length > systemMaxPathSegmentSize ||
|
|
1688
|
-
path.join(this._schemaExportDir, schemaFileName).length >=
|
|
1689
|
-
windowsMaxPathLimit) {
|
|
1690
|
-
// this name should be well under 255 bytes
|
|
1691
|
-
// ( 100 + (Number.MAX_SAFE_INTEGER.toString().length = 16) + (ext.length = 13) ) = 129 which is less than 255
|
|
1692
|
-
// You'd have to be past 2**53-1 (Number.MAX_SAFE_INTEGER) long named schemas in order to hit decimal formatting,
|
|
1693
|
-
// and that's on the scale of at least petabytes. `Map.prototype.size` shouldn't return floating points, and even
|
|
1694
|
-
// if they do they're in scientific notation, size bound and contain no invalid windows path chars
|
|
1695
|
-
schemaFileName = `${schema.name.slice(0, 100)}${this._longNamedSchemasMap.size}${ext}`;
|
|
1696
|
-
nodeAssert(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
|
|
1697
|
-
this._longNamedSchemasMap.set(schema.name, schemaFileName);
|
|
1698
|
-
}
|
|
1699
|
-
this.sourceDb.exportSchema({
|
|
1700
|
-
schemaName: schema.name,
|
|
1701
|
-
outputDirectory: this._schemaExportDir,
|
|
1702
|
-
outputFileName: schemaFileName,
|
|
1703
|
-
});
|
|
1704
|
-
return { schemaPath: path.join(this._schemaExportDir, schemaFileName) };
|
|
1705
|
-
}
|
|
1706
|
-
_makeLongNameResolvingSchemaCtx() {
|
|
1707
|
-
const result = new core_backend_1.ECSchemaXmlContext();
|
|
1708
|
-
result.setSchemaLocater((key) => {
|
|
1709
|
-
const match = this._longNamedSchemasMap.get(key.name);
|
|
1710
|
-
if (match !== undefined)
|
|
1711
|
-
return path.join(this._schemaExportDir, match);
|
|
1712
|
-
return undefined;
|
|
1713
|
-
});
|
|
1714
|
-
return result;
|
|
1323
|
+
return this._schemaProcessingCoordinator.serializeSourceSchema(schema);
|
|
1715
1324
|
}
|
|
1716
|
-
/**
|
|
1325
|
+
/** Process source schemas and import selected or generated definitions into the target iModel.
|
|
1326
|
+
* @param options Selects the schema processing strategy.
|
|
1327
|
+
* @note When no strategy is supplied, [[NewerVersionSchemaImportStrategy]] is used.
|
|
1717
1328
|
* @note For performance reasons, it is recommended that [IModelDb.saveChanges]($backend) be called after `processSchemas` is complete.
|
|
1718
1329
|
* It is more efficient to process *data* changes after the schema changes have been saved.
|
|
1719
1330
|
*/
|
|
1720
|
-
async processSchemas() {
|
|
1721
|
-
|
|
1722
|
-
try {
|
|
1723
|
-
core_backend_1.IModelJsFs.mkdirSync(this._schemaExportDir);
|
|
1724
|
-
this._longNamedSchemasMap.clear();
|
|
1725
|
-
await this.exporter.exportSchemas();
|
|
1726
|
-
const exportedSchemaFiles = core_backend_1.IModelJsFs.readdirSync(this._schemaExportDir);
|
|
1727
|
-
if (exportedSchemaFiles.length === 0)
|
|
1728
|
-
return;
|
|
1729
|
-
const schemaFullPaths = exportedSchemaFiles.map((s) => path.join(this._schemaExportDir, s));
|
|
1730
|
-
const maybeLongNameResolvingSchemaCtx = this._longNamedSchemasMap.size > 0
|
|
1731
|
-
? this._makeLongNameResolvingSchemaCtx()
|
|
1732
|
-
: undefined;
|
|
1733
|
-
return await this.targetDb.importSchemas(schemaFullPaths, {
|
|
1734
|
-
ecSchemaXmlContext: maybeLongNameResolvingSchemaCtx,
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1737
|
-
finally {
|
|
1738
|
-
core_backend_1.IModelJsFs.removeSync(this._schemaExportDir);
|
|
1739
|
-
this._longNamedSchemasMap.clear();
|
|
1740
|
-
}
|
|
1331
|
+
async processSchemas(options) {
|
|
1332
|
+
return this._schemaProcessingCoordinator.process(options?.strategy ?? new SchemaProcessingStrategy_1.NewerVersionSchemaImportStrategy());
|
|
1741
1333
|
}
|
|
1742
1334
|
/** Cause all fonts to be exported from the source iModel and imported into the target iModel.
|
|
1743
1335
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
@@ -1748,8 +1340,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1748
1340
|
return this.exporter.exportFonts();
|
|
1749
1341
|
}
|
|
1750
1342
|
/** Override of [IModelExportHandler.onExportFont]($transformer) that imports a font into the target iModel when it is exported from the source iModel. */
|
|
1751
|
-
onExportFont(font, _isUpdate) {
|
|
1752
|
-
this.
|
|
1343
|
+
async onExportFont(font, _isUpdate) {
|
|
1344
|
+
this._cloneContext.importFont(font.id);
|
|
1753
1345
|
}
|
|
1754
1346
|
/** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
|
|
1755
1347
|
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
@@ -1768,21 +1360,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1768
1360
|
/** Override of [IModelExportHandler.shouldExportCodeSpec]($transformer) that is called to determine if a CodeSpec should be exported from the source iModel.
|
|
1769
1361
|
* @note Reaching this point means that the CodeSpec has passed the standard exclusion checks in [IModelExporter]($transformer).
|
|
1770
1362
|
*/
|
|
1771
|
-
shouldExportCodeSpec(_sourceCodeSpec) {
|
|
1363
|
+
async shouldExportCodeSpec(_sourceCodeSpec) {
|
|
1772
1364
|
return true;
|
|
1773
1365
|
}
|
|
1774
1366
|
/** Override of [IModelExportHandler.onExportCodeSpec]($transformer) that imports a CodeSpec into the target iModel when it is exported from the source iModel. */
|
|
1775
|
-
onExportCodeSpec(sourceCodeSpec) {
|
|
1776
|
-
this.
|
|
1367
|
+
async onExportCodeSpec(sourceCodeSpec) {
|
|
1368
|
+
this._cloneContext.importCodeSpec(sourceCodeSpec.id);
|
|
1777
1369
|
}
|
|
1778
|
-
/** Recursively import all Elements and sub-Models that descend from the specified Subject
|
|
1370
|
+
/** Recursively import all Elements and sub-Models that descend from the specified Subject.
|
|
1371
|
+
* @note After composing subset processing calls, call [[IModelImporter.finalize]] before saving target changes.
|
|
1372
|
+
*/
|
|
1779
1373
|
async processSubject(sourceSubjectId, targetSubjectId) {
|
|
1780
1374
|
await this.initialize();
|
|
1781
1375
|
this.sourceDb.elements.getElement(sourceSubjectId, core_backend_1.Subject); // throws if sourceSubjectId is not a Subject
|
|
1782
1376
|
this.targetDb.elements.getElement(targetSubjectId, core_backend_1.Subject); // throws if targetSubjectId is not a Subject
|
|
1783
1377
|
this.context.remapElement(sourceSubjectId, targetSubjectId);
|
|
1784
|
-
await this.
|
|
1785
|
-
|
|
1378
|
+
await this.processScopedElementExport(async () => {
|
|
1379
|
+
await this.processChildElements(sourceSubjectId);
|
|
1380
|
+
await this.processSubjectSubModels(sourceSubjectId);
|
|
1381
|
+
});
|
|
1786
1382
|
await this.completePartiallyCommittedElements();
|
|
1787
1383
|
await this.completePartiallyCommittedAspects();
|
|
1788
1384
|
}
|
|
@@ -1791,6 +1387,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1791
1387
|
_sourceChangeDataState = "uninited";
|
|
1792
1388
|
/** length === 0 when _changeDataState = "no-change", length > 0 means "has-changes", otherwise undefined */
|
|
1793
1389
|
_csFileProps = undefined;
|
|
1390
|
+
_deletionRecordsByChangeset;
|
|
1794
1391
|
/**
|
|
1795
1392
|
* Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
|
|
1796
1393
|
* 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.
|
|
@@ -1800,15 +1397,38 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1800
1397
|
async initialize() {
|
|
1801
1398
|
if (this._initialized)
|
|
1802
1399
|
return;
|
|
1400
|
+
this.assertEditTxnActive();
|
|
1803
1401
|
await this.initScopeProvenance();
|
|
1402
|
+
this._targetScopeElementIds = await (0, ProvenanceManager_1.queryTargetScopeElementIds)(this.targetDb);
|
|
1804
1403
|
await this._tryInitChangesetData(this._options.argsForProcessChanges);
|
|
1805
|
-
await this.
|
|
1806
|
-
|
|
1807
|
-
await this.
|
|
1404
|
+
await this._cloneContext.initialize();
|
|
1405
|
+
const exporterInitOptions = await this.getExportInitOpts(this._options.argsForProcessChanges ?? {});
|
|
1406
|
+
await this.initializeChangesetScanAndExporter(exporterInitOptions);
|
|
1808
1407
|
// 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).
|
|
1809
1408
|
await this.processChangesets();
|
|
1810
1409
|
this._initialized = true;
|
|
1811
1410
|
}
|
|
1411
|
+
async initializeChangesetScanAndExporter(exporterInitOptions) {
|
|
1412
|
+
if (this._csFileProps !== undefined &&
|
|
1413
|
+
this._csFileProps.length > 0 &&
|
|
1414
|
+
this.sourceDb.isBriefcaseDb()) {
|
|
1415
|
+
const changedInstanceIds = this.exporter.sourceDbChanges ??
|
|
1416
|
+
("changedInstanceIds" in exporterInitOptions
|
|
1417
|
+
? exporterInitOptions.changedInstanceIds
|
|
1418
|
+
: new IModelExporter_1.ChangedInstanceIds(this.sourceDb));
|
|
1419
|
+
this._deletionRecordsByChangeset = await ChangesetScanner_1.ChangesetScanner.scan(this.sourceDb, this._csFileProps, changedInstanceIds, {
|
|
1420
|
+
populateChangedInstanceIds: this.exporter.sourceDbChanges === undefined &&
|
|
1421
|
+
!("changedInstanceIds" in exporterInitOptions),
|
|
1422
|
+
});
|
|
1423
|
+
await this.exporter.initialize({
|
|
1424
|
+
changedInstanceIds,
|
|
1425
|
+
skipPropagateChangesToRootElements: exporterInitOptions.skipPropagateChangesToRootElements,
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
else {
|
|
1429
|
+
await this.exporter.initialize(exporterInitOptions);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1812
1432
|
/**
|
|
1813
1433
|
* Reads all the changeset files in the private member of the transformer: _csFileProps
|
|
1814
1434
|
* and finds the corresponding target entity for any deleted source entities and remaps the sourceId to the targetId.
|
|
@@ -1816,12 +1436,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1816
1436
|
* @returns void
|
|
1817
1437
|
*/
|
|
1818
1438
|
async processChangesets() {
|
|
1819
|
-
await this.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1439
|
+
await this._provenanceManager.forEachTrackedElement((sourceElementId, targetElementId) => {
|
|
1820
1440
|
this.context.remapElement(sourceElementId, targetElementId);
|
|
1821
1441
|
});
|
|
1822
1442
|
if (this.exporter.sourceDbChanges)
|
|
1823
1443
|
await this.addCustomChanges(this.exporter.sourceDbChanges);
|
|
1824
|
-
|
|
1444
|
+
const csFileProps = this._csFileProps;
|
|
1445
|
+
if (csFileProps === undefined || csFileProps.length === 0) {
|
|
1825
1446
|
if (this.exporter.sourceDbChanges === undefined ||
|
|
1826
1447
|
!this.exporter.sourceDbChanges.hasChanges)
|
|
1827
1448
|
return;
|
|
@@ -1829,6 +1450,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1829
1450
|
if (this._sourceChangeDataState === "no-changes")
|
|
1830
1451
|
this._sourceChangeDataState = "has-changes";
|
|
1831
1452
|
}
|
|
1453
|
+
const deletionRecordsByChangeset = this._deletionRecordsByChangeset;
|
|
1454
|
+
if (deletionRecordsByChangeset === undefined)
|
|
1455
|
+
return;
|
|
1832
1456
|
const relationshipECClassIdsToSkip = new Set();
|
|
1833
1457
|
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)", undefined, { usePrimaryConn: true })) {
|
|
1834
1458
|
relationshipECClassIdsToSkip.add(row.ECInstanceId);
|
|
@@ -1837,6 +1461,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1837
1461
|
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)", undefined, { usePrimaryConn: true })) {
|
|
1838
1462
|
relationshipECClassIds.add(row.ECInstanceId);
|
|
1839
1463
|
}
|
|
1464
|
+
const elementAspectECClassIds = new Set();
|
|
1465
|
+
for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementAspect)", undefined, { usePrimaryConn: true })) {
|
|
1466
|
+
elementAspectECClassIds.add(row.ECInstanceId);
|
|
1467
|
+
}
|
|
1840
1468
|
// For later use when processing deletes.
|
|
1841
1469
|
const alreadyImportedElementInserts = new Set();
|
|
1842
1470
|
const alreadyImportedModelInserts = new Set();
|
|
@@ -1851,46 +1479,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1851
1479
|
alreadyImportedModelInserts.add(targetModelId);
|
|
1852
1480
|
});
|
|
1853
1481
|
this._deletedSourceRelationshipData = new Map();
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
fileName: csFile.pathname,
|
|
1857
|
-
db: this.sourceDb,
|
|
1858
|
-
disableSchemaCheck: true,
|
|
1859
|
-
});
|
|
1860
|
-
const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
|
|
1861
|
-
const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier(this.sourceDb);
|
|
1862
|
-
while (csAdaptor.step()) {
|
|
1863
|
-
ecChangeUnifier.appendFrom(csAdaptor);
|
|
1864
|
-
}
|
|
1865
|
-
const changes = [...ecChangeUnifier.instances];
|
|
1482
|
+
const isElementAspectDeletion = (change) => elementAspectECClassIds.has(change.ecClassId);
|
|
1483
|
+
for (const changes of deletionRecordsByChangeset) {
|
|
1866
1484
|
/** a map of element ids to this transformation scope's ESA data for that element, in case the ESA is deleted in the target */
|
|
1867
1485
|
const elemIdToScopeEsa = new Map();
|
|
1868
1486
|
for (const change of changes) {
|
|
1869
|
-
if (change.
|
|
1870
|
-
relationshipECClassIdsToSkip.has(change.ECClassId))
|
|
1487
|
+
if (relationshipECClassIdsToSkip.has(change.ecClassId))
|
|
1871
1488
|
continue;
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
change
|
|
1875
|
-
change.
|
|
1876
|
-
change.
|
|
1877
|
-
elemIdToScopeEsa.set(change.Element.Id, change);
|
|
1489
|
+
if (change.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
|
|
1490
|
+
change.scopeId === this.targetScopeElementId &&
|
|
1491
|
+
change.kind === core_backend_1.ExternalSourceAspect.Kind.Element &&
|
|
1492
|
+
change.elementId !== undefined) {
|
|
1493
|
+
elemIdToScopeEsa.set(change.elementId, change);
|
|
1878
1494
|
}
|
|
1879
1495
|
}
|
|
1880
1496
|
// Loop to process deletes.
|
|
1881
1497
|
for (const change of changes) {
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
if (
|
|
1885
|
-
throw new Error(`ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change?.$meta?.tables}`);
|
|
1886
|
-
if (changeType === undefined)
|
|
1887
|
-
throw new Error(`ChangeType was undefined for id: ${change.ECInstanceId}.`);
|
|
1888
|
-
if (changeType !== "Deleted" ||
|
|
1889
|
-
relationshipECClassIdsToSkip.has(ecClassId))
|
|
1498
|
+
if (relationshipECClassIdsToSkip.has(change.ecClassId))
|
|
1499
|
+
continue;
|
|
1500
|
+
if (isElementAspectDeletion(change))
|
|
1890
1501
|
continue;
|
|
1891
|
-
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(ecClassId
|
|
1502
|
+
await this.processDeletedOp(change, elemIdToScopeEsa, relationshipECClassIds.has(change.ecClassId), alreadyImportedElementInserts, alreadyImportedModelInserts);
|
|
1892
1503
|
}
|
|
1893
|
-
csReader.close();
|
|
1894
1504
|
}
|
|
1895
1505
|
return;
|
|
1896
1506
|
}
|
|
@@ -1923,7 +1533,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1923
1533
|
if (notConnectedModel || noChanges)
|
|
1924
1534
|
return;
|
|
1925
1535
|
/**
|
|
1926
|
-
*
|
|
1536
|
+
* If the deleted source entity is in the provenanceDb, then we can use its ids to query for ESAs.
|
|
1927
1537
|
* This is because the ESAs are stored on an element Id thats present in the provenanceDb.
|
|
1928
1538
|
*/
|
|
1929
1539
|
const changeDataInProvenanceDb = this.sourceDb === (await this.getProvenanceDb());
|
|
@@ -1935,7 +1545,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1935
1545
|
}
|
|
1936
1546
|
const fedGuid = isRelationship
|
|
1937
1547
|
? element?.federationGuid
|
|
1938
|
-
: change.
|
|
1548
|
+
: change.federationGuid;
|
|
1939
1549
|
if (changeDataInProvenanceDb) {
|
|
1940
1550
|
// TODO: clarify what happens if there are multiple (e.g. elements were merged)
|
|
1941
1551
|
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([
|
|
@@ -1946,7 +1556,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1946
1556
|
identifierValue = row.Identifier;
|
|
1947
1557
|
}
|
|
1948
1558
|
identifierValue =
|
|
1949
|
-
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.
|
|
1559
|
+
identifierValue ?? mapOfDeletedElemIdToScopeEsas.get(id)?.identifier;
|
|
1950
1560
|
}
|
|
1951
1561
|
// Check for targetId by an esa first
|
|
1952
1562
|
if (changeDataInProvenanceDb && identifierValue) {
|
|
@@ -1960,11 +1570,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1960
1570
|
}
|
|
1961
1571
|
return undefined;
|
|
1962
1572
|
};
|
|
1963
|
-
const changedInstanceId = change.
|
|
1573
|
+
const changedInstanceId = change.ecInstanceId;
|
|
1964
1574
|
if (isRelationship) {
|
|
1965
|
-
const sourceIdOfRelationshipInSource = change.
|
|
1966
|
-
const targetIdOfRelationshipInSource = change.
|
|
1967
|
-
const classFullName = change
|
|
1575
|
+
const sourceIdOfRelationshipInSource = change.sourceECInstanceId;
|
|
1576
|
+
const targetIdOfRelationshipInSource = change.targetECInstanceId;
|
|
1577
|
+
const classFullName = change.classFullName;
|
|
1578
|
+
if (sourceIdOfRelationshipInSource === undefined ||
|
|
1579
|
+
targetIdOfRelationshipInSource === undefined) {
|
|
1580
|
+
core_bentley_1.ITwinError.throwError({
|
|
1581
|
+
iTwinErrorId: {
|
|
1582
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1583
|
+
key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
|
|
1584
|
+
},
|
|
1585
|
+
message: `Relationship deletion ${changedInstanceId} is missing an endpoint.`,
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1968
1588
|
const sourceIdOfRelationshipInTarget = await getTargetIdFromSourceId(sourceIdOfRelationshipInSource);
|
|
1969
1589
|
const targetIdOfRelationshipInTarget = await getTargetIdFromSourceId(targetIdOfRelationshipInSource);
|
|
1970
1590
|
if (sourceIdOfRelationshipInTarget && targetIdOfRelationshipInTarget) {
|
|
@@ -1974,8 +1594,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1974
1594
|
targetIdInTarget: targetIdOfRelationshipInTarget,
|
|
1975
1595
|
});
|
|
1976
1596
|
}
|
|
1977
|
-
else if (this.sourceDb ===
|
|
1978
|
-
|
|
1597
|
+
else if (this.sourceDb ===
|
|
1598
|
+
(await this._provenanceManager.getProvenanceSourceDb())) {
|
|
1599
|
+
const relProvenance = await this._provenanceManager.queryProvenanceForRelationship(changedInstanceId, {
|
|
1979
1600
|
classFullName: classFullName ?? "",
|
|
1980
1601
|
sourceId: sourceIdOfRelationshipInSource,
|
|
1981
1602
|
targetId: targetIdOfRelationshipInSource,
|
|
@@ -1991,8 +1612,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1991
1612
|
else {
|
|
1992
1613
|
let targetId = await getTargetIdFromSourceId(changedInstanceId);
|
|
1993
1614
|
if (targetId === undefined &&
|
|
1994
|
-
this.sourceDb ===
|
|
1995
|
-
|
|
1615
|
+
this.sourceDb ===
|
|
1616
|
+
(await this._provenanceManager.getProvenanceSourceDb())) {
|
|
1617
|
+
const contextTargetId = this.context.findTargetElementId(changedInstanceId);
|
|
1618
|
+
if (core_bentley_1.Id64.isValidId64(contextTargetId))
|
|
1619
|
+
targetId = contextTargetId;
|
|
1996
1620
|
}
|
|
1997
1621
|
// since we are processing one changeset at a time, we can see local source deletes
|
|
1998
1622
|
// of entities that were never synced and can be safely ignored
|
|
@@ -2000,7 +1624,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2000
1624
|
if (deletionNotInTarget)
|
|
2001
1625
|
return;
|
|
2002
1626
|
if (targetId === undefined) {
|
|
2003
|
-
throw new
|
|
1627
|
+
throw new Error("targetId should be acquired from source id or transformation context");
|
|
2004
1628
|
}
|
|
2005
1629
|
this.context.remapElement(changedInstanceId, targetId);
|
|
2006
1630
|
// If an entity insert and an entity delete both point to the same entity in target iModel, that means that entity was recreated.
|
|
@@ -2043,20 +1667,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2043
1667
|
?.ignoreMissingChangesetsInSynchronizations &&
|
|
2044
1668
|
startChangesetIndex !== syncVersion.index + 1 &&
|
|
2045
1669
|
syncVersion.index !== -1) {
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
`
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
1670
|
+
core_bentley_1.ITwinError.throwError({
|
|
1671
|
+
iTwinErrorId: {
|
|
1672
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1673
|
+
key: IModelTransformerError_1.IModelTransformerError.SynchronizationRangeInvalid,
|
|
1674
|
+
},
|
|
1675
|
+
message: `synchronization is ${missingChangesets ? "missing changesets" : ""},` +
|
|
1676
|
+
" startChangesetId should be" +
|
|
1677
|
+
" exactly the first changeset *after* the previous synchronization to not miss data." +
|
|
1678
|
+
` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
|
|
1679
|
+
` but the previous synchronization for this targetScopeElem ${syncVersion.id}'` +
|
|
1680
|
+
` which is changeset #${syncVersion.index}. The transformer expected` +
|
|
1681
|
+
` #${syncVersion.index + 1}.`,
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
const changesetsToSkip = await this._provenanceManager.getChangesetsToSkip();
|
|
2060
1685
|
core_bentley_1.Logger.logTrace(loggerCategory, `changesets to skip: ${changesetsToSkip}`);
|
|
2061
1686
|
this._changesetRanges = (0, Algo_1.rangesFromRangeAndSkipped)(startChangesetIndex, endChangesetIndex, changesetsToSkip);
|
|
2062
1687
|
core_bentley_1.Logger.logTrace(loggerCategory, `ranges: ${this._changesetRanges}`);
|
|
@@ -2075,6 +1700,27 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2075
1700
|
this._sourceChangeDataState =
|
|
2076
1701
|
this._csFileProps.length === 0 ? "no-changes" : "has-changes";
|
|
2077
1702
|
}
|
|
1703
|
+
/** Asserts that the EditTxn is active before any write operations. */
|
|
1704
|
+
assertEditTxnActive() {
|
|
1705
|
+
if (!this._targetEditTxn.isActive) {
|
|
1706
|
+
core_bentley_1.ITwinError.throwError({
|
|
1707
|
+
iTwinErrorId: {
|
|
1708
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1709
|
+
key: IModelTransformerError_1.IModelTransformerError.EditTxnNotActive,
|
|
1710
|
+
},
|
|
1711
|
+
message: "The target EditTxn must be started before calling process(). Call targetEditTxn.start() before invoking the transformer.",
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
if (this._sourceEditTxn && !this._sourceEditTxn.isActive) {
|
|
1715
|
+
core_bentley_1.ITwinError.throwError({
|
|
1716
|
+
iTwinErrorId: {
|
|
1717
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1718
|
+
key: IModelTransformerError_1.IModelTransformerError.EditTxnNotActive,
|
|
1719
|
+
},
|
|
1720
|
+
message: "The source EditTxn was provided but is not active. Call sourceEditTxn.start() before invoking the transformer.",
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
2078
1724
|
/**
|
|
2079
1725
|
* The behavior of process is influenced by [[IModelTransformOptions.argsForProcessChanges]] being defined or not defined during construction passed of the IModelTransformer.
|
|
2080
1726
|
* @section When argsForProcessChanges are defined:
|
|
@@ -2109,23 +1755,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2109
1755
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2110
1756
|
*/
|
|
2111
1757
|
async processAll() {
|
|
1758
|
+
this._targetElementIdsRemappedByCode.clear();
|
|
1759
|
+
this._targetModelsImportedInCurrentTransform.clear();
|
|
1760
|
+
// processAll always has changes to process, so mark it as such for version tracking
|
|
1761
|
+
this._sourceChangeDataState = "has-changes";
|
|
2112
1762
|
await this.exporter.exportCodeSpecs();
|
|
2113
1763
|
await this.exporter.exportFonts();
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
1764
|
+
await this.exporter.elementAspectExportCoordinator.run(async () => {
|
|
1765
|
+
if (this._options.skipPropagateChangesToRootElements) {
|
|
1766
|
+
// The RepositoryModel and root Subject of the target iModel should not be transformed.
|
|
1767
|
+
await this.exporter.exportChildElements(core_common_1.IModel.rootSubjectId); // start below the root Subject
|
|
1768
|
+
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
|
|
1769
|
+
await this.exporter.exportSubModels(core_common_1.IModel.repositoryModelId); // start below the RepositoryModel
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
|
|
1773
|
+
}
|
|
1774
|
+
await this.completePartiallyCommittedElements();
|
|
1775
|
+
});
|
|
2125
1776
|
await this.completePartiallyCommittedAspects();
|
|
2126
1777
|
await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
2127
1778
|
if (this._options.forceExternalSourceAspectProvenance &&
|
|
2128
|
-
this.shouldDetectDeletes()) {
|
|
1779
|
+
(await this.shouldDetectDeletes())) {
|
|
2129
1780
|
core_bentley_1.Logger.logWarning(loggerCategory, "This workflows was deprecated in v1 and is no longer supported");
|
|
2130
1781
|
}
|
|
2131
1782
|
if (this._options.optimizeGeometry)
|
|
@@ -2133,21 +1784,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2133
1784
|
this.importer.computeProjectExtents();
|
|
2134
1785
|
await this.finalizeTransformation();
|
|
2135
1786
|
}
|
|
2136
|
-
/** previous provenance, either a federation guid, a `${sourceFedGuid}/${targetFedGuid}` pair, or required aspect props */
|
|
2137
|
-
_lastProvenanceEntityInfo = nullLastProvenanceEntityInfo;
|
|
2138
|
-
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
2139
|
-
this._lastProvenanceEntityInfo =
|
|
2140
|
-
typeof sourceAspect === "string"
|
|
2141
|
-
? sourceAspect
|
|
2142
|
-
: {
|
|
2143
|
-
entityId: sourceAspect.element.id,
|
|
2144
|
-
aspectId: sourceAspect.id,
|
|
2145
|
-
aspectVersion: sourceAspect.version ?? "",
|
|
2146
|
-
aspectKind: isRelationship
|
|
2147
|
-
? core_backend_1.ExternalSourceAspect.Kind.Relationship
|
|
2148
|
-
: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
2149
|
-
};
|
|
2150
|
-
}
|
|
2151
1787
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2152
1788
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2153
1789
|
* @note the transformer assumes that you saveChanges after processing changes. You should not
|
|
@@ -2158,6 +1794,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2158
1794
|
* @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.
|
|
2159
1795
|
*/
|
|
2160
1796
|
async processChanges(options) {
|
|
1797
|
+
this._targetElementIdsRemappedByCode.clear();
|
|
1798
|
+
this._targetModelsImportedInCurrentTransform.clear();
|
|
2161
1799
|
// must wait for initialization of synchronization provenance data
|
|
2162
1800
|
await this.exporter.exportChanges(await this.getExportInitOpts(options));
|
|
2163
1801
|
await this.completePartiallyCommittedElements();
|
|
@@ -2167,7 +1805,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2167
1805
|
this.importer.computeProjectExtents();
|
|
2168
1806
|
await this.finalizeTransformation();
|
|
2169
1807
|
const defaultSaveTargetChanges = () => {
|
|
2170
|
-
this.
|
|
1808
|
+
this._targetEditTxn.saveChanges();
|
|
2171
1809
|
};
|
|
2172
1810
|
await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
2173
1811
|
}
|
|
@@ -2177,6 +1815,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2177
1815
|
async getExportInitOpts(opts) {
|
|
2178
1816
|
if (!this._options.argsForProcessChanges)
|
|
2179
1817
|
return {};
|
|
1818
|
+
if ("changedInstanceIds" in opts) {
|
|
1819
|
+
return {
|
|
1820
|
+
changedInstanceIds: opts.changedInstanceIds,
|
|
1821
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
2180
1824
|
const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
|
|
2181
1825
|
return {
|
|
2182
1826
|
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
@@ -2215,16 +1859,14 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2215
1859
|
/** Accumulates the mapping of sourceElementIds to targetElementIds from the elements in the template model that were cloned. */
|
|
2216
1860
|
_sourceIdToTargetIdMap;
|
|
2217
1861
|
/** Construct a new TemplateModelCloner
|
|
2218
|
-
* @param
|
|
2219
|
-
* @param targetDb Optionally specify the target IModelDb where the cloned template will be inserted.
|
|
2220
|
-
* Typically this is left unspecified, and the default is to use the sourceDb as the target
|
|
1862
|
+
* @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.
|
|
2221
1863
|
* @note The expectation is that the template definitions are within the same iModel where instances will be placed.
|
|
2222
1864
|
*/
|
|
2223
|
-
constructor(
|
|
2224
|
-
const target = new IModelImporter_1.IModelImporter(
|
|
1865
|
+
constructor(editTxn) {
|
|
1866
|
+
const target = new IModelImporter_1.IModelImporter(editTxn, {
|
|
2225
1867
|
autoExtendProjectExtents: false, // autoExtendProjectExtents is intended for transformation service use cases, not template --> instance cloning
|
|
2226
1868
|
});
|
|
2227
|
-
super(
|
|
1869
|
+
super({ source: editTxn.iModel, target }, { noProvenance: true }); // WIP: need to decide the proper way to handle provenance
|
|
2228
1870
|
}
|
|
2229
1871
|
/** Place a template from the sourceDb at the specified placement in the target model within the targetDb.
|
|
2230
1872
|
* @param sourceTemplateModelId The Id of the template model in the sourceDb
|
|
@@ -2275,7 +1917,13 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2275
1917
|
// TODO: consider going through all definition elements at once and remapping them to themselves
|
|
2276
1918
|
if (!core_backend_1.EntityReferences.isValid(await this.context.findTargetEntityId(referenceId))) {
|
|
2277
1919
|
if (this.context.isBetweenIModels) {
|
|
2278
|
-
|
|
1920
|
+
core_bentley_1.ITwinError.throwError({
|
|
1921
|
+
iTwinErrorId: {
|
|
1922
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1923
|
+
key: IModelTransformerError_1.IModelTransformerError.DependencyMappingMissing,
|
|
1924
|
+
},
|
|
1925
|
+
message: `Remapping for source dependency ${referenceId} not found for target iModel`,
|
|
1926
|
+
});
|
|
2279
1927
|
}
|
|
2280
1928
|
else {
|
|
2281
1929
|
const definitionElement = this.sourceDb.elements.tryGetElement(referenceId, core_backend_1.DefinitionElement);
|
|
@@ -2284,7 +1932,13 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2284
1932
|
this.context.remapElement(referenceId, referenceId); // when in the same iModel, can use existing DefinitionElements without remapping
|
|
2285
1933
|
}
|
|
2286
1934
|
else {
|
|
2287
|
-
|
|
1935
|
+
core_bentley_1.ITwinError.throwError({
|
|
1936
|
+
iTwinErrorId: {
|
|
1937
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1938
|
+
key: IModelTransformerError_1.IModelTransformerError.DependencyMappingMissing,
|
|
1939
|
+
},
|
|
1940
|
+
message: `Remapping for dependency ${referenceId} not found`,
|
|
1941
|
+
});
|
|
2288
1942
|
}
|
|
2289
1943
|
}
|
|
2290
1944
|
}
|
|
@@ -2297,7 +1951,7 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2297
1951
|
const placementClass = is3d ? core_common_1.Placement3d : core_common_1.Placement2d;
|
|
2298
1952
|
const placement = placementClass.fromJSON(targetElementProps.placement);
|
|
2299
1953
|
if (placement.isValid) {
|
|
2300
|
-
|
|
1954
|
+
(0, node_assert_1.strict)(this._transform3d);
|
|
2301
1955
|
placement.multiplyTransform(this._transform3d);
|
|
2302
1956
|
targetElementProps.placement = placement;
|
|
2303
1957
|
}
|