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