@itwin/imodel-transformer 2.0.0-dev.60 → 2.0.0-dev.62
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/lib/cjs/ElementBulkDelete.d.ts +25 -6
- package/lib/cjs/ElementBulkDelete.d.ts.map +1 -1
- package/lib/cjs/ElementBulkDelete.js +321 -18
- package/lib/cjs/ElementBulkDelete.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +7 -3
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +31 -8
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +13 -11
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Id64Array, Id64String } from "@itwin/core-bentley";
|
|
2
2
|
import { IModelDb } from "@itwin/core-backend";
|
|
3
|
-
/**
|
|
3
|
+
/** Native deletion calls needed to delete a set of element trees without a partial result.
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export interface BulkDeletePlan {
|
|
7
|
+
/** Roots for each native deletion call, in execution order. Empty when nothing needs deleting. */
|
|
8
|
+
readonly phases: readonly Id64Array[];
|
|
9
|
+
/** Elements kept because an element outside the deleted trees still references them, each mapped to one such
|
|
10
|
+
* referencing element. Their child elements and sub-models, and the elements whose deletion would delete them,
|
|
11
|
+
* are kept too.
|
|
12
|
+
*/
|
|
13
|
+
readonly keptReferences: ReadonlyMap<Id64String, Id64String>;
|
|
14
|
+
}
|
|
15
|
+
/** Plans native bulk deletion of the specified element trees.
|
|
16
|
+
*
|
|
17
|
+
* An element that something outside the deleted trees still references is kept, together with what it
|
|
18
|
+
* needs to stay valid, and the rest is deleted. Keeping elements can leave new outside references, so the
|
|
19
|
+
* plan repeats until none remain.
|
|
4
20
|
*
|
|
5
|
-
* Native
|
|
6
|
-
*
|
|
7
|
-
*
|
|
21
|
+
* Native validation checks each root against the iModel before the call, so a root is refused when its tree
|
|
22
|
+
* contains a definition, such as a category or a view, that a non-definition element in the same call still
|
|
23
|
+
* uses. The plan deletes those users in an earlier call than the definitions they use. Validation stays enabled
|
|
24
|
+
* for every call, so a reference this plan does not know about fails loudly instead of leaving a dangling
|
|
25
|
+
* reference. That includes elements outside the trees whose geometry uses a definition in them, such as a
|
|
26
|
+
* geometry part: only native code can read geometry, so those uses are not checked in advance.
|
|
8
27
|
* @internal
|
|
9
28
|
*/
|
|
10
|
-
export declare function
|
|
29
|
+
export declare function planBulkDelete(targetDb: IModelDb, elementIds: ReadonlySet<Id64String>): Promise<BulkDeletePlan>;
|
|
11
30
|
//# sourceMappingURL=ElementBulkDelete.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElementBulkDelete.d.ts","sourceRoot":"","sources":["../../src/ElementBulkDelete.ts"],"names":[],"mappings":"AAKA,OAAO,
|
|
1
|
+
{"version":3,"file":"ElementBulkDelete.d.ts","sourceRoot":"","sources":["../../src/ElementBulkDelete.ts"],"names":[],"mappings":"AAKA,OAAO,EAAU,SAAS,EAAW,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;CAC9D;AASD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,WAAW,CAAC,UAAU,CAAC,GAClC,OAAO,CAAC,cAAc,CAAC,CAkCzB"}
|
|
@@ -4,27 +4,144 @@
|
|
|
4
4
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
5
|
*--------------------------------------------------------------------------------------------*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.
|
|
7
|
+
exports.planBulkDelete = planBulkDelete;
|
|
8
|
+
const core_bentley_1 = require("@itwin/core-bentley");
|
|
8
9
|
const core_common_1 = require("@itwin/core-common");
|
|
9
|
-
/**
|
|
10
|
+
/** Plans native bulk deletion of the specified element trees.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* An element that something outside the deleted trees still references is kept, together with what it
|
|
13
|
+
* needs to stay valid, and the rest is deleted. Keeping elements can leave new outside references, so the
|
|
14
|
+
* plan repeats until none remain.
|
|
15
|
+
*
|
|
16
|
+
* Native validation checks each root against the iModel before the call, so a root is refused when its tree
|
|
17
|
+
* contains a definition, such as a category or a view, that a non-definition element in the same call still
|
|
18
|
+
* uses. The plan deletes those users in an earlier call than the definitions they use. Validation stays enabled
|
|
19
|
+
* for every call, so a reference this plan does not know about fails loudly instead of leaving a dangling
|
|
20
|
+
* reference. That includes elements outside the trees whose geometry uses a definition in them, such as a
|
|
21
|
+
* geometry part: only native code can read geometry, so those uses are not checked in advance.
|
|
14
22
|
* @internal
|
|
15
23
|
*/
|
|
16
|
-
async function
|
|
17
|
-
const
|
|
24
|
+
async function planBulkDelete(targetDb, elementIds) {
|
|
25
|
+
const requestedIds = new Set(elementIds);
|
|
26
|
+
const keptReferences = new Map();
|
|
27
|
+
for (;;) {
|
|
28
|
+
const trees = await queryDeletionTrees(targetDb, requestedIds);
|
|
29
|
+
if (trees.roots.size === 0)
|
|
30
|
+
return { phases: [], keptReferences };
|
|
31
|
+
const references = await queryReferences(targetDb, trees.rootsByElement.keys());
|
|
32
|
+
const blockedIds = new Set();
|
|
33
|
+
for (const { referencingId, referencedId } of references.references) {
|
|
34
|
+
if (trees.rootsByElement.has(referencingId))
|
|
35
|
+
continue;
|
|
36
|
+
blockedIds.add(referencedId);
|
|
37
|
+
if (!keptReferences.has(referencedId))
|
|
38
|
+
keptReferences.set(referencedId, referencingId);
|
|
39
|
+
}
|
|
40
|
+
if (blockedIds.size === 0)
|
|
41
|
+
return {
|
|
42
|
+
phases: await planPhases(targetDb, trees, references),
|
|
43
|
+
keptReferences,
|
|
44
|
+
};
|
|
45
|
+
// Every blocked element is reached from a requested element, and queryKeptElements walks back up to it,
|
|
46
|
+
// so each pass removes at least one requested element and the loop ends.
|
|
47
|
+
const requestedCount = requestedIds.size;
|
|
48
|
+
for (const id of await queryKeptElements(targetDb, blockedIds))
|
|
49
|
+
requestedIds.delete(id);
|
|
50
|
+
(0, core_bentley_1.assert)(requestedIds.size < requestedCount, "Keeping referenced elements must remove a requested element");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Orders the roots of deletion trees that nothing outside them references into native calls. */
|
|
54
|
+
async function planPhases(targetDb, trees, { references, geometryDefinitionIds }) {
|
|
55
|
+
// Elements in the trees that use a definition that is also in the trees. Native validation refuses the definition while they exist.
|
|
56
|
+
const usersOfDeletedDefinitions = new Set();
|
|
57
|
+
// Roots whose trees contain such a definition. They must be deleted after its users.
|
|
58
|
+
const rootsWithUsedDefinitions = new Set();
|
|
59
|
+
const addUsedDefinition = (definitionId) => {
|
|
60
|
+
for (const rootId of trees.rootsByElement.get(definitionId) ?? [])
|
|
61
|
+
rootsWithUsedDefinitions.add(rootId);
|
|
62
|
+
};
|
|
63
|
+
const sameCallReferences = [];
|
|
64
|
+
for (const reference of references) {
|
|
65
|
+
if (reference.usersFirst) {
|
|
66
|
+
usersOfDeletedDefinitions.add(reference.referencingId);
|
|
67
|
+
addUsedDefinition(reference.referencedId);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
sameCallReferences.push(reference);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Only native code can tell which geometry uses a geometry part, material, texture, line style, or sub-category,
|
|
74
|
+
// so every geometric element in the trees counts as a user of every such definition. Deleting the trees that
|
|
75
|
+
// contain those definitions last is enough unless one of them also contains a geometric element; only then are
|
|
76
|
+
// all geometric elements queried, to delete them as roots of their own.
|
|
77
|
+
const geometryRoots = new Set();
|
|
78
|
+
for (const id of geometryDefinitionIds)
|
|
79
|
+
for (const rootId of trees.rootsByElement.get(id) ?? [])
|
|
80
|
+
geometryRoots.add(rootId);
|
|
81
|
+
if (geometryRoots.size > 0) {
|
|
82
|
+
const elementsInGeometryTrees = [...trees.rootsByElement]
|
|
83
|
+
.filter(([, rootIds]) => rootIds.some((id) => geometryRoots.has(id)))
|
|
84
|
+
.map(([id]) => id);
|
|
85
|
+
if ((await queryGeometricElements(targetDb, elementsInGeometryTrees)).length >
|
|
86
|
+
0)
|
|
87
|
+
for (const id of await queryGeometricElements(targetDb, trees.rootsByElement.keys()))
|
|
88
|
+
usersOfDeletedDefinitions.add(id);
|
|
89
|
+
for (const rootId of geometryRoots)
|
|
90
|
+
rootsWithUsedDefinitions.add(rootId);
|
|
91
|
+
}
|
|
92
|
+
if (rootsWithUsedDefinitions.size === 0)
|
|
93
|
+
return [[...trees.roots]];
|
|
94
|
+
// True when a root deleted in the last call removes the element.
|
|
95
|
+
const isInTreeWithUsedDefinition = (id) => (trees.rootsByElement.get(id) ?? []).some((rootId) => rootsWithUsedDefinitions.has(rootId));
|
|
96
|
+
// True when a root deleted in the first call removes the element. An element in overlapping trees can satisfy both checks.
|
|
97
|
+
const isInOtherTree = (id) => (trees.rootsByElement.get(id) ?? []).some((rootId) => !rootsWithUsedDefinitions.has(rootId));
|
|
98
|
+
// Deleting the other roots first works unless a user shares a tree with a used definition, or an other tree
|
|
99
|
+
// contains an element that an element deleted later references, such as its code scope.
|
|
100
|
+
const canDeleteOtherRootsFirst = ![...usersOfDeletedDefinitions].some(isInTreeWithUsedDefinition) &&
|
|
101
|
+
!sameCallReferences.some(({ referencingId, referencedId }) => isInTreeWithUsedDefinition(referencingId) && isInOtherTree(referencedId));
|
|
102
|
+
if (canDeleteOtherRootsFirst) {
|
|
103
|
+
// There are no other roots only when every tree contains a geometry definition and nothing in the trees uses a
|
|
104
|
+
// definition through a queried reference.
|
|
105
|
+
const otherRoots = [...trees.roots].filter((id) => !rootsWithUsedDefinitions.has(id));
|
|
106
|
+
return otherRoots.length > 0
|
|
107
|
+
? [otherRoots, [...rootsWithUsedDefinitions]]
|
|
108
|
+
: [[...rootsWithUsedDefinitions]];
|
|
109
|
+
}
|
|
110
|
+
// Otherwise delete the users and their code dependents as roots of their own. Core refuses to delete an
|
|
111
|
+
// element whose code scopes an element deleted in a later call, so the first call also takes code dependents
|
|
112
|
+
// that have a parent. They are all in the trees; one outside them would have kept its code scope above.
|
|
113
|
+
const userTrees = await queryDeletionTrees(targetDb, usersOfDeletedDefinitions, true);
|
|
114
|
+
const remainingRoots = [...trees.roots].filter((id) => !userTrees.rootsByElement.has(id));
|
|
115
|
+
const phases = [[...userTrees.roots]];
|
|
116
|
+
if (remainingRoots.length > 0)
|
|
117
|
+
phases.push(remainingRoots);
|
|
118
|
+
return phases;
|
|
119
|
+
}
|
|
120
|
+
/** Finds the native deletion roots and every element their deletion removes.
|
|
121
|
+
*
|
|
122
|
+
* Native deletion already cascades through child elements and modeled contents. This query follows
|
|
123
|
+
* those dependencies to find elements whose codes are scoped by an element in a deleted tree. Each
|
|
124
|
+
* code-dependent element becomes another native deletion root. Only top-level code dependents are
|
|
125
|
+
* included unless `includeParentedCodeDependents` is true.
|
|
126
|
+
*/
|
|
127
|
+
async function queryDeletionTrees(targetDb, elementIds, includeParentedCodeDependents = false) {
|
|
128
|
+
const roots = new Set();
|
|
129
|
+
// An element has more than one root only when requested trees overlap. UNION in the query already removes duplicate
|
|
130
|
+
// (element, root) pairs, so an array holds distinct roots without allocating a Set per element.
|
|
131
|
+
const rootsByElement = new Map();
|
|
18
132
|
// CascadeIds pairs each traversed element with a root that the native API must delete. The anchor
|
|
19
|
-
// adds requested
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
133
|
+
// adds requested elements that still exist, except those whose parent or modeled element is also
|
|
134
|
+
// requested: the recursion reaches them anyway, and anchoring them too would walk a nested tree once
|
|
135
|
+
// per requested ancestor. Recursive branches walk child elements and modeled contents while
|
|
136
|
+
// preserving DeleteRootId. An element whose code is scoped by anything already traversed becomes a
|
|
137
|
+
// new root, so recursion also covers its tree and code dependents.
|
|
23
138
|
const query = `
|
|
24
139
|
WITH RECURSIVE CascadeIds(Id, DeleteRootId) AS (
|
|
25
140
|
SELECT element.ECInstanceId, element.ECInstanceId
|
|
26
141
|
FROM bis.Element element
|
|
27
142
|
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
143
|
+
WHERE (element.Parent.Id IS NULL OR element.Parent.Id NOT IN (SELECT id FROM IdSet(:elementIds)))
|
|
144
|
+
AND element.Model.Id NOT IN (SELECT id FROM IdSet(:elementIds))
|
|
28
145
|
UNION
|
|
29
146
|
SELECT child.ECInstanceId, deletionParent.DeleteRootId
|
|
30
147
|
FROM bis.Element child
|
|
@@ -37,19 +154,205 @@ async function findBulkDeleteRoots(targetDb, elementIds) {
|
|
|
37
154
|
SELECT codeDependent.ECInstanceId, codeDependent.ECInstanceId
|
|
38
155
|
FROM bis.Element codeDependent
|
|
39
156
|
INNER JOIN CascadeIds scope ON codeDependent.CodeScope.Id = scope.Id
|
|
40
|
-
WHERE codeDependent.Parent.Id IS NULL
|
|
41
|
-
),
|
|
42
|
-
DeleteRoots(Id) AS (
|
|
43
|
-
SELECT DISTINCT DeleteRootId FROM CascadeIds
|
|
157
|
+
${includeParentedCodeDependents ? "" : "WHERE codeDependent.Parent.Id IS NULL"}
|
|
44
158
|
)
|
|
45
|
-
SELECT Id AS id FROM
|
|
159
|
+
SELECT Id AS id, DeleteRootId AS rootId FROM CascadeIds
|
|
46
160
|
`;
|
|
47
161
|
const params = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
48
162
|
for await (const row of targetDb.createQueryReader(query, params, {
|
|
49
163
|
usePrimaryConn: true,
|
|
50
164
|
})) {
|
|
51
|
-
|
|
165
|
+
roots.add(row.rootId);
|
|
166
|
+
const elementRoots = rootsByElement.get(row.id);
|
|
167
|
+
if (elementRoots)
|
|
168
|
+
elementRoots.push(row.rootId);
|
|
169
|
+
else
|
|
170
|
+
rootsByElement.set(row.id, [row.rootId]);
|
|
171
|
+
}
|
|
172
|
+
return { roots, rootsByElement };
|
|
173
|
+
}
|
|
174
|
+
/** Finds the elements to keep so that the blocked elements stay valid: each blocked element and its child
|
|
175
|
+
* elements and sub-model contents, plus every element whose deletion would delete a kept element or leave its
|
|
176
|
+
* parent, model, or code scope dangling.
|
|
177
|
+
*
|
|
178
|
+
* The whole subtree below a blocked element is kept, even when the element is blocked only as a code scope and
|
|
179
|
+
* its children could be deleted. A kept category needs its sub-categories, because the plan can't see geometry
|
|
180
|
+
* outside the trees that uses them, and keeping too much is safe.
|
|
181
|
+
*/
|
|
182
|
+
async function queryKeptElements(targetDb, blockedIds) {
|
|
183
|
+
const query = `
|
|
184
|
+
WITH RECURSIVE
|
|
185
|
+
Above(Id) AS (
|
|
186
|
+
SELECT ids.id FROM IdSet(:blockedIds) ids
|
|
187
|
+
UNION
|
|
188
|
+
SELECT element.Parent.Id FROM bis.Element element
|
|
189
|
+
INNER JOIN Above ON element.ECInstanceId = Above.Id
|
|
190
|
+
WHERE element.Parent.Id IS NOT NULL
|
|
191
|
+
UNION
|
|
192
|
+
SELECT element.Model.Id FROM bis.Element element
|
|
193
|
+
INNER JOIN Above ON element.ECInstanceId = Above.Id
|
|
194
|
+
UNION
|
|
195
|
+
SELECT element.CodeScope.Id FROM bis.Element element
|
|
196
|
+
INNER JOIN Above ON element.ECInstanceId = Above.Id
|
|
197
|
+
),
|
|
198
|
+
Below(Id) AS (
|
|
199
|
+
SELECT ids.id FROM IdSet(:blockedIds) ids
|
|
200
|
+
UNION
|
|
201
|
+
SELECT element.ECInstanceId FROM bis.Element element
|
|
202
|
+
INNER JOIN Below ON element.Parent.Id = Below.Id
|
|
203
|
+
UNION
|
|
204
|
+
SELECT element.ECInstanceId FROM bis.Element element
|
|
205
|
+
INNER JOIN Below ON element.Model.Id = Below.Id
|
|
206
|
+
)
|
|
207
|
+
SELECT Id AS id FROM Above
|
|
208
|
+
UNION
|
|
209
|
+
SELECT Id FROM Below
|
|
210
|
+
`;
|
|
211
|
+
const keptIds = new Set();
|
|
212
|
+
const params = new core_common_1.QueryBinder().bindIdSet("blockedIds", blockedIds);
|
|
213
|
+
for await (const row of targetDb.createQueryReader(query, params, {
|
|
214
|
+
usePrimaryConn: true,
|
|
215
|
+
}))
|
|
216
|
+
keptIds.add(row.id);
|
|
217
|
+
return keptIds;
|
|
218
|
+
}
|
|
219
|
+
/** Finds BisCore references to the specified elements that native deletion validates and that [[queryDeletionTrees]]
|
|
220
|
+
* does not follow. Parent and model references are not included because the deleted trees contain every child and
|
|
221
|
+
* modeled element.
|
|
222
|
+
*/
|
|
223
|
+
async function queryReferences(targetDb, elementIds) {
|
|
224
|
+
const references = [];
|
|
225
|
+
const geometryDefinitionIds = [];
|
|
226
|
+
// Any element can be a code scope. Every other validated reference targets a definition element, so those are
|
|
227
|
+
// queried only for the definitions among the elements, which are usually few or none.
|
|
228
|
+
const definitionIds = [];
|
|
229
|
+
const firstQuery = `
|
|
230
|
+
SELECT element.ECInstanceId AS referencingId, element.CodeScope.Id AS referencedId, 0 AS isDefinition
|
|
231
|
+
FROM bis.Element element
|
|
232
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.CodeScope.Id
|
|
233
|
+
UNION ALL
|
|
234
|
+
SELECT definition.ECInstanceId, definition.ECInstanceId, 1
|
|
235
|
+
FROM bis.DefinitionElement definition
|
|
236
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = definition.ECInstanceId
|
|
237
|
+
`;
|
|
238
|
+
for await (const row of targetDb.createQueryReader(firstQuery, new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds), { usePrimaryConn: true })) {
|
|
239
|
+
if (row.isDefinition === 1)
|
|
240
|
+
definitionIds.push(row.referencedId);
|
|
241
|
+
else
|
|
242
|
+
references.push({
|
|
243
|
+
referencingId: row.referencingId,
|
|
244
|
+
referencedId: row.referencedId,
|
|
245
|
+
usersFirst: false,
|
|
246
|
+
});
|
|
52
247
|
}
|
|
53
|
-
|
|
248
|
+
if (definitionIds.length === 0)
|
|
249
|
+
return { references, geometryDefinitionIds };
|
|
250
|
+
// Classes and properties added in later BisCore versions.
|
|
251
|
+
const sectionReferences = await querySectionViewReferences(targetDb);
|
|
252
|
+
// Kind 1: native validation refuses the definition while the referencing element exists, even in the same call.
|
|
253
|
+
// Kind 0: deleting both in the same call is enough. Native validation treats a default sub-category, whose ID
|
|
254
|
+
// follows its category's, as used by the category unless both are deleted.
|
|
255
|
+
// Kind 2: a definition that geometry references, so its users can't be queried.
|
|
256
|
+
const definitionQuery = `
|
|
257
|
+
SELECT element.ECInstanceId AS referencingId, element.Category.Id AS referencedId, 1 AS kind
|
|
258
|
+
FROM bis.GeometricElement3d element
|
|
259
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.Category.Id
|
|
260
|
+
UNION ALL
|
|
261
|
+
SELECT element.ECInstanceId, element.Category.Id, 1
|
|
262
|
+
FROM bis.GeometricElement2d element
|
|
263
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.Category.Id
|
|
264
|
+
UNION ALL
|
|
265
|
+
SELECT element.ECInstanceId, element.View.Id, 1
|
|
266
|
+
FROM bis.ViewAttachment element
|
|
267
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.View.Id
|
|
268
|
+
${sectionReferences
|
|
269
|
+
.map(({ className, propertyName }) => `
|
|
270
|
+
UNION ALL
|
|
271
|
+
SELECT element.ECInstanceId, element.${propertyName}.Id, 1
|
|
272
|
+
FROM bis.${className} element
|
|
273
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.${propertyName}.Id`)
|
|
274
|
+
.join("")}
|
|
275
|
+
UNION ALL
|
|
276
|
+
SELECT viewDefinition.ECInstanceId, viewDefinition.DisplayStyle.Id, 0
|
|
277
|
+
FROM bis.ViewDefinition viewDefinition
|
|
278
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.DisplayStyle.Id
|
|
279
|
+
UNION ALL
|
|
280
|
+
SELECT viewDefinition.ECInstanceId, viewDefinition.CategorySelector.Id, 0
|
|
281
|
+
FROM bis.ViewDefinition viewDefinition
|
|
282
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.CategorySelector.Id
|
|
283
|
+
UNION ALL
|
|
284
|
+
SELECT viewDefinition.ECInstanceId, viewDefinition.ModelSelector.Id, 0
|
|
285
|
+
FROM bis.SpatialViewDefinition viewDefinition
|
|
286
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.ModelSelector.Id
|
|
287
|
+
UNION ALL
|
|
288
|
+
SELECT subCategory.Parent.Id, subCategory.ECInstanceId, 0
|
|
289
|
+
FROM bis.SubCategory subCategory
|
|
290
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = subCategory.ECInstanceId
|
|
291
|
+
WHERE CAST(subCategory.ECInstanceId AS INTEGER) = CAST(subCategory.Parent.Id AS INTEGER) + 1
|
|
292
|
+
UNION ALL
|
|
293
|
+
SELECT subCategory.ECInstanceId, subCategory.ECInstanceId, 2
|
|
294
|
+
FROM bis.SubCategory subCategory
|
|
295
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = subCategory.ECInstanceId
|
|
296
|
+
WHERE CAST(subCategory.ECInstanceId AS INTEGER) <> CAST(subCategory.Parent.Id AS INTEGER) + 1
|
|
297
|
+
${["GeometryPart", "RenderMaterial", "Texture", "LineStyle"]
|
|
298
|
+
.map((className) => `
|
|
299
|
+
UNION ALL
|
|
300
|
+
SELECT element.ECInstanceId, element.ECInstanceId, 2
|
|
301
|
+
FROM bis.${className} element
|
|
302
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId`)
|
|
303
|
+
.join("")}
|
|
304
|
+
`;
|
|
305
|
+
for await (const row of targetDb.createQueryReader(definitionQuery, new core_common_1.QueryBinder().bindIdSet("elementIds", definitionIds), { usePrimaryConn: true })) {
|
|
306
|
+
if (row.kind === 2)
|
|
307
|
+
geometryDefinitionIds.push(row.referencedId);
|
|
308
|
+
else
|
|
309
|
+
references.push({
|
|
310
|
+
referencingId: row.referencingId,
|
|
311
|
+
referencedId: row.referencedId,
|
|
312
|
+
usersFirst: row.kind === 1,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return { references, geometryDefinitionIds };
|
|
316
|
+
}
|
|
317
|
+
/** Finds the BisCore section classes and view properties that exist in the iModel's schema. */
|
|
318
|
+
async function querySectionViewReferences(targetDb) {
|
|
319
|
+
const query = `
|
|
320
|
+
SELECT classDef.Name AS className, propertyDef.Name AS propertyName
|
|
321
|
+
FROM meta.ECPropertyDef propertyDef
|
|
322
|
+
INNER JOIN meta.ECClassDef classDef ON classDef.ECInstanceId = propertyDef.Class.Id
|
|
323
|
+
INNER JOIN meta.ECSchemaDef schemaDef ON schemaDef.ECInstanceId = classDef.Schema.Id
|
|
324
|
+
WHERE schemaDef.Name = 'BisCore'
|
|
325
|
+
AND ((classDef.Name = 'SectionDrawing' AND propertyDef.Name = 'SpatialView')
|
|
326
|
+
OR (classDef.Name = 'SectionDrawingLocation' AND propertyDef.Name = 'SectionView'))
|
|
327
|
+
`;
|
|
328
|
+
const references = [];
|
|
329
|
+
for await (const row of targetDb.createQueryReader(query, undefined, {
|
|
330
|
+
usePrimaryConn: true,
|
|
331
|
+
}))
|
|
332
|
+
references.push({
|
|
333
|
+
className: row.className,
|
|
334
|
+
propertyName: row.propertyName,
|
|
335
|
+
});
|
|
336
|
+
return references;
|
|
337
|
+
}
|
|
338
|
+
/** Finds the 2D and 3D geometric elements among the specified elements. */
|
|
339
|
+
async function queryGeometricElements(targetDb, elementIds) {
|
|
340
|
+
// Joining the abstract bis.GeometricElement with an IdSet is quadratic, so query its 3D and 2D subclasses.
|
|
341
|
+
const query = `
|
|
342
|
+
SELECT element.ECInstanceId AS id
|
|
343
|
+
FROM bis.GeometricElement3d element
|
|
344
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
345
|
+
UNION ALL
|
|
346
|
+
SELECT element.ECInstanceId
|
|
347
|
+
FROM bis.GeometricElement2d element
|
|
348
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
349
|
+
`;
|
|
350
|
+
const ids = [];
|
|
351
|
+
const params = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
352
|
+
for await (const row of targetDb.createQueryReader(query, params, {
|
|
353
|
+
usePrimaryConn: true,
|
|
354
|
+
}))
|
|
355
|
+
ids.push(row.id);
|
|
356
|
+
return ids;
|
|
54
357
|
}
|
|
55
358
|
//# sourceMappingURL=ElementBulkDelete.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElementBulkDelete.js","sourceRoot":"","sources":["../../src/ElementBulkDelete.ts"],"names":[],"mappings":";AAAA;;;gGAGgG;;;AAGhG,oDAAiD;AAGjD;;;;;;GAMG;AACI,KAAK,8BACV,QAAkB,EAClB,UAAmC;IAEnC,MAAM,WAAW,GAAY,IAAI,GAAG,EAAc,CAAC;IACnD,kGAAkG;IAClG,4FAA4F;IAC5F,+FAA+F;IAC/F,+FAA+F;IAC/F,mGAAmG;IACnG,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;GAuBb,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,yBAAW,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACrE,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE;QAChE,cAAc,EAAE,IAAI;KACrB,CAAC,EAAE,CAAC;QACH,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { Id64Set, Id64String } from \"@itwin/core-bentley\";\nimport { QueryBinder } from \"@itwin/core-common\";\nimport { IModelDb } from \"@itwin/core-backend\";\n\n/** Finds the target roots required for native bulk deletion.\n *\n * Native deletion already cascades through child elements and modeled contents. This query follows\n * those dependencies to find top-level elements whose codes are scoped by an element in a deleted\n * tree. Each code-dependent element becomes another native deletion root.\n * @internal\n */\nexport async function findBulkDeleteRoots(\n targetDb: IModelDb,\n elementIds: ReadonlySet<Id64String>\n): Promise<Id64Set> {\n const deleteRoots: Id64Set = new Set<Id64String>();\n // CascadeIds pairs each traversed element with a root that the native API must delete. The anchor\n // adds requested roots that still exist. Recursive branches walk child elements and modeled\n // contents while preserving DeleteRootId. A top-level element whose code is scoped by anything\n // already traversed becomes a new root, so recursion also covers its tree and code dependents.\n // DeleteRoots returns only the roots because native deletion performs the child and model cascade.\n const query = `\n WITH RECURSIVE CascadeIds(Id, DeleteRootId) AS (\n SELECT element.ECInstanceId, element.ECInstanceId\n FROM bis.Element element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId\n UNION\n SELECT child.ECInstanceId, deletionParent.DeleteRootId\n FROM bis.Element child\n INNER JOIN CascadeIds deletionParent ON child.Parent.Id = deletionParent.Id\n UNION\n SELECT modelElement.ECInstanceId, modeledElement.DeleteRootId\n FROM bis.Element modelElement\n INNER JOIN CascadeIds modeledElement ON modelElement.Model.Id = modeledElement.Id\n UNION\n SELECT codeDependent.ECInstanceId, codeDependent.ECInstanceId\n FROM bis.Element codeDependent\n INNER JOIN CascadeIds scope ON codeDependent.CodeScope.Id = scope.Id\n WHERE codeDependent.Parent.Id IS NULL\n ),\n DeleteRoots(Id) AS (\n SELECT DISTINCT DeleteRootId FROM CascadeIds\n )\n SELECT Id AS id FROM DeleteRoots\n `;\n const params = new QueryBinder().bindIdSet(\"elementIds\", elementIds);\n for await (const row of targetDb.createQueryReader(query, params, {\n usePrimaryConn: true,\n })) {\n deleteRoots.add(row.id);\n }\n return deleteRoots;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ElementBulkDelete.js","sourceRoot":"","sources":["../../src/ElementBulkDelete.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { assert, Id64Array, Id64Set, Id64String } from \"@itwin/core-bentley\";\nimport { QueryBinder } from \"@itwin/core-common\";\nimport { IModelDb } from \"@itwin/core-backend\";\n\n/** Native deletion calls needed to delete a set of element trees without a partial result.\n * @internal\n */\nexport interface BulkDeletePlan {\n /** Roots for each native deletion call, in execution order. Empty when nothing needs deleting. */\n readonly phases: readonly Id64Array[];\n /** Elements kept because an element outside the deleted trees still references them, each mapped to one such\n * referencing element. Their child elements and sub-models, and the elements whose deletion would delete them,\n * are kept too.\n */\n readonly keptReferences: ReadonlyMap<Id64String, Id64String>;\n}\n\ninterface DeletionTrees {\n /** Roots that the native API must receive. */\n readonly roots: Id64Set;\n /** Every element that deleting the roots removes, mapped to the roots whose trees contain it. */\n readonly rootsByElement: ReadonlyMap<Id64String, readonly Id64String[]>;\n}\n\n/** Plans native bulk deletion of the specified element trees.\n *\n * An element that something outside the deleted trees still references is kept, together with what it\n * needs to stay valid, and the rest is deleted. Keeping elements can leave new outside references, so the\n * plan repeats until none remain.\n *\n * Native validation checks each root against the iModel before the call, so a root is refused when its tree\n * contains a definition, such as a category or a view, that a non-definition element in the same call still\n * uses. The plan deletes those users in an earlier call than the definitions they use. Validation stays enabled\n * for every call, so a reference this plan does not know about fails loudly instead of leaving a dangling\n * reference. That includes elements outside the trees whose geometry uses a definition in them, such as a\n * geometry part: only native code can read geometry, so those uses are not checked in advance.\n * @internal\n */\nexport async function planBulkDelete(\n targetDb: IModelDb,\n elementIds: ReadonlySet<Id64String>\n): Promise<BulkDeletePlan> {\n const requestedIds = new Set(elementIds);\n const keptReferences = new Map<Id64String, Id64String>();\n for (;;) {\n const trees = await queryDeletionTrees(targetDb, requestedIds);\n if (trees.roots.size === 0) return { phases: [], keptReferences };\n\n const references = await queryReferences(\n targetDb,\n trees.rootsByElement.keys()\n );\n const blockedIds = new Set<Id64String>();\n for (const { referencingId, referencedId } of references.references) {\n if (trees.rootsByElement.has(referencingId)) continue;\n blockedIds.add(referencedId);\n if (!keptReferences.has(referencedId))\n keptReferences.set(referencedId, referencingId);\n }\n if (blockedIds.size === 0)\n return {\n phases: await planPhases(targetDb, trees, references),\n keptReferences,\n };\n\n // Every blocked element is reached from a requested element, and queryKeptElements walks back up to it,\n // so each pass removes at least one requested element and the loop ends.\n const requestedCount = requestedIds.size;\n for (const id of await queryKeptElements(targetDb, blockedIds))\n requestedIds.delete(id);\n assert(\n requestedIds.size < requestedCount,\n \"Keeping referenced elements must remove a requested element\"\n );\n }\n}\n\n/** Orders the roots of deletion trees that nothing outside them references into native calls. */\nasync function planPhases(\n targetDb: IModelDb,\n trees: DeletionTrees,\n { references, geometryDefinitionIds }: TreeReferences\n): Promise<Id64Array[]> {\n // Elements in the trees that use a definition that is also in the trees. Native validation refuses the definition while they exist.\n const usersOfDeletedDefinitions = new Set<Id64String>();\n // Roots whose trees contain such a definition. They must be deleted after its users.\n const rootsWithUsedDefinitions = new Set<Id64String>();\n const addUsedDefinition = (definitionId: Id64String) => {\n for (const rootId of trees.rootsByElement.get(definitionId) ?? [])\n rootsWithUsedDefinitions.add(rootId);\n };\n const sameCallReferences: TreeReference[] = [];\n for (const reference of references) {\n if (reference.usersFirst) {\n usersOfDeletedDefinitions.add(reference.referencingId);\n addUsedDefinition(reference.referencedId);\n } else {\n sameCallReferences.push(reference);\n }\n }\n // Only native code can tell which geometry uses a geometry part, material, texture, line style, or sub-category,\n // so every geometric element in the trees counts as a user of every such definition. Deleting the trees that\n // contain those definitions last is enough unless one of them also contains a geometric element; only then are\n // all geometric elements queried, to delete them as roots of their own.\n const geometryRoots = new Set<Id64String>();\n for (const id of geometryDefinitionIds)\n for (const rootId of trees.rootsByElement.get(id) ?? [])\n geometryRoots.add(rootId);\n if (geometryRoots.size > 0) {\n const elementsInGeometryTrees = [...trees.rootsByElement]\n .filter(([, rootIds]) => rootIds.some((id) => geometryRoots.has(id)))\n .map(([id]) => id);\n if (\n (await queryGeometricElements(targetDb, elementsInGeometryTrees)).length >\n 0\n )\n for (const id of await queryGeometricElements(\n targetDb,\n trees.rootsByElement.keys()\n ))\n usersOfDeletedDefinitions.add(id);\n for (const rootId of geometryRoots) rootsWithUsedDefinitions.add(rootId);\n }\n if (rootsWithUsedDefinitions.size === 0) return [[...trees.roots]];\n // True when a root deleted in the last call removes the element.\n const isInTreeWithUsedDefinition = (id: Id64String) =>\n (trees.rootsByElement.get(id) ?? []).some((rootId) =>\n rootsWithUsedDefinitions.has(rootId)\n );\n // True when a root deleted in the first call removes the element. An element in overlapping trees can satisfy both checks.\n const isInOtherTree = (id: Id64String) =>\n (trees.rootsByElement.get(id) ?? []).some(\n (rootId) => !rootsWithUsedDefinitions.has(rootId)\n );\n // Deleting the other roots first works unless a user shares a tree with a used definition, or an other tree\n // contains an element that an element deleted later references, such as its code scope.\n const canDeleteOtherRootsFirst =\n ![...usersOfDeletedDefinitions].some(isInTreeWithUsedDefinition) &&\n !sameCallReferences.some(\n ({ referencingId, referencedId }) =>\n isInTreeWithUsedDefinition(referencingId) && isInOtherTree(referencedId)\n );\n if (canDeleteOtherRootsFirst) {\n // There are no other roots only when every tree contains a geometry definition and nothing in the trees uses a\n // definition through a queried reference.\n const otherRoots = [...trees.roots].filter(\n (id) => !rootsWithUsedDefinitions.has(id)\n );\n return otherRoots.length > 0\n ? [otherRoots, [...rootsWithUsedDefinitions]]\n : [[...rootsWithUsedDefinitions]];\n }\n\n // Otherwise delete the users and their code dependents as roots of their own. Core refuses to delete an\n // element whose code scopes an element deleted in a later call, so the first call also takes code dependents\n // that have a parent. They are all in the trees; one outside them would have kept its code scope above.\n const userTrees = await queryDeletionTrees(\n targetDb,\n usersOfDeletedDefinitions,\n true\n );\n const remainingRoots = [...trees.roots].filter(\n (id) => !userTrees.rootsByElement.has(id)\n );\n const phases = [[...userTrees.roots]];\n if (remainingRoots.length > 0) phases.push(remainingRoots);\n return phases;\n}\n\n/** Finds the native deletion roots and every element their deletion removes.\n *\n * Native deletion already cascades through child elements and modeled contents. This query follows\n * those dependencies to find elements whose codes are scoped by an element in a deleted tree. Each\n * code-dependent element becomes another native deletion root. Only top-level code dependents are\n * included unless `includeParentedCodeDependents` is true.\n */\nasync function queryDeletionTrees(\n targetDb: IModelDb,\n elementIds: ReadonlySet<Id64String>,\n includeParentedCodeDependents = false\n): Promise<DeletionTrees> {\n const roots: Id64Set = new Set<Id64String>();\n // An element has more than one root only when requested trees overlap. UNION in the query already removes duplicate\n // (element, root) pairs, so an array holds distinct roots without allocating a Set per element.\n const rootsByElement = new Map<Id64String, Id64String[]>();\n // CascadeIds pairs each traversed element with a root that the native API must delete. The anchor\n // adds requested elements that still exist, except those whose parent or modeled element is also\n // requested: the recursion reaches them anyway, and anchoring them too would walk a nested tree once\n // per requested ancestor. Recursive branches walk child elements and modeled contents while\n // preserving DeleteRootId. An element whose code is scoped by anything already traversed becomes a\n // new root, so recursion also covers its tree and code dependents.\n const query = `\n WITH RECURSIVE CascadeIds(Id, DeleteRootId) AS (\n SELECT element.ECInstanceId, element.ECInstanceId\n FROM bis.Element element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId\n WHERE (element.Parent.Id IS NULL OR element.Parent.Id NOT IN (SELECT id FROM IdSet(:elementIds)))\n AND element.Model.Id NOT IN (SELECT id FROM IdSet(:elementIds))\n UNION\n SELECT child.ECInstanceId, deletionParent.DeleteRootId\n FROM bis.Element child\n INNER JOIN CascadeIds deletionParent ON child.Parent.Id = deletionParent.Id\n UNION\n SELECT modelElement.ECInstanceId, modeledElement.DeleteRootId\n FROM bis.Element modelElement\n INNER JOIN CascadeIds modeledElement ON modelElement.Model.Id = modeledElement.Id\n UNION\n SELECT codeDependent.ECInstanceId, codeDependent.ECInstanceId\n FROM bis.Element codeDependent\n INNER JOIN CascadeIds scope ON codeDependent.CodeScope.Id = scope.Id\n ${includeParentedCodeDependents ? \"\" : \"WHERE codeDependent.Parent.Id IS NULL\"}\n )\n SELECT Id AS id, DeleteRootId AS rootId FROM CascadeIds\n `;\n const params = new QueryBinder().bindIdSet(\"elementIds\", elementIds);\n for await (const row of targetDb.createQueryReader(query, params, {\n usePrimaryConn: true,\n })) {\n roots.add(row.rootId);\n const elementRoots = rootsByElement.get(row.id);\n if (elementRoots) elementRoots.push(row.rootId);\n else rootsByElement.set(row.id, [row.rootId]);\n }\n return { roots, rootsByElement };\n}\n\n/** Finds the elements to keep so that the blocked elements stay valid: each blocked element and its child\n * elements and sub-model contents, plus every element whose deletion would delete a kept element or leave its\n * parent, model, or code scope dangling.\n *\n * The whole subtree below a blocked element is kept, even when the element is blocked only as a code scope and\n * its children could be deleted. A kept category needs its sub-categories, because the plan can't see geometry\n * outside the trees that uses them, and keeping too much is safe.\n */\nasync function queryKeptElements(\n targetDb: IModelDb,\n blockedIds: ReadonlySet<Id64String>\n): Promise<Id64Set> {\n const query = `\n WITH RECURSIVE\n Above(Id) AS (\n SELECT ids.id FROM IdSet(:blockedIds) ids\n UNION\n SELECT element.Parent.Id FROM bis.Element element\n INNER JOIN Above ON element.ECInstanceId = Above.Id\n WHERE element.Parent.Id IS NOT NULL\n UNION\n SELECT element.Model.Id FROM bis.Element element\n INNER JOIN Above ON element.ECInstanceId = Above.Id\n UNION\n SELECT element.CodeScope.Id FROM bis.Element element\n INNER JOIN Above ON element.ECInstanceId = Above.Id\n ),\n Below(Id) AS (\n SELECT ids.id FROM IdSet(:blockedIds) ids\n UNION\n SELECT element.ECInstanceId FROM bis.Element element\n INNER JOIN Below ON element.Parent.Id = Below.Id\n UNION\n SELECT element.ECInstanceId FROM bis.Element element\n INNER JOIN Below ON element.Model.Id = Below.Id\n )\n SELECT Id AS id FROM Above\n UNION\n SELECT Id FROM Below\n `;\n const keptIds: Id64Set = new Set<Id64String>();\n const params = new QueryBinder().bindIdSet(\"blockedIds\", blockedIds);\n for await (const row of targetDb.createQueryReader(query, params, {\n usePrimaryConn: true,\n }))\n keptIds.add(row.id);\n return keptIds;\n}\n\ninterface TreeReference {\n readonly referencingId: Id64String;\n readonly referencedId: Id64String;\n /** True when native validation refuses the referenced element while the referencing element exists, even when\n * both are deleted in the same call. False when deleting them in the same call is enough.\n */\n readonly usersFirst: boolean;\n}\n\ninterface TreeReferences {\n readonly references: readonly TreeReference[];\n /** Geometry parts, materials, textures, line styles, and non-default sub-categories in the trees. Geometry\n * references them, so their users can't be queried.\n */\n readonly geometryDefinitionIds: readonly Id64String[];\n}\n\n/** Finds BisCore references to the specified elements that native deletion validates and that [[queryDeletionTrees]]\n * does not follow. Parent and model references are not included because the deleted trees contain every child and\n * modeled element.\n */\nasync function queryReferences(\n targetDb: IModelDb,\n elementIds: Iterable<Id64String>\n): Promise<TreeReferences> {\n const references: TreeReference[] = [];\n const geometryDefinitionIds: Id64String[] = [];\n // Any element can be a code scope. Every other validated reference targets a definition element, so those are\n // queried only for the definitions among the elements, which are usually few or none.\n const definitionIds: Id64String[] = [];\n const firstQuery = `\n SELECT element.ECInstanceId AS referencingId, element.CodeScope.Id AS referencedId, 0 AS isDefinition\n FROM bis.Element element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.CodeScope.Id\n UNION ALL\n SELECT definition.ECInstanceId, definition.ECInstanceId, 1\n FROM bis.DefinitionElement definition\n INNER JOIN IdSet(:elementIds) ids ON ids.id = definition.ECInstanceId\n `;\n for await (const row of targetDb.createQueryReader(\n firstQuery,\n new QueryBinder().bindIdSet(\"elementIds\", elementIds),\n { usePrimaryConn: true }\n )) {\n if (row.isDefinition === 1) definitionIds.push(row.referencedId);\n else\n references.push({\n referencingId: row.referencingId,\n referencedId: row.referencedId,\n usersFirst: false,\n });\n }\n if (definitionIds.length === 0) return { references, geometryDefinitionIds };\n\n // Classes and properties added in later BisCore versions.\n const sectionReferences = await querySectionViewReferences(targetDb);\n // Kind 1: native validation refuses the definition while the referencing element exists, even in the same call.\n // Kind 0: deleting both in the same call is enough. Native validation treats a default sub-category, whose ID\n // follows its category's, as used by the category unless both are deleted.\n // Kind 2: a definition that geometry references, so its users can't be queried.\n const definitionQuery = `\n SELECT element.ECInstanceId AS referencingId, element.Category.Id AS referencedId, 1 AS kind\n FROM bis.GeometricElement3d element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.Category.Id\n UNION ALL\n SELECT element.ECInstanceId, element.Category.Id, 1\n FROM bis.GeometricElement2d element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.Category.Id\n UNION ALL\n SELECT element.ECInstanceId, element.View.Id, 1\n FROM bis.ViewAttachment element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.View.Id\n ${sectionReferences\n .map(\n ({ className, propertyName }) => `\n UNION ALL\n SELECT element.ECInstanceId, element.${propertyName}.Id, 1\n FROM bis.${className} element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.${propertyName}.Id`\n )\n .join(\"\")}\n UNION ALL\n SELECT viewDefinition.ECInstanceId, viewDefinition.DisplayStyle.Id, 0\n FROM bis.ViewDefinition viewDefinition\n INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.DisplayStyle.Id\n UNION ALL\n SELECT viewDefinition.ECInstanceId, viewDefinition.CategorySelector.Id, 0\n FROM bis.ViewDefinition viewDefinition\n INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.CategorySelector.Id\n UNION ALL\n SELECT viewDefinition.ECInstanceId, viewDefinition.ModelSelector.Id, 0\n FROM bis.SpatialViewDefinition viewDefinition\n INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.ModelSelector.Id\n UNION ALL\n SELECT subCategory.Parent.Id, subCategory.ECInstanceId, 0\n FROM bis.SubCategory subCategory\n INNER JOIN IdSet(:elementIds) ids ON ids.id = subCategory.ECInstanceId\n WHERE CAST(subCategory.ECInstanceId AS INTEGER) = CAST(subCategory.Parent.Id AS INTEGER) + 1\n UNION ALL\n SELECT subCategory.ECInstanceId, subCategory.ECInstanceId, 2\n FROM bis.SubCategory subCategory\n INNER JOIN IdSet(:elementIds) ids ON ids.id = subCategory.ECInstanceId\n WHERE CAST(subCategory.ECInstanceId AS INTEGER) <> CAST(subCategory.Parent.Id AS INTEGER) + 1\n ${[\"GeometryPart\", \"RenderMaterial\", \"Texture\", \"LineStyle\"]\n .map(\n (className) => `\n UNION ALL\n SELECT element.ECInstanceId, element.ECInstanceId, 2\n FROM bis.${className} element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId`\n )\n .join(\"\")}\n `;\n for await (const row of targetDb.createQueryReader(\n definitionQuery,\n new QueryBinder().bindIdSet(\"elementIds\", definitionIds),\n { usePrimaryConn: true }\n )) {\n if (row.kind === 2) geometryDefinitionIds.push(row.referencedId);\n else\n references.push({\n referencingId: row.referencingId,\n referencedId: row.referencedId,\n usersFirst: row.kind === 1,\n });\n }\n return { references, geometryDefinitionIds };\n}\n\n/** Finds the BisCore section classes and view properties that exist in the iModel's schema. */\nasync function querySectionViewReferences(\n targetDb: IModelDb\n): Promise<{ className: string; propertyName: string }[]> {\n const query = `\n SELECT classDef.Name AS className, propertyDef.Name AS propertyName\n FROM meta.ECPropertyDef propertyDef\n INNER JOIN meta.ECClassDef classDef ON classDef.ECInstanceId = propertyDef.Class.Id\n INNER JOIN meta.ECSchemaDef schemaDef ON schemaDef.ECInstanceId = classDef.Schema.Id\n WHERE schemaDef.Name = 'BisCore'\n AND ((classDef.Name = 'SectionDrawing' AND propertyDef.Name = 'SpatialView')\n OR (classDef.Name = 'SectionDrawingLocation' AND propertyDef.Name = 'SectionView'))\n `;\n const references: { className: string; propertyName: string }[] = [];\n for await (const row of targetDb.createQueryReader(query, undefined, {\n usePrimaryConn: true,\n }))\n references.push({\n className: row.className,\n propertyName: row.propertyName,\n });\n return references;\n}\n\n/** Finds the 2D and 3D geometric elements among the specified elements. */\nasync function queryGeometricElements(\n targetDb: IModelDb,\n elementIds: Iterable<Id64String>\n): Promise<Id64String[]> {\n // Joining the abstract bis.GeometricElement with an IdSet is quadratic, so query its 3D and 2D subclasses.\n const query = `\n SELECT element.ECInstanceId AS id\n FROM bis.GeometricElement3d element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId\n UNION ALL\n SELECT element.ECInstanceId\n FROM bis.GeometricElement2d element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId\n `;\n const ids: Id64String[] = [];\n const params = new QueryBinder().bindIdSet(\"elementIds\", elementIds);\n for await (const row of targetDb.createQueryReader(query, params, {\n usePrimaryConn: true,\n }))\n ids.push(row.id);\n return ids;\n}\n"]}
|
|
@@ -166,13 +166,17 @@ export declare class IModelImporter {
|
|
|
166
166
|
protected onUpdateElement(elementProps: ElementProps): Promise<void>;
|
|
167
167
|
/** Deletes one target element tree through [[deleteElements]]. */
|
|
168
168
|
deleteElement(elementId: Id64String): Promise<void>;
|
|
169
|
-
/** Adds roots required by code-scope dependencies, then deletes the target element trees in
|
|
169
|
+
/** Adds roots required by code-scope dependencies, then deletes the target element trees in as few native operations as their references allow.
|
|
170
|
+
* Usually one native operation is enough. When a tree contains a definition, such as a category, that another deleted element uses, the elements that use it are deleted first.
|
|
171
|
+
* Elements that something outside the deleted trees still references are kept, with a warning, and the rest are deleted.
|
|
170
172
|
* @note An override must call `super.onDeleteElements` once to perform the deletion.
|
|
171
173
|
*/
|
|
172
174
|
protected onDeleteElements(elementIds: ReadonlySet<Id64String>): Promise<void>;
|
|
173
|
-
|
|
175
|
+
private deleteElementRoots;
|
|
176
|
+
/** Deletes target element trees, using one native operation unless references between the trees require more.
|
|
174
177
|
* Requested roots in [[doNotUpdateElementIds]] are skipped. This does not prevent an element in that set from being deleted as part of another root's cascade.
|
|
175
|
-
*
|
|
178
|
+
* Elements that something outside the deleted trees still references are kept, along with their child elements and sub-models and the elements that contain them. A warning lists them.
|
|
179
|
+
* @throws [[ElementBulkDeleteError]] if a native operation fails for any root, for example because geometry outside the deleted trees uses a geometry part in them, or because of a reference in a domain schema. The transformer does not check those references first. Deletions from that and earlier native operations stay pending in the caller-owned transaction. Abandon the transaction before retrying.
|
|
176
180
|
*/
|
|
177
181
|
deleteElements(elementIds: ReadonlySet<Id64String>): Promise<void>;
|
|
178
182
|
/** Delete the specified Model from the target iModel.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IModelImporter.d.ts","sourceRoot":"","sources":["../../src/IModelImporter.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,OAAO,EACL,QAAQ,EAGR,UAAU,EAEV,UAAU,EAEX,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAIL,kBAAkB,EAClB,YAAY,EACZ,WAAW,EAGX,UAAU,EAGX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,wBAAwB,EACxB,OAAO,EACP,aAAa,EACb,kBAAkB,EAClB,MAAM,EAEN,QAAQ,EAER,iBAAiB,EAGlB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAW9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"IModelImporter.d.ts","sourceRoot":"","sources":["../../src/IModelImporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,16 @@ const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
|
|
|
19
19
|
const IModelTransformerError_1 = require("./IModelTransformerError");
|
|
20
20
|
const ElementBulkDelete_1 = require("./ElementBulkDelete");
|
|
21
21
|
const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter;
|
|
22
|
+
/** Lists the first kept references for a log message. */
|
|
23
|
+
function formatKeptReferences(keptReferences) {
|
|
24
|
+
const maxListed = 10;
|
|
25
|
+
const listed = [...keptReferences]
|
|
26
|
+
.slice(0, maxListed)
|
|
27
|
+
.map(([elementId, referencingId]) => `${elementId} (referenced by ${referencingId})`)
|
|
28
|
+
.join(", ");
|
|
29
|
+
const unlisted = keptReferences.size - maxListed;
|
|
30
|
+
return unlisted > 0 ? `${listed}, and ${unlisted} more` : listed;
|
|
31
|
+
}
|
|
22
32
|
/** Base class for importing data into an iModel.
|
|
23
33
|
* @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md)
|
|
24
34
|
* @see [IModelExporter]($transformer)
|
|
@@ -339,14 +349,28 @@ class IModelImporter {
|
|
|
339
349
|
async deleteElement(elementId) {
|
|
340
350
|
await this.deleteElements(new Set([elementId]));
|
|
341
351
|
}
|
|
342
|
-
/** Adds roots required by code-scope dependencies, then deletes the target element trees in
|
|
352
|
+
/** Adds roots required by code-scope dependencies, then deletes the target element trees in as few native operations as their references allow.
|
|
353
|
+
* Usually one native operation is enough. When a tree contains a definition, such as a category, that another deleted element uses, the elements that use it are deleted first.
|
|
354
|
+
* Elements that something outside the deleted trees still references are kept, with a warning, and the rest are deleted.
|
|
343
355
|
* @note An override must call `super.onDeleteElements` once to perform the deletion.
|
|
344
356
|
*/
|
|
345
357
|
async onDeleteElements(elementIds) {
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
358
|
+
const plan = await (0, ElementBulkDelete_1.planBulkDelete)(this.targetDb, elementIds);
|
|
359
|
+
if (plan.keptReferences.size > 0) {
|
|
360
|
+
core_bentley_1.Logger.logWarning(loggerCategory, `Kept ${plan.keptReferences.size} elements that elements outside the deleted trees still reference, and the elements that contain them: ${formatKeptReferences(plan.keptReferences)}`);
|
|
361
|
+
}
|
|
362
|
+
if (plan.phases.length === 0)
|
|
348
363
|
return;
|
|
349
|
-
|
|
364
|
+
let rootCount = 0;
|
|
365
|
+
for (const roots of plan.phases) {
|
|
366
|
+
rootCount += roots.length;
|
|
367
|
+
this.deleteElementRoots(roots);
|
|
368
|
+
}
|
|
369
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Deleted ${elementIds.size} requested element trees using ${rootCount} native deletion roots in ${plan.phases.length} native operations`);
|
|
370
|
+
await this.trackProgress(elementIds.size);
|
|
371
|
+
}
|
|
372
|
+
deleteElementRoots(roots) {
|
|
373
|
+
const result = this._editTxn.deleteElements(roots);
|
|
350
374
|
if (result.status !== core_backend_1.BulkDeleteElementsStatus.Success) {
|
|
351
375
|
core_bentley_1.ITwinError.throwError({
|
|
352
376
|
iTwinErrorId: {
|
|
@@ -362,12 +386,11 @@ class IModelImporter {
|
|
|
362
386
|
failedIds: new Set(result.failedIds),
|
|
363
387
|
});
|
|
364
388
|
}
|
|
365
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `Deleted ${elementIds.size} requested element trees using ${deleteRoots.size} native deletion roots`);
|
|
366
|
-
await this.trackProgress(elementIds.size);
|
|
367
389
|
}
|
|
368
|
-
/** Deletes target element trees
|
|
390
|
+
/** Deletes target element trees, using one native operation unless references between the trees require more.
|
|
369
391
|
* Requested roots in [[doNotUpdateElementIds]] are skipped. This does not prevent an element in that set from being deleted as part of another root's cascade.
|
|
370
|
-
*
|
|
392
|
+
* Elements that something outside the deleted trees still references are kept, along with their child elements and sub-models and the elements that contain them. A warning lists them.
|
|
393
|
+
* @throws [[ElementBulkDeleteError]] if a native operation fails for any root, for example because geometry outside the deleted trees uses a geometry part in them, or because of a reference in a domain schema. The transformer does not check those references first. Deletions from that and earlier native operations stay pending in the caller-owned transaction. Abandon the transaction before retrying.
|
|
371
394
|
*/
|
|
372
395
|
async deleteElements(elementIds) {
|
|
373
396
|
const idsToDelete = new Set();
|