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