@itwin/core-backend 5.15.0-dev.2 → 5.15.0-dev.20
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/CHANGELOG.md +52 -1
- package/lib/cjs/BlobContainerService.d.ts +2 -0
- package/lib/cjs/BlobContainerService.d.ts.map +1 -1
- package/lib/cjs/BlobContainerService.js.map +1 -1
- package/lib/cjs/BriefcaseManager.d.ts +4 -3
- package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
- package/lib/cjs/BriefcaseManager.js +39 -9
- package/lib/cjs/BriefcaseManager.js.map +1 -1
- package/lib/cjs/ChangeUnifier.d.ts +82 -0
- package/lib/cjs/ChangeUnifier.d.ts.map +1 -0
- package/lib/cjs/ChangeUnifier.js +163 -0
- package/lib/cjs/ChangeUnifier.js.map +1 -0
- package/lib/cjs/ChangesetReader.d.ts +21 -2
- package/lib/cjs/ChangesetReader.d.ts.map +1 -1
- package/lib/cjs/ChangesetReader.js +37 -1
- package/lib/cjs/ChangesetReader.js.map +1 -1
- package/lib/cjs/ChangesetReaderTypes.d.ts +1 -1
- package/lib/cjs/ChangesetReaderTypes.js.map +1 -1
- package/lib/cjs/CloudSqlite.d.ts +15 -0
- package/lib/cjs/CloudSqlite.d.ts.map +1 -1
- package/lib/cjs/CloudSqlite.js.map +1 -1
- package/lib/cjs/ECDb.d.ts +1 -0
- package/lib/cjs/ECDb.d.ts.map +1 -1
- package/lib/cjs/ECDb.js +4 -1
- package/lib/cjs/ECDb.js.map +1 -1
- package/lib/cjs/ECSqlRowExecutor.d.ts +3 -3
- package/lib/cjs/ECSqlRowExecutor.d.ts.map +1 -1
- package/lib/cjs/ECSqlRowExecutor.js +2 -2
- package/lib/cjs/ECSqlRowExecutor.js.map +1 -1
- package/lib/cjs/ECSqlStatement.d.ts +5 -0
- package/lib/cjs/ECSqlStatement.d.ts.map +1 -1
- package/lib/cjs/ECSqlStatement.js +15 -2
- package/lib/cjs/ECSqlStatement.js.map +1 -1
- package/lib/cjs/ECSqlSyncReader.d.ts.map +1 -1
- package/lib/cjs/ECSqlSyncReader.js +2 -2
- package/lib/cjs/ECSqlSyncReader.js.map +1 -1
- package/lib/cjs/EditTxn.d.ts +61 -19
- package/lib/cjs/EditTxn.d.ts.map +1 -1
- package/lib/cjs/EditTxn.js +84 -29
- package/lib/cjs/EditTxn.js.map +1 -1
- package/lib/cjs/Element.d.ts +29 -2
- package/lib/cjs/Element.d.ts.map +1 -1
- package/lib/cjs/Element.js +38 -2
- package/lib/cjs/Element.js.map +1 -1
- package/lib/cjs/GeoCoordConfig.d.ts +1 -0
- package/lib/cjs/GeoCoordConfig.d.ts.map +1 -1
- package/lib/cjs/GeoCoordConfig.js +12 -8
- package/lib/cjs/GeoCoordConfig.js.map +1 -1
- package/lib/cjs/GeographicCRSServices.d.ts +50 -2
- package/lib/cjs/GeographicCRSServices.d.ts.map +1 -1
- package/lib/cjs/GeographicCRSServices.js +11 -1
- package/lib/cjs/GeographicCRSServices.js.map +1 -1
- package/lib/cjs/IModelDb.d.ts +22 -9
- package/lib/cjs/IModelDb.d.ts.map +1 -1
- package/lib/cjs/IModelDb.js +54 -12
- package/lib/cjs/IModelDb.js.map +1 -1
- package/lib/cjs/NativeHost.d.ts.map +1 -1
- package/lib/cjs/NativeHost.js +5 -1
- package/lib/cjs/NativeHost.js.map +1 -1
- package/lib/cjs/Relationship.d.ts +28 -0
- package/lib/cjs/Relationship.d.ts.map +1 -1
- package/lib/cjs/Relationship.js +78 -0
- package/lib/cjs/Relationship.js.map +1 -1
- package/lib/cjs/SchemaSync.d.ts.map +1 -1
- package/lib/cjs/SchemaSync.js +1 -0
- package/lib/cjs/SchemaSync.js.map +1 -1
- package/lib/cjs/TxnManager.d.ts +126 -89
- package/lib/cjs/TxnManager.d.ts.map +1 -1
- package/lib/cjs/TxnManager.js +114 -76
- package/lib/cjs/TxnManager.js.map +1 -1
- package/lib/cjs/ViewStateHydrator.d.ts.map +1 -1
- package/lib/cjs/ViewStateHydrator.js.map +1 -1
- package/lib/cjs/ViewStore.d.ts.map +1 -1
- package/lib/cjs/ViewStore.js +62 -19
- package/lib/cjs/ViewStore.js.map +1 -1
- package/lib/cjs/annotations/LeaderGeometry.d.ts.map +1 -1
- package/lib/cjs/annotations/LeaderGeometry.js +3 -0
- package/lib/cjs/annotations/LeaderGeometry.js.map +1 -1
- package/lib/cjs/core-backend.d.ts +1 -0
- package/lib/cjs/core-backend.d.ts.map +1 -1
- package/lib/cjs/core-backend.js +1 -0
- package/lib/cjs/core-backend.js.map +1 -1
- package/lib/cjs/internal/BriefcaseUtils.d.ts +19 -0
- package/lib/cjs/internal/BriefcaseUtils.d.ts.map +1 -0
- package/lib/cjs/internal/BriefcaseUtils.js +65 -0
- package/lib/cjs/internal/BriefcaseUtils.js.map +1 -0
- package/lib/cjs/internal/QueryMetadataCache.d.ts +44 -0
- package/lib/cjs/internal/QueryMetadataCache.d.ts.map +1 -0
- package/lib/cjs/internal/QueryMetadataCache.js +107 -0
- package/lib/cjs/internal/QueryMetadataCache.js.map +1 -0
- package/lib/cjs/internal/Symbols.d.ts +9 -0
- package/lib/cjs/internal/Symbols.d.ts.map +1 -1
- package/lib/cjs/internal/Symbols.js +10 -1
- package/lib/cjs/internal/Symbols.js.map +1 -1
- package/lib/cjs/internal/workspace/WorkspaceImpl.d.ts.map +1 -1
- package/lib/cjs/internal/workspace/WorkspaceImpl.js +20 -6
- package/lib/cjs/internal/workspace/WorkspaceImpl.js.map +1 -1
- package/lib/cjs/rpc-impl/IModelReadRpcImpl.d.ts +6 -6
- package/lib/cjs/rpc-impl/IModelReadRpcImpl.d.ts.map +1 -1
- package/lib/cjs/rpc-impl/IModelReadRpcImpl.js.map +1 -1
- package/lib/cjs/workspace/SettingsEditor.d.ts +4 -3
- package/lib/cjs/workspace/SettingsEditor.d.ts.map +1 -1
- package/lib/cjs/workspace/SettingsEditor.js +7 -5
- package/lib/cjs/workspace/SettingsEditor.js.map +1 -1
- package/lib/esm/BlobContainerService.d.ts +2 -0
- package/lib/esm/BlobContainerService.d.ts.map +1 -1
- package/lib/esm/BlobContainerService.js.map +1 -1
- package/lib/esm/BriefcaseManager.d.ts +4 -3
- package/lib/esm/BriefcaseManager.d.ts.map +1 -1
- package/lib/esm/BriefcaseManager.js +40 -10
- package/lib/esm/BriefcaseManager.js.map +1 -1
- package/lib/esm/ChangeUnifier.d.ts +82 -0
- package/lib/esm/ChangeUnifier.d.ts.map +1 -0
- package/lib/esm/ChangeUnifier.js +159 -0
- package/lib/esm/ChangeUnifier.js.map +1 -0
- package/lib/esm/ChangesetReader.d.ts +21 -2
- package/lib/esm/ChangesetReader.d.ts.map +1 -1
- package/lib/esm/ChangesetReader.js +38 -2
- package/lib/esm/ChangesetReader.js.map +1 -1
- package/lib/esm/ChangesetReaderTypes.d.ts +1 -1
- package/lib/esm/ChangesetReaderTypes.js.map +1 -1
- package/lib/esm/CloudSqlite.d.ts +15 -0
- package/lib/esm/CloudSqlite.d.ts.map +1 -1
- package/lib/esm/CloudSqlite.js.map +1 -1
- package/lib/esm/ECDb.d.ts +1 -0
- package/lib/esm/ECDb.d.ts.map +1 -1
- package/lib/esm/ECDb.js +4 -1
- package/lib/esm/ECDb.js.map +1 -1
- package/lib/esm/ECSqlRowExecutor.d.ts +3 -3
- package/lib/esm/ECSqlRowExecutor.d.ts.map +1 -1
- package/lib/esm/ECSqlRowExecutor.js +2 -2
- package/lib/esm/ECSqlRowExecutor.js.map +1 -1
- package/lib/esm/ECSqlStatement.d.ts +5 -0
- package/lib/esm/ECSqlStatement.d.ts.map +1 -1
- package/lib/esm/ECSqlStatement.js +15 -2
- package/lib/esm/ECSqlStatement.js.map +1 -1
- package/lib/esm/ECSqlSyncReader.d.ts.map +1 -1
- package/lib/esm/ECSqlSyncReader.js +3 -3
- package/lib/esm/ECSqlSyncReader.js.map +1 -1
- package/lib/esm/EditTxn.d.ts +61 -19
- package/lib/esm/EditTxn.d.ts.map +1 -1
- package/lib/esm/EditTxn.js +85 -30
- package/lib/esm/EditTxn.js.map +1 -1
- package/lib/esm/Element.d.ts +29 -2
- package/lib/esm/Element.d.ts.map +1 -1
- package/lib/esm/Element.js +41 -5
- package/lib/esm/Element.js.map +1 -1
- package/lib/esm/GeoCoordConfig.d.ts +1 -0
- package/lib/esm/GeoCoordConfig.d.ts.map +1 -1
- package/lib/esm/GeoCoordConfig.js +12 -8
- package/lib/esm/GeoCoordConfig.js.map +1 -1
- package/lib/esm/GeographicCRSServices.d.ts +50 -2
- package/lib/esm/GeographicCRSServices.d.ts.map +1 -1
- package/lib/esm/GeographicCRSServices.js +10 -1
- package/lib/esm/GeographicCRSServices.js.map +1 -1
- package/lib/esm/IModelDb.d.ts +22 -9
- package/lib/esm/IModelDb.d.ts.map +1 -1
- package/lib/esm/IModelDb.js +56 -14
- package/lib/esm/IModelDb.js.map +1 -1
- package/lib/esm/NativeHost.d.ts.map +1 -1
- package/lib/esm/NativeHost.js +5 -1
- package/lib/esm/NativeHost.js.map +1 -1
- package/lib/esm/Relationship.d.ts +28 -0
- package/lib/esm/Relationship.d.ts.map +1 -1
- package/lib/esm/Relationship.js +78 -0
- package/lib/esm/Relationship.js.map +1 -1
- package/lib/esm/SchemaSync.d.ts.map +1 -1
- package/lib/esm/SchemaSync.js +1 -0
- package/lib/esm/SchemaSync.js.map +1 -1
- package/lib/esm/TxnManager.d.ts +126 -89
- package/lib/esm/TxnManager.d.ts.map +1 -1
- package/lib/esm/TxnManager.js +116 -78
- package/lib/esm/TxnManager.js.map +1 -1
- package/lib/esm/ViewStateHydrator.d.ts.map +1 -1
- package/lib/esm/ViewStateHydrator.js.map +1 -1
- package/lib/esm/ViewStore.d.ts.map +1 -1
- package/lib/esm/ViewStore.js +62 -19
- package/lib/esm/ViewStore.js.map +1 -1
- package/lib/esm/annotations/LeaderGeometry.d.ts.map +1 -1
- package/lib/esm/annotations/LeaderGeometry.js +3 -0
- package/lib/esm/annotations/LeaderGeometry.js.map +1 -1
- package/lib/esm/core-backend.d.ts +1 -0
- package/lib/esm/core-backend.d.ts.map +1 -1
- package/lib/esm/core-backend.js +1 -0
- package/lib/esm/core-backend.js.map +1 -1
- package/lib/esm/internal/BriefcaseUtils.d.ts +19 -0
- package/lib/esm/internal/BriefcaseUtils.d.ts.map +1 -0
- package/lib/esm/internal/BriefcaseUtils.js +61 -0
- package/lib/esm/internal/BriefcaseUtils.js.map +1 -0
- package/lib/esm/internal/QueryMetadataCache.d.ts +44 -0
- package/lib/esm/internal/QueryMetadataCache.d.ts.map +1 -0
- package/lib/esm/internal/QueryMetadataCache.js +103 -0
- package/lib/esm/internal/QueryMetadataCache.js.map +1 -0
- package/lib/esm/internal/Symbols.d.ts +9 -0
- package/lib/esm/internal/Symbols.d.ts.map +1 -1
- package/lib/esm/internal/Symbols.js +9 -0
- package/lib/esm/internal/Symbols.js.map +1 -1
- package/lib/esm/internal/workspace/WorkspaceImpl.d.ts.map +1 -1
- package/lib/esm/internal/workspace/WorkspaceImpl.js +20 -6
- package/lib/esm/internal/workspace/WorkspaceImpl.js.map +1 -1
- package/lib/esm/rpc-impl/IModelReadRpcImpl.d.ts +6 -6
- package/lib/esm/rpc-impl/IModelReadRpcImpl.d.ts.map +1 -1
- package/lib/esm/rpc-impl/IModelReadRpcImpl.js.map +1 -1
- package/lib/esm/test/annotations/LeaderGeometry.test.js +15 -1
- package/lib/esm/test/annotations/LeaderGeometry.test.js.map +1 -1
- package/lib/esm/test/ecdb/ConcurrentQuery.test.js +144 -30
- package/lib/esm/test/ecdb/ConcurrentQuery.test.js.map +1 -1
- package/lib/esm/test/ecdb/ECSqlSyncReader.test.js +140 -0
- package/lib/esm/test/ecdb/ECSqlSyncReader.test.js.map +1 -1
- package/lib/esm/test/ecdb/QueryMetadataCache.test.d.ts +2 -0
- package/lib/esm/test/ecdb/QueryMetadataCache.test.d.ts.map +1 -0
- package/lib/esm/test/ecdb/QueryMetadataCache.test.js +630 -0
- package/lib/esm/test/ecdb/QueryMetadataCache.test.js.map +1 -0
- package/lib/esm/test/element/DeleteDefinitionElements.test.js +403 -249
- package/lib/esm/test/element/DeleteDefinitionElements.test.js.map +1 -1
- package/lib/esm/test/element/ElementSubModelInsert.test.d.ts +2 -0
- package/lib/esm/test/element/ElementSubModelInsert.test.d.ts.map +1 -0
- package/lib/esm/test/element/ElementSubModelInsert.test.js +183 -0
- package/lib/esm/test/element/ElementSubModelInsert.test.js.map +1 -0
- package/lib/esm/test/element/RelationshipBatchLookup.test.d.ts +2 -0
- package/lib/esm/test/element/RelationshipBatchLookup.test.d.ts.map +1 -0
- package/lib/esm/test/element/RelationshipBatchLookup.test.js +202 -0
- package/lib/esm/test/element/RelationshipBatchLookup.test.js.map +1 -0
- package/lib/esm/test/hubaccess/BriefcaseManager.test.js +187 -1
- package/lib/esm/test/hubaccess/BriefcaseManager.test.js.map +1 -1
- package/lib/esm/test/hubaccess/SchemaImportOrphanColumns.test.d.ts +2 -0
- package/lib/esm/test/hubaccess/SchemaImportOrphanColumns.test.d.ts.map +1 -0
- package/lib/esm/test/hubaccess/SchemaImportOrphanColumns.test.js +142 -0
- package/lib/esm/test/hubaccess/SchemaImportOrphanColumns.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.js +13 -11
- package/lib/esm/test/imodel/IModelGeolocation.test.js.map +1 -1
- package/lib/esm/test/imodel/IModelProps.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelProps.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelProps.test.js +411 -0
- package/lib/esm/test/imodel/IModelProps.test.js.map +1 -0
- package/lib/esm/test/schema/SchemaViewPropertyOverrides.test.d.ts +2 -0
- package/lib/esm/test/schema/SchemaViewPropertyOverrides.test.d.ts.map +1 -0
- package/lib/esm/test/schema/SchemaViewPropertyOverrides.test.js +240 -0
- package/lib/esm/test/schema/SchemaViewPropertyOverrides.test.js.map +1 -0
- package/lib/esm/test/schema/SchemaViewValidation.test.js +2 -8
- package/lib/esm/test/schema/SchemaViewValidation.test.js.map +1 -1
- package/lib/esm/test/standalone/BriefcaseManagerDelete.test.d.ts +2 -0
- package/lib/esm/test/standalone/BriefcaseManagerDelete.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/BriefcaseManagerDelete.test.js +159 -0
- package/lib/esm/test/standalone/BriefcaseManagerDelete.test.js.map +1 -0
- package/lib/esm/test/standalone/ChangeUnifier.test.d.ts +2 -0
- package/lib/esm/test/standalone/ChangeUnifier.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/ChangeUnifier.test.js +732 -0
- package/lib/esm/test/standalone/ChangeUnifier.test.js.map +1 -0
- package/lib/esm/test/standalone/DeleteElements.test.js +962 -83
- package/lib/esm/test/standalone/DeleteElements.test.js.map +1 -1
- package/lib/esm/test/standalone/EditTxn.test.js +72 -1
- package/lib/esm/test/standalone/EditTxn.test.js.map +1 -1
- package/lib/esm/test/standalone/GeoCoordConfig.test.d.ts +2 -0
- package/lib/esm/test/standalone/GeoCoordConfig.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/GeoCoordConfig.test.js +158 -0
- package/lib/esm/test/standalone/GeoCoordConfig.test.js.map +1 -0
- package/lib/esm/test/standalone/SessionApplyIndirect.test.d.ts +2 -0
- package/lib/esm/test/standalone/SessionApplyIndirect.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/SessionApplyIndirect.test.js +211 -0
- package/lib/esm/test/standalone/SessionApplyIndirect.test.js.map +1 -0
- package/lib/esm/test/standalone/ViewStoreDb.test.js +53 -0
- package/lib/esm/test/standalone/ViewStoreDb.test.js.map +1 -1
- package/lib/esm/test/workspace/SettingsContainers.test.d.ts +2 -0
- package/lib/esm/test/workspace/SettingsContainers.test.d.ts.map +1 -0
- package/lib/esm/test/workspace/SettingsContainers.test.js +95 -0
- package/lib/esm/test/workspace/SettingsContainers.test.js.map +1 -0
- package/lib/esm/workspace/SettingsEditor.d.ts +4 -3
- package/lib/esm/workspace/SettingsEditor.d.ts.map +1 -1
- package/lib/esm/workspace/SettingsEditor.js +7 -5
- package/lib/esm/workspace/SettingsEditor.js.map +1 -1
- package/package.json +18 -18
|
@@ -4,14 +4,38 @@
|
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import { assert } from "chai";
|
|
6
6
|
import * as path from "path";
|
|
7
|
-
import { Id64 } from "@itwin/core-bentley";
|
|
8
|
-
import { Point3d } from "@itwin/core-geometry";
|
|
7
|
+
import { DbResult, Id64 } from "@itwin/core-bentley";
|
|
8
|
+
import { Point3d, Range3d } from "@itwin/core-geometry";
|
|
9
9
|
import { Code, CodeScopeSpec, ImageSourceFormat, IModel, QueryBinder, QueryRowFormat, SubCategoryAppearance } from "@itwin/core-common";
|
|
10
|
-
import { EditTxn } from "../../EditTxn";
|
|
11
|
-
import { CategorySelector, ChannelControl, DefinitionContainer, DefinitionModel, DisplayStyle2d, DisplayStyle3d, DocumentListModel, Drawing, DrawingCategory, DrawingGraphic, DrawingViewDefinition, GenericGraphicalType2d, GenericPhysicalType, GeometryPart, GraphicalElement2dIsOfType, IModelJsFs, InformationPartitionElement, ModelSelector, OrthographicViewDefinition, PhysicalElementIsOfType, PhysicalModel, PhysicalPartition, RenderMaterialElement, SnapshotDb, SpatialCategory, SubCategory, Subject, Texture, } from "../../core-backend";
|
|
10
|
+
import { BulkDeleteElementsStatus, BulkDeleteFailureReason, EditTxn } from "../../EditTxn";
|
|
11
|
+
import { _nativeDb, CategorySelector, ChannelControl, DefinitionContainer, DefinitionModel, DisplayStyle2d, DisplayStyle3d, DocumentListModel, Drawing, DrawingCategory, DrawingGraphic, DrawingViewDefinition, GenericGraphicalType2d, GenericPhysicalType, GeometryPart, GraphicalElement2dIsOfType, IModelJsFs, InformationPartitionElement, ModelSelector, OrthographicViewDefinition, PhysicalElementIsOfType, PhysicalModel, PhysicalPartition, RenderMaterialElement, SnapshotDb, SpatialCategory, SubCategory, Subject, Texture, } from "../../core-backend";
|
|
12
12
|
import { ExtensiveTestScenario, IModelTestUtils } from "../IModelTestUtils";
|
|
13
13
|
import { KnownTestLocations } from "../KnownTestLocations";
|
|
14
14
|
import { withEditTxn } from "../TestEditTxn";
|
|
15
|
+
const success = { status: BulkDeleteElementsStatus.Success, sqlDeleteStatus: DbResult.BE_SQLITE_OK, failures: [] };
|
|
16
|
+
/** Nothing is deleted; `failures` lists the direct violators (one entry per element, `ReferencedByForeignKey` wins over `DefinitionInUse`). */
|
|
17
|
+
const constraintViolation = (...failures) => ({ status: BulkDeleteElementsStatus.ConstraintViolation, failures });
|
|
18
|
+
/** The element is a DefinitionElement still used (e.g. via a GeometryStream) by elements outside the delete set. */
|
|
19
|
+
const definitionInUse = (elementId) => ({ elementId, reason: BulkDeleteFailureReason.DefinitionInUse });
|
|
20
|
+
/** The element itself is referenced from outside the delete set via its Code scope. */
|
|
21
|
+
const codeScopeViolator = (elementId) => referencedByForeignKey(elementId);
|
|
22
|
+
/** A row outside the delete set references the element via a hard NO ACTION foreign key. */
|
|
23
|
+
const referencedByForeignKey = (elementId) => ({ elementId, reason: BulkDeleteFailureReason.ReferencedByForeignKey });
|
|
24
|
+
/** A Category still assigned to a GeometricElement3d outside the delete set (hard FK, so not `DefinitionInUse`). */
|
|
25
|
+
const categoryInUse3d = (elementId) => referencedByForeignKey(elementId);
|
|
26
|
+
/** A Category still assigned to a GeometricElement2d outside the delete set (hard FK, so not `DefinitionInUse`). */
|
|
27
|
+
const categoryInUse2d = (elementId) => referencedByForeignKey(elementId);
|
|
28
|
+
/** Assert the result matches `expected` exactly, then check that each id in `deletedIds` is gone and each id in `retainedIds` is still present. */
|
|
29
|
+
const assertResult = (result, expected, deletedIds, retainedIds, iModelDb) => {
|
|
30
|
+
assert.equal(result.status, expected.status, `Bulk element deletion status is ${BulkDeleteElementsStatus[result.status]}; Expected: ${BulkDeleteElementsStatus[expected.status]}. Actual failures: ${JSON.stringify(result.failures)}`);
|
|
31
|
+
assert.equal(result.sqlDeleteStatus, expected.sqlDeleteStatus ?? DbResult.BE_SQLITE_OK, `Bulk element deletion internal sql status is ${result.sqlDeleteStatus}; Expected: ${expected.sqlDeleteStatus ?? DbResult.BE_SQLITE_OK}`);
|
|
32
|
+
assert.equal(result.failures.length, expected.failures.length, `Actual failures don't match the expected. Actual: ${JSON.stringify(result.failures)}; Expected: ${JSON.stringify(expected.failures)}`);
|
|
33
|
+
assert.deepEqualInAnyOrder(result.failures.map(({ elementId, reason }) => ({ elementId, reason })), expected.failures);
|
|
34
|
+
for (const retainedId of retainedIds)
|
|
35
|
+
assert.isDefined(iModelDb.elements.tryGetElement(retainedId), `${retainedId} should have been retained`);
|
|
36
|
+
for (const deletedId of deletedIds)
|
|
37
|
+
assert.isUndefined(iModelDb.elements.tryGetElement(deletedId), `${deletedId} should have been deleted`);
|
|
38
|
+
};
|
|
15
39
|
describe("DeleteDefinitionElements", () => {
|
|
16
40
|
const outputDir = path.join(KnownTestLocations.outputDir, "DeleteDefinitionElements");
|
|
17
41
|
let seedDb;
|
|
@@ -21,6 +45,7 @@ describe("DeleteDefinitionElements", () => {
|
|
|
21
45
|
let physicalModelId;
|
|
22
46
|
let spatialCategoryId;
|
|
23
47
|
let subCategoryId;
|
|
48
|
+
let filteredSubCategoryId;
|
|
24
49
|
let spatialCategorySelectorId;
|
|
25
50
|
let modelSelectorId;
|
|
26
51
|
let displayStyle3dId;
|
|
@@ -55,6 +80,7 @@ describe("DeleteDefinitionElements", () => {
|
|
|
55
80
|
physicalModelId = seedDb.elements.queryElementIdByCode(PhysicalPartition.createCode(seedDb, subjectId, "Physical"));
|
|
56
81
|
spatialCategoryId = seedDb.elements.queryElementIdByCode(SpatialCategory.createCode(seedDb, definitionModelId, "SpatialCategory"));
|
|
57
82
|
subCategoryId = seedDb.elements.queryElementIdByCode(SubCategory.createCode(seedDb, spatialCategoryId, "SubCategory"));
|
|
83
|
+
filteredSubCategoryId = seedDb.elements.queryElementIdByCode(SubCategory.createCode(seedDb, spatialCategoryId, "FilteredSubCategory"));
|
|
58
84
|
spatialCategorySelectorId = seedDb.elements.queryElementIdByCode(CategorySelector.createCode(seedDb, definitionModelId, "SpatialCategories"));
|
|
59
85
|
modelSelectorId = seedDb.elements.queryElementIdByCode(ModelSelector.createCode(seedDb, definitionModelId, "SpatialModels"));
|
|
60
86
|
displayStyle3dId = seedDb.elements.queryElementIdByCode(DisplayStyle3d.createCode(seedDb, definitionModelId, "DisplayStyle3d"));
|
|
@@ -282,183 +308,168 @@ describe("DeleteDefinitionElements", () => {
|
|
|
282
308
|
return txn.insertElement(props);
|
|
283
309
|
};
|
|
284
310
|
/**
|
|
285
|
-
*
|
|
311
|
+
* Failures reported when the used `spatialCategoryId` is requested on its own. Validation runs once over the expanded set
|
|
312
|
+
* (no pruning between the FK pass and the definition-usage pass), so the used SpatialCategory is reported via its hard FK and
|
|
313
|
+
* its used non-default SubCategories (pulled into the set as children) are reported as `DefinitionInUse` as well.
|
|
314
|
+
*/
|
|
315
|
+
const usedSpatialCategoryViolations = () => [
|
|
316
|
+
categoryInUse3d(spatialCategoryId),
|
|
317
|
+
definitionInUse(subCategoryId),
|
|
318
|
+
definitionInUse(filteredSubCategoryId),
|
|
319
|
+
];
|
|
320
|
+
/**
|
|
321
|
+
* Call deleteElements, compare the returned result object against `expected`, then check which elements survived vs. were deleted.
|
|
286
322
|
* Rolls back changes with abandonChanges afterwards.
|
|
287
323
|
*/
|
|
288
|
-
const executeTestCase = (txn,
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
for (const id of expectedDeleted)
|
|
292
|
-
assert.isUndefined(iModelDb.elements.tryGetElement(id), `error reading element`);
|
|
293
|
-
for (const id of expectedRetained)
|
|
294
|
-
assert.isDefined(iModelDb.elements.tryGetElement(id), `[${label}] ${id} should have been retained`);
|
|
324
|
+
const executeTestCase = (txn, _label, idsToDelete, deletedIds, expected, retainedIds, abandonChanges = true) => {
|
|
325
|
+
const result = txn.deleteElements(idsToDelete);
|
|
326
|
+
assertResult(result, expected, deletedIds, retainedIds, iModelDb);
|
|
295
327
|
if (abandonChanges)
|
|
296
328
|
txn.abandonChanges();
|
|
297
329
|
};
|
|
298
330
|
it("should delete only if not used with deleteElements", async () => {
|
|
299
|
-
let usedDefinitionElementIds;
|
|
300
331
|
const txn = new EditTxn(iModelDb, "delete definition elements");
|
|
301
332
|
txn.start();
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
txn.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
txn.abandonChanges();
|
|
443
|
-
// deleteElement/deleteElements of DefinitionElements used by an existing DrawingViewDefinition should fail
|
|
444
|
-
assert.throws(() => txn.deleteElement(drawingCategorySelectorId));
|
|
445
|
-
assert.throws(() => txn.deleteElement(displayStyle2dId));
|
|
446
|
-
usedDefinitionElementIds = txn.deleteElements([drawingCategorySelectorId, displayStyle2dId]).failedIds;
|
|
447
|
-
assert.isTrue(usedDefinitionElementIds.has(drawingCategorySelectorId));
|
|
448
|
-
assert.isTrue(usedDefinitionElementIds.has(displayStyle2dId));
|
|
449
|
-
assert.isDefined(iModelDb.elements.tryGetElement(drawingCategorySelectorId));
|
|
450
|
-
assert.isDefined(iModelDb.elements.tryGetElement(displayStyle2dId));
|
|
451
|
-
assert.isDefined(iModelDb.elements.tryGetElement(drawingViewId));
|
|
452
|
-
txn.abandonChanges();
|
|
453
|
-
// deleteElements should succeed when the list includes the DrawingViewDefinition as the only thing referencing other DefinitionElements, delete should still fail
|
|
454
|
-
assert.throws(() => txn.deleteElement(drawingViewId));
|
|
455
|
-
usedDefinitionElementIds = txn.deleteElements([drawingViewId, drawingCategorySelectorId, displayStyle2dId]).failedIds;
|
|
456
|
-
assert.equal(usedDefinitionElementIds.size, 0);
|
|
457
|
-
assert.isUndefined(iModelDb.elements.tryGetElement(drawingCategorySelectorId));
|
|
458
|
-
assert.isUndefined(iModelDb.elements.tryGetElement(displayStyle2dId));
|
|
459
|
-
assert.isUndefined(iModelDb.elements.tryGetElement(drawingViewId));
|
|
460
|
-
txn.end("abandon");
|
|
461
|
-
iModelDb.close();
|
|
333
|
+
/** Run deleteElements and compare the returned result object against `expected`, then check deleted / retained ids. */
|
|
334
|
+
const expectDelete = (idsToDelete, expected, deletedIds, retainedIds) => assertResult(txn.deleteElements(idsToDelete), expected, deletedIds, retainedIds, iModelDb);
|
|
335
|
+
try {
|
|
336
|
+
// Deleting the Subject expands the delete set to everything beneath it (partitions, sub-models and their contents).
|
|
337
|
+
// Every element in this iModel lives under that Subject, so all usages of the categories / GeometryPart / RenderMaterial
|
|
338
|
+
// in the Definition model are *intra-set* and must not block the deletion: everything is deleted.
|
|
339
|
+
expectDelete([physicalObjectId1, physicalObjectId2, physicalObjectId3, subjectId], success, [physicalObjectId1, physicalObjectId2, physicalObjectId3, subjectId, definitionModelId, physicalModelId, spatialCategoryId, geometryPartId, renderMaterialId], []);
|
|
340
|
+
// Reset the db state
|
|
341
|
+
txn.abandonChanges();
|
|
342
|
+
// make sure deleteElements throws for invalid Ids
|
|
343
|
+
assert.throws(() => txn.deleteElements([Id64.invalid, Id64.invalid]));
|
|
344
|
+
// deleteElement/deleteElements for a used GeometryPart should fail
|
|
345
|
+
assert.throws(() => txn.deleteElement(geometryPartId));
|
|
346
|
+
expectDelete([geometryPartId], constraintViolation(definitionInUse(geometryPartId)), [], [geometryPartId]);
|
|
347
|
+
// deleteElements for an unused GeometryPart should succeed, delete should still fail
|
|
348
|
+
const unusedGeometryPartProps = {
|
|
349
|
+
classFullName: GeometryPart.classFullName,
|
|
350
|
+
model: definitionModelId,
|
|
351
|
+
code: GeometryPart.createCode(iModelDb, definitionModelId, "Unused GeometryPart"),
|
|
352
|
+
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1)),
|
|
353
|
+
};
|
|
354
|
+
// Reset the db state
|
|
355
|
+
txn.abandonChanges();
|
|
356
|
+
const unusedGeometryPartId = txn.insertElement(unusedGeometryPartProps);
|
|
357
|
+
assert.isTrue(Id64.isValidId64(unusedGeometryPartId));
|
|
358
|
+
assert.throws(() => txn.deleteElement(unusedGeometryPartId));
|
|
359
|
+
expectDelete([unusedGeometryPartId], success, [unusedGeometryPartId], []);
|
|
360
|
+
txn.abandonChanges();
|
|
361
|
+
// deleteElement/deleteElements for a used RenderMaterial should fail
|
|
362
|
+
assert.throws(() => txn.deleteElement(renderMaterialId));
|
|
363
|
+
expectDelete([renderMaterialId], constraintViolation(definitionInUse(renderMaterialId)), [], [renderMaterialId]);
|
|
364
|
+
txn.abandonChanges();
|
|
365
|
+
// deleteElements for an unused RenderMaterial should succeed, delete should still fail
|
|
366
|
+
const unusedRenderMaterialId = RenderMaterialElement.insert(txn, definitionModelId, "Unused RenderMaterial", { paletteName: "PaletteName" });
|
|
367
|
+
assert.isTrue(Id64.isValidId64(unusedRenderMaterialId));
|
|
368
|
+
assert.throws(() => txn.deleteElement(unusedRenderMaterialId));
|
|
369
|
+
expectDelete([unusedRenderMaterialId], success, [unusedRenderMaterialId], []);
|
|
370
|
+
txn.abandonChanges();
|
|
371
|
+
// deleteElement/deleteElements for a used Texture should fail
|
|
372
|
+
assert.throws(() => txn.deleteElement(textureId));
|
|
373
|
+
expectDelete([textureId], constraintViolation(definitionInUse(textureId)), [], [textureId]);
|
|
374
|
+
txn.abandonChanges();
|
|
375
|
+
// deleteElements for an unused Texture should succeed, delete should still fail
|
|
376
|
+
const unusedTextureId = IModelTestUtils.insertTextureElement(txn, definitionModelId, "Unused Texture");
|
|
377
|
+
assert.isTrue(Id64.isValidId64(unusedTextureId));
|
|
378
|
+
assert.throws(() => txn.deleteElement(unusedTextureId));
|
|
379
|
+
expectDelete([unusedTextureId], success, [unusedTextureId], []);
|
|
380
|
+
txn.abandonChanges();
|
|
381
|
+
// deleteElement/deleteElements for a used SpatialCategory should fail (hard FK from GeometricElement3d.CategoryId, so not DefinitionInUse).
|
|
382
|
+
// Its used non-default SubCategories are pulled into the set as children and are reported as well.
|
|
383
|
+
assert.throws(() => txn.deleteElement(spatialCategoryId));
|
|
384
|
+
expectDelete([spatialCategoryId], constraintViolation(...usedSpatialCategoryViolations()), [], [spatialCategoryId, subCategoryId, filteredSubCategoryId]);
|
|
385
|
+
txn.abandonChanges();
|
|
386
|
+
// deleteElement/deleteElements for a default SubCategory should fail
|
|
387
|
+
const spatialCategory = iModelDb.elements.getElement(spatialCategoryId, SpatialCategory);
|
|
388
|
+
const defaultSpatialSubCategoryId = spatialCategory.myDefaultSubCategoryId();
|
|
389
|
+
assert.throws(() => txn.deleteElement(defaultSpatialSubCategoryId));
|
|
390
|
+
expectDelete([defaultSpatialSubCategoryId], constraintViolation(definitionInUse(defaultSpatialSubCategoryId)), [], [defaultSpatialSubCategoryId]);
|
|
391
|
+
txn.abandonChanges();
|
|
392
|
+
// deleteElement/deleteElements for a used, non-default SubCategory should fail
|
|
393
|
+
assert.throws(() => txn.deleteElement(subCategoryId));
|
|
394
|
+
expectDelete([subCategoryId], constraintViolation(definitionInUse(subCategoryId)), [], [subCategoryId]);
|
|
395
|
+
txn.abandonChanges();
|
|
396
|
+
// deleteElements for an unused SubCategory should succeed, delete should still fail
|
|
397
|
+
const unusedSubCategoryId = SubCategory.insert(txn, spatialCategoryId, "Unused SubCategory", {});
|
|
398
|
+
assert.isTrue(Id64.isValidId64(unusedSubCategoryId));
|
|
399
|
+
assert.throws(() => txn.deleteElement(unusedSubCategoryId));
|
|
400
|
+
expectDelete([unusedSubCategoryId], success, [unusedSubCategoryId], []);
|
|
401
|
+
txn.abandonChanges();
|
|
402
|
+
// deleteElements for an unused SpatialCategory should succeed, delete should still fail
|
|
403
|
+
const unusedSpatialCategoryId = SpatialCategory.insert(txn, definitionModelId, "Unused SpatialCategory", {});
|
|
404
|
+
assert.isTrue(Id64.isValidId64(unusedSpatialCategoryId));
|
|
405
|
+
const unusedSpatialCategory = iModelDb.elements.getElement(unusedSpatialCategoryId, SpatialCategory);
|
|
406
|
+
const unusedSpatialCategoryDefaultSubCategoryId = unusedSpatialCategory.myDefaultSubCategoryId();
|
|
407
|
+
assert.isTrue(Id64.isValidId64(unusedSpatialCategoryDefaultSubCategoryId));
|
|
408
|
+
assert.throws(() => txn.deleteElement(unusedSpatialCategoryId));
|
|
409
|
+
assert.throws(() => txn.deleteElement(unusedSpatialCategoryDefaultSubCategoryId));
|
|
410
|
+
expectDelete([unusedSpatialCategoryId], success, [unusedSpatialCategoryId, unusedSpatialCategoryDefaultSubCategoryId], []);
|
|
411
|
+
txn.abandonChanges();
|
|
412
|
+
// deleteElement/deleteElements of a used DrawingCategory should fail (hard FK from GeometricElement2d.CategoryId, so not DefinitionInUse)
|
|
413
|
+
assert.throws(() => txn.deleteElement(drawingCategoryId));
|
|
414
|
+
expectDelete([drawingCategoryId], constraintViolation(categoryInUse2d(drawingCategoryId)), [], [drawingCategoryId]);
|
|
415
|
+
txn.abandonChanges();
|
|
416
|
+
// deleteElements for an unused DrawingCategory should succeed, delete should still fail
|
|
417
|
+
const unusedDrawingCategoryId = DrawingCategory.insert(txn, definitionModelId, "Unused DrawingCategory", {});
|
|
418
|
+
assert.isTrue(Id64.isValidId64(unusedDrawingCategoryId));
|
|
419
|
+
const unusedDrawingCategory = iModelDb.elements.getElement(unusedDrawingCategoryId, DrawingCategory);
|
|
420
|
+
const unusedDrawingSubCategoryId = unusedDrawingCategory.myDefaultSubCategoryId();
|
|
421
|
+
assert.isTrue(Id64.isValidId64(unusedDrawingSubCategoryId));
|
|
422
|
+
assert.throws(() => txn.deleteElement(unusedDrawingSubCategoryId));
|
|
423
|
+
expectDelete([unusedDrawingCategoryId], success, [unusedDrawingCategoryId, unusedDrawingSubCategoryId], []);
|
|
424
|
+
txn.abandonChanges();
|
|
425
|
+
// deleteElement/deleteElements of DefinitionElements used by an existing SpatialViewDefinition should fail
|
|
426
|
+
assert.throws(() => txn.deleteElement(spatialCategorySelectorId));
|
|
427
|
+
assert.throws(() => txn.deleteElement(modelSelectorId));
|
|
428
|
+
assert.throws(() => txn.deleteElement(displayStyle3dId));
|
|
429
|
+
expectDelete([spatialCategorySelectorId, modelSelectorId, displayStyle3dId], constraintViolation(definitionInUse(spatialCategorySelectorId), definitionInUse(modelSelectorId), definitionInUse(displayStyle3dId)), [], [spatialCategorySelectorId, modelSelectorId, displayStyle3dId, viewId]);
|
|
430
|
+
txn.abandonChanges();
|
|
431
|
+
// deleteElements should succeed when the list includes the SpatialViewDefinition as the only thing referencing other DefinitionElements, delete should still fail
|
|
432
|
+
assert.throws(() => txn.deleteElement(viewId));
|
|
433
|
+
expectDelete([viewId, spatialCategorySelectorId, modelSelectorId, displayStyle3dId], success, [viewId, spatialCategorySelectorId, modelSelectorId, displayStyle3dId], []);
|
|
434
|
+
txn.abandonChanges();
|
|
435
|
+
// deleteElement/deleteElements of DefinitionElements used by an existing DrawingViewDefinition should fail
|
|
436
|
+
assert.throws(() => txn.deleteElement(drawingCategorySelectorId));
|
|
437
|
+
assert.throws(() => txn.deleteElement(displayStyle2dId));
|
|
438
|
+
expectDelete([drawingCategorySelectorId, displayStyle2dId], constraintViolation(definitionInUse(drawingCategorySelectorId), definitionInUse(displayStyle2dId)), [], [drawingCategorySelectorId, displayStyle2dId, drawingViewId]);
|
|
439
|
+
txn.abandonChanges();
|
|
440
|
+
// deleteElements should succeed when the list includes the DrawingViewDefinition as the only thing referencing other DefinitionElements, delete should still fail
|
|
441
|
+
assert.throws(() => txn.deleteElement(drawingViewId));
|
|
442
|
+
expectDelete([drawingViewId, drawingCategorySelectorId, displayStyle2dId], success, [drawingViewId, drawingCategorySelectorId, displayStyle2dId], []);
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
if (txn.isActive)
|
|
446
|
+
txn.end("abandon");
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
it("deleting a Subject is rejected when a definition beneath it is used from outside its subtree", () => {
|
|
450
|
+
const txn = new EditTxn(iModelDb, "delete subject with outside user");
|
|
451
|
+
txn.start();
|
|
452
|
+
try {
|
|
453
|
+
// A PhysicalObject in a model that is NOT under the Subject uses the SpatialCategory. The category is a hard FK violator
|
|
454
|
+
// (bis_GeometricElement3d.CategoryId, so ReferencedByForeignKey rather than DefinitionInUse). Its SubCategories are only
|
|
455
|
+
// used by elements inside the Subject's subtree, so they are not reported. The whole call is rejected - the requested
|
|
456
|
+
// PhysicalObjects survive as well, even though nothing references them.
|
|
457
|
+
const outsideModelId = PhysicalModel.insert(txn, IModel.rootSubjectId, "OutsideSubject");
|
|
458
|
+
const outsideUserProps = {
|
|
459
|
+
classFullName: "Generic:PhysicalObject",
|
|
460
|
+
model: outsideModelId,
|
|
461
|
+
category: spatialCategoryId,
|
|
462
|
+
code: Code.createEmpty(),
|
|
463
|
+
placement: { origin: Point3d.create(0, 0, 0).toJSON(), angles: {} },
|
|
464
|
+
};
|
|
465
|
+
const outsideUserId = txn.insertElement(outsideUserProps);
|
|
466
|
+
txn.saveChanges();
|
|
467
|
+
executeTestCase(txn, "subject rejected by outside user of its category", [physicalObjectId1, physicalObjectId2, physicalObjectId3, subjectId], [], constraintViolation(categoryInUse3d(spatialCategoryId)), [physicalObjectId1, physicalObjectId2, physicalObjectId3, subjectId, definitionModelId, physicalModelId, spatialCategoryId, IModel.getDefaultSubCategoryId(spatialCategoryId), outsideUserId]);
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
if (txn.isActive)
|
|
471
|
+
txn.end("abandon");
|
|
472
|
+
}
|
|
462
473
|
});
|
|
463
474
|
describe("basic tests", () => {
|
|
464
475
|
let txn;
|
|
@@ -471,28 +482,25 @@ describe("DeleteDefinitionElements", () => {
|
|
|
471
482
|
});
|
|
472
483
|
it("deletes a single unused definition element", () => {
|
|
473
484
|
const element = insertDefinitionElement(txn);
|
|
474
|
-
executeTestCase(txn, "single unused definition element", [element], [element],
|
|
485
|
+
executeTestCase(txn, "single unused definition element", [element], [element], success, []);
|
|
475
486
|
});
|
|
476
487
|
it("deletes multiple unused definition elements in one call", () => {
|
|
477
488
|
const element1 = insertDefinitionElement(txn);
|
|
478
489
|
const element2 = insertDefinitionElement(txn);
|
|
479
490
|
const element3 = insertDefinitionElement(txn);
|
|
480
|
-
executeTestCase(txn, "multiple unused definition elements", [element1, element2, element3], [element1, element2, element3],
|
|
491
|
+
executeTestCase(txn, "multiple unused definition elements", [element1, element2, element3], [element1, element2, element3], success, []);
|
|
481
492
|
});
|
|
482
|
-
it("returns
|
|
483
|
-
|
|
484
|
-
assert.isEmpty(Array.from(failed), "empty input should return empty failed set");
|
|
493
|
+
it("returns a Success result when given an empty array", () => {
|
|
494
|
+
executeTestCase(txn, "empty input", [], [], success, []);
|
|
485
495
|
});
|
|
486
496
|
it("throws for invalid ids", () => {
|
|
487
497
|
assert.throws(() => txn.deleteElements([Id64.invalid, Id64.invalid]));
|
|
488
498
|
});
|
|
489
|
-
it("
|
|
499
|
+
it("reports an in-use definition element as a failure and does not delete it", () => {
|
|
490
500
|
// geometryPartId is referenced by PhysicalObject1's geometry stream
|
|
491
|
-
|
|
492
|
-
assert.isTrue(failed.failedIds.has(geometryPartId), "in-use definition element must be in failed set");
|
|
493
|
-
assert.isDefined(iModelDb.elements.tryGetElement(geometryPartId), "in-use definition element must not be deleted");
|
|
501
|
+
executeTestCase(txn, "in-use GeometryPart", [geometryPartId], [], constraintViolation(definitionInUse(geometryPartId)), [geometryPartId]);
|
|
494
502
|
});
|
|
495
|
-
it("
|
|
503
|
+
it("reports only the in-use definition element as a failure when mixing used and unused parts, and deletes nothing", () => {
|
|
496
504
|
const unusedElement = insertDefinitionElement(txn);
|
|
497
505
|
const usedElement = insertDefinitionElement(txn);
|
|
498
506
|
// Reference usedElement from a physical element geometry stream
|
|
@@ -505,27 +513,23 @@ describe("DeleteDefinitionElements", () => {
|
|
|
505
513
|
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1), spatialCategoryId, undefined, undefined, usedElement),
|
|
506
514
|
};
|
|
507
515
|
const physElemId = txn.insertElement(physElemProps);
|
|
508
|
-
executeTestCase(txn, "used + unused definition elements", [unusedElement, usedElement], [
|
|
516
|
+
executeTestCase(txn, "used + unused definition elements", [unusedElement, usedElement], [], constraintViolation(definitionInUse(usedElement)), [unusedElement, usedElement, physElemId]);
|
|
509
517
|
});
|
|
510
|
-
it("
|
|
511
|
-
|
|
512
|
-
assert.isTrue(failed.failedIds.has(renderMaterialId), "in-use RenderMaterial must be in failed set");
|
|
513
|
-
assert.isDefined(iModelDb.elements.tryGetElement(renderMaterialId), "in-use RenderMaterial must not be deleted");
|
|
518
|
+
it("reports an in-use RenderMaterial as a failure and does not delete it", () => {
|
|
519
|
+
executeTestCase(txn, "in-use RenderMaterial", [renderMaterialId], [], constraintViolation(definitionInUse(renderMaterialId)), [renderMaterialId]);
|
|
514
520
|
});
|
|
515
521
|
it("deletes an unused RenderMaterial", () => {
|
|
516
522
|
const unusedRenderMaterialId = RenderMaterialElement.insert(txn, definitionModelId, "Unused RenderMaterial", { paletteName: "PaletteName" });
|
|
517
|
-
executeTestCase(txn, "unused RenderMaterial", [unusedRenderMaterialId], [unusedRenderMaterialId],
|
|
523
|
+
executeTestCase(txn, "unused RenderMaterial", [unusedRenderMaterialId], [unusedRenderMaterialId], success, []);
|
|
518
524
|
});
|
|
519
|
-
it("
|
|
520
|
-
|
|
521
|
-
assert.isTrue(failed.failedIds.has(textureId), "in-use Texture must be in failed set");
|
|
522
|
-
assert.isDefined(iModelDb.elements.tryGetElement(textureId), "in-use Texture must not be deleted");
|
|
525
|
+
it("reports an in-use Texture as a failure and does not delete it", () => {
|
|
526
|
+
executeTestCase(txn, "in-use Texture", [textureId], [], constraintViolation(definitionInUse(textureId)), [textureId]);
|
|
523
527
|
});
|
|
524
528
|
it("deletes an unused Texture", () => {
|
|
525
529
|
const unusedTextureId = IModelTestUtils.insertTextureElement(txn, definitionModelId, "Unused Texture");
|
|
526
|
-
executeTestCase(txn, "unused Texture", [unusedTextureId], [unusedTextureId],
|
|
530
|
+
executeTestCase(txn, "unused Texture", [unusedTextureId], [unusedTextureId], success, []);
|
|
527
531
|
});
|
|
528
|
-
it("
|
|
532
|
+
it("reports every element as a failure when all are in-use definition elements", () => {
|
|
529
533
|
const usedElement1 = insertDefinitionElement(txn);
|
|
530
534
|
const usedElement2 = insertDefinitionElement(txn);
|
|
531
535
|
const usedElement3 = insertDefinitionElement(txn);
|
|
@@ -543,11 +547,33 @@ describe("DeleteDefinitionElements", () => {
|
|
|
543
547
|
assert.isTrue(Id64.isValid(txn.insertElement(physElemProps)));
|
|
544
548
|
physElemProps.geom = IModelTestUtils.createBox(Point3d.create(1, 1, 1), spatialCategoryId, undefined, undefined, usedElement3);
|
|
545
549
|
assert.isTrue(Id64.isValid(txn.insertElement(physElemProps)));
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
550
|
+
// Every requested element is in use - all three are reported and nothing is deleted.
|
|
551
|
+
executeTestCase(txn, "all in-use", [usedElement1, usedElement2, usedElement3], [], constraintViolation(definitionInUse(usedElement1), definitionInUse(usedElement2), definitionInUse(usedElement3)), [usedElement1, usedElement2, usedElement3]);
|
|
552
|
+
});
|
|
553
|
+
it("a definition used only by elements inside the delete set is not DefinitionInUse", () => {
|
|
554
|
+
const insertPhysicalObject = (model, category) => {
|
|
555
|
+
const props = {
|
|
556
|
+
classFullName: "Generic:PhysicalObject",
|
|
557
|
+
model,
|
|
558
|
+
category,
|
|
559
|
+
code: Code.createEmpty(),
|
|
560
|
+
placement: { origin: [0, 0, 0], angles: { yaw: 0, pitch: 0, roll: 0 } },
|
|
561
|
+
};
|
|
562
|
+
return txn.insertElement(props);
|
|
563
|
+
};
|
|
564
|
+
// Case 1: the only user of the category is requested directly.
|
|
565
|
+
const directCategory = SpatialCategory.insert(txn, definitionModelId, "DirectUserCategory", new SubCategoryAppearance());
|
|
566
|
+
const directUser = insertPhysicalObject(physicalModelId, directCategory);
|
|
567
|
+
// Case 2: the only user of the category lives in a sub-model that is reached through the partition cascade, i.e. it is in the
|
|
568
|
+
// *expanded* delete set but not in the requested ids.
|
|
569
|
+
const cascadeCategory = SpatialCategory.insert(txn, definitionModelId, "CascadeUserCategory", new SubCategoryAppearance());
|
|
570
|
+
const partitionId = PhysicalModel.insert(txn, subjectId, "CascadeUserModel");
|
|
571
|
+
const cascadeUser = insertPhysicalObject(partitionId, cascadeCategory);
|
|
572
|
+
txn.saveChanges();
|
|
573
|
+
const directSubCategory = IModel.getDefaultSubCategoryId(directCategory);
|
|
574
|
+
const cascadeSubCategory = IModel.getDefaultSubCategoryId(cascadeCategory);
|
|
575
|
+
executeTestCase(txn, "category + its direct user", [directCategory, directUser], [directCategory, directSubCategory, directUser], success, [cascadeCategory, cascadeSubCategory, cascadeUser]);
|
|
576
|
+
executeTestCase(txn, "category + partition containing its only user", [cascadeCategory, partitionId], [cascadeCategory, cascadeSubCategory, partitionId, cascadeUser], success, [directCategory, directSubCategory, directUser]);
|
|
551
577
|
});
|
|
552
578
|
});
|
|
553
579
|
describe("category and subcategory tests", () => {
|
|
@@ -559,53 +585,47 @@ describe("DeleteDefinitionElements", () => {
|
|
|
559
585
|
afterEach(() => {
|
|
560
586
|
txn.end("abandon");
|
|
561
587
|
});
|
|
562
|
-
it("
|
|
563
|
-
|
|
564
|
-
assert.isTrue(failed.failedIds.has(spatialCategoryId), "in-use SpatialCategory must be in failed set");
|
|
565
|
-
assert.isDefined(iModelDb.elements.tryGetElement(spatialCategoryId), "in-use SpatialCategory must not be deleted");
|
|
588
|
+
it("reports an in-use SpatialCategory as an FK violator (plus its used SubCategories) and does not delete it", () => {
|
|
589
|
+
executeTestCase(txn, "in-use SpatialCategory", [spatialCategoryId], [], constraintViolation(...usedSpatialCategoryViolations()), [spatialCategoryId, subCategoryId, filteredSubCategoryId]);
|
|
566
590
|
});
|
|
567
|
-
it("
|
|
591
|
+
it("reports the default SubCategory as a failure when its parent SpatialCategory is in use", () => {
|
|
568
592
|
const spatialCategory = iModelDb.elements.getElement(spatialCategoryId, SpatialCategory);
|
|
569
593
|
const defaultSubCategoryId = spatialCategory.myDefaultSubCategoryId();
|
|
570
|
-
|
|
571
|
-
assert.isTrue(failed.failedIds.has(defaultSubCategoryId), "default SubCategory must be in failed set");
|
|
572
|
-
assert.isDefined(iModelDb.elements.tryGetElement(defaultSubCategoryId), "default SubCategory must not be deleted");
|
|
594
|
+
executeTestCase(txn, "default SubCategory", [defaultSubCategoryId], [], constraintViolation(definitionInUse(defaultSubCategoryId)), [defaultSubCategoryId]);
|
|
573
595
|
});
|
|
574
|
-
it("
|
|
575
|
-
|
|
576
|
-
assert.isTrue(failed.failedIds.has(subCategoryId), "in-use SubCategory must be in failed set");
|
|
577
|
-
assert.isDefined(iModelDb.elements.tryGetElement(subCategoryId), "in-use SubCategory must not be deleted");
|
|
596
|
+
it("reports a used non-default SubCategory as a failure and does not delete it", () => {
|
|
597
|
+
executeTestCase(txn, "in-use SubCategory", [subCategoryId], [], constraintViolation(definitionInUse(subCategoryId)), [subCategoryId]);
|
|
578
598
|
});
|
|
579
599
|
it("deletes an unused non-default SubCategory", () => {
|
|
580
600
|
const unusedSubCategoryId = SubCategory.insert(txn, spatialCategoryId, "Unused SubCategory", {});
|
|
581
|
-
executeTestCase(txn, "unused SubCategory", [unusedSubCategoryId], [unusedSubCategoryId],
|
|
601
|
+
executeTestCase(txn, "unused SubCategory", [unusedSubCategoryId], [unusedSubCategoryId], success, [spatialCategoryId]);
|
|
582
602
|
});
|
|
583
603
|
it("deletes an unused SpatialCategory together with its default SubCategory", () => {
|
|
584
604
|
const unusedCategoryId = SpatialCategory.insert(txn, definitionModelId, "Unused SpatialCategory", {});
|
|
585
605
|
const unusedCategory = iModelDb.elements.getElement(unusedCategoryId, SpatialCategory);
|
|
586
606
|
const unusedDefaultSubCategoryId = unusedCategory.myDefaultSubCategoryId();
|
|
587
|
-
executeTestCase(txn, "unused SpatialCategory + default SubCategory", [unusedCategoryId], [unusedCategoryId, unusedDefaultSubCategoryId],
|
|
607
|
+
executeTestCase(txn, "unused SpatialCategory + default SubCategory", [unusedCategoryId], [unusedCategoryId, unusedDefaultSubCategoryId], success, []);
|
|
588
608
|
});
|
|
589
609
|
it("deletes an unused DrawingCategory together with its default SubCategory", () => {
|
|
590
610
|
const unusedDrawingCategoryId = DrawingCategory.insert(txn, definitionModelId, "Unused DrawingCategory", {});
|
|
591
611
|
const unusedDrawingCategory = iModelDb.elements.getElement(unusedDrawingCategoryId, DrawingCategory);
|
|
592
612
|
const unusedDrawingSubCategoryId = unusedDrawingCategory.myDefaultSubCategoryId();
|
|
593
|
-
executeTestCase(txn, "unused DrawingCategory + default SubCategory", [unusedDrawingCategoryId], [unusedDrawingCategoryId, unusedDrawingSubCategoryId],
|
|
613
|
+
executeTestCase(txn, "unused DrawingCategory + default SubCategory", [unusedDrawingCategoryId], [unusedDrawingCategoryId, unusedDrawingSubCategoryId], success, []);
|
|
594
614
|
});
|
|
595
|
-
it("
|
|
596
|
-
executeTestCase(txn, "view-related still referenced", [spatialCategorySelectorId, modelSelectorId, displayStyle3dId], [],
|
|
615
|
+
it("reports CategorySelector, ModelSelector, DisplayStyle3d as failures when their view still exists", () => {
|
|
616
|
+
executeTestCase(txn, "view-related still referenced", [spatialCategorySelectorId, modelSelectorId, displayStyle3dId], [], constraintViolation(definitionInUse(spatialCategorySelectorId), definitionInUse(modelSelectorId), definitionInUse(displayStyle3dId)), [spatialCategorySelectorId, modelSelectorId, displayStyle3dId, viewId]);
|
|
597
617
|
});
|
|
598
618
|
it("deletes CategorySelector, ModelSelector, DisplayStyle3d and the SpatialViewDefinition in one call", () => {
|
|
599
|
-
executeTestCase(txn, "view + view-related", [viewId, spatialCategorySelectorId, modelSelectorId, displayStyle3dId], [viewId, spatialCategorySelectorId, modelSelectorId, displayStyle3dId],
|
|
619
|
+
executeTestCase(txn, "view + view-related", [viewId, spatialCategorySelectorId, modelSelectorId, displayStyle3dId], [viewId, spatialCategorySelectorId, modelSelectorId, displayStyle3dId], success, []);
|
|
600
620
|
});
|
|
601
621
|
it("deletes DrawingCategorySelector, DisplayStyle2d and the DrawingViewDefinition in one call", () => {
|
|
602
|
-
executeTestCase(txn, "drawing view + drawing view-related", [drawingViewId, drawingCategorySelectorId, displayStyle2dId], [drawingViewId, drawingCategorySelectorId, displayStyle2dId],
|
|
622
|
+
executeTestCase(txn, "drawing view + drawing view-related", [drawingViewId, drawingCategorySelectorId, displayStyle2dId], [drawingViewId, drawingCategorySelectorId, displayStyle2dId], success, []);
|
|
603
623
|
});
|
|
604
624
|
it("deletes orphaned CategorySelector and DisplayStyle2d (no view references them)", () => {
|
|
605
625
|
// Insert a standalone CategorySelector and DisplayStyle2d that are not referenced by any view
|
|
606
626
|
const orphanCategorySelectorId = CategorySelector.insert(txn, definitionModelId, "OrphanCategorySelector", [drawingCategoryId]);
|
|
607
627
|
const orphanDisplayStyle2dId = DisplayStyle2d.insert(txn, definitionModelId, "OrphanDisplayStyle2d");
|
|
608
|
-
executeTestCase(txn, "orphaned view-related elements", [orphanCategorySelectorId, orphanDisplayStyle2dId], [orphanCategorySelectorId, orphanDisplayStyle2dId],
|
|
628
|
+
executeTestCase(txn, "orphaned view-related elements", [orphanCategorySelectorId, orphanDisplayStyle2dId], [orphanCategorySelectorId, orphanDisplayStyle2dId], success, []);
|
|
609
629
|
});
|
|
610
630
|
});
|
|
611
631
|
describe("Element hierarchy", () => {
|
|
@@ -621,13 +641,13 @@ describe("DeleteDefinitionElements", () => {
|
|
|
621
641
|
const parentPart = insertDefinitionElement(txn);
|
|
622
642
|
const childPart = insertDefinitionElement(txn, parentPart);
|
|
623
643
|
// Only pass the parent - the child must be pulled in automatically
|
|
624
|
-
executeTestCase(txn, "parent + child definition elements", [parentPart], [parentPart, childPart],
|
|
644
|
+
executeTestCase(txn, "parent + child definition elements", [parentPart], [parentPart, childPart], success, []);
|
|
625
645
|
});
|
|
626
646
|
it("deletes a parent DefinitionElement and its child explicitly passed", () => {
|
|
627
647
|
const parentPart = insertDefinitionElement(txn);
|
|
628
648
|
const childPart = insertDefinitionElement(txn, parentPart);
|
|
629
649
|
// Pass both parent and child explicitly
|
|
630
|
-
executeTestCase(txn, "parent + child definition elements", [parentPart, childPart], [parentPart, childPart],
|
|
650
|
+
executeTestCase(txn, "parent + child definition elements", [parentPart, childPart], [parentPart, childPart], success, []);
|
|
631
651
|
});
|
|
632
652
|
// Intra-set code scope
|
|
633
653
|
// Both partA and partB are in the input set; partB uses partA as its CodeScope.
|
|
@@ -644,7 +664,7 @@ describe("DeleteDefinitionElements", () => {
|
|
|
644
664
|
};
|
|
645
665
|
const partB = txn.insertElement(partBProps);
|
|
646
666
|
// Both unused externally; the intra-set code-scope link must not block deletion
|
|
647
|
-
executeTestCase(txn, "intra-set code scope", [partA, partB], [partA, partB],
|
|
667
|
+
executeTestCase(txn, "intra-set code scope", [partA, partB], [partA, partB], success, []);
|
|
648
668
|
});
|
|
649
669
|
// Multi-level parent-child hierarchy
|
|
650
670
|
// Passing only the root must automatically pull in all descendants (leaf-first).
|
|
@@ -655,14 +675,14 @@ describe("DeleteDefinitionElements", () => {
|
|
|
655
675
|
const child = insertDefinitionElement(txn, root);
|
|
656
676
|
const grandchild = insertDefinitionElement(txn, child);
|
|
657
677
|
// Only supply the root - all descendants must be included and deleted leaf-first
|
|
658
|
-
executeTestCase(txn, "3-level hierarchy via root only", [root], [root, child, grandchild],
|
|
678
|
+
executeTestCase(txn, "3-level hierarchy via root only", [root], [root, child, grandchild], success, []);
|
|
659
679
|
});
|
|
660
680
|
it("deletes a 3-level parent-child hierarchy by passing all elements explicitly", () => {
|
|
661
681
|
const root = insertDefinitionElement(txn);
|
|
662
682
|
const child = insertDefinitionElement(txn, root);
|
|
663
683
|
const grandchild = insertDefinitionElement(txn, child);
|
|
664
684
|
// Pass all elements explicitly
|
|
665
|
-
executeTestCase(txn, "3-level hierarchy with all elements", [root, child, grandchild], [root, child, grandchild],
|
|
685
|
+
executeTestCase(txn, "3-level hierarchy with all elements", [root, child, grandchild], [root, child, grandchild], success, []);
|
|
666
686
|
});
|
|
667
687
|
// Mixed inputs
|
|
668
688
|
it("deletes a mixed batch of unused DefinitionElements of different types in one call", () => {
|
|
@@ -670,7 +690,7 @@ describe("DeleteDefinitionElements", () => {
|
|
|
670
690
|
const unusedRenderMaterial = RenderMaterialElement.insert(txn, definitionModelId, "BatchRenderMaterial", { paletteName: "P" });
|
|
671
691
|
const unusedTexture = IModelTestUtils.insertTextureElement(txn, definitionModelId, "BatchTexture");
|
|
672
692
|
const unusedSubCategory = SubCategory.insert(txn, spatialCategoryId, "BatchSubCategory", {});
|
|
673
|
-
executeTestCase(txn, "mixed unused batch", [unusedPart, unusedRenderMaterial, unusedTexture, unusedSubCategory], [unusedPart, unusedRenderMaterial, unusedTexture, unusedSubCategory],
|
|
693
|
+
executeTestCase(txn, "mixed unused batch", [unusedPart, unusedRenderMaterial, unusedTexture, unusedSubCategory], [unusedPart, unusedRenderMaterial, unusedTexture, unusedSubCategory], success, []);
|
|
674
694
|
});
|
|
675
695
|
// Mixed inputs with intra scope violations and parent child hierarchies
|
|
676
696
|
it("deletes a mixed batch of used and unused DefinitionElements in a hierarchy with conflicting code scopes with child", () => {
|
|
@@ -686,7 +706,7 @@ describe("DeleteDefinitionElements", () => {
|
|
|
686
706
|
};
|
|
687
707
|
const scopedPart = txn.insertElement(scopedPartProps);
|
|
688
708
|
// Pass both used and unused parts, with a child that has a code scope reference
|
|
689
|
-
executeTestCase(txn, "mixed used and unused batch", [usedPart, unusedPart, scopedPart], [usedPart, unusedPart, childPart, scopedPart],
|
|
709
|
+
executeTestCase(txn, "mixed used and unused batch", [usedPart, unusedPart, scopedPart], [usedPart, unusedPart, childPart, scopedPart], success, []);
|
|
690
710
|
});
|
|
691
711
|
// Mixed inputs with intra scope violations and parent child hierarchies
|
|
692
712
|
it("deletes a mixed batch of used and unused DefinitionElements in a hierarchy with conflicting code scopes", () => {
|
|
@@ -701,8 +721,9 @@ describe("DeleteDefinitionElements", () => {
|
|
|
701
721
|
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1)),
|
|
702
722
|
};
|
|
703
723
|
const scopedPart = txn.insertElement(scopedPartProps);
|
|
704
|
-
// Pass both used and unused parts
|
|
705
|
-
|
|
724
|
+
// Pass both used and unused parts; usedPart is the code scope of scopedPart (outside the set) so it is a direct FK violator
|
|
725
|
+
// and the whole call is rejected (unusedPart survives too).
|
|
726
|
+
executeTestCase(txn, "mixed used and unused batch", [usedPart, unusedPart], [], constraintViolation(codeScopeViolator(usedPart)), [usedPart, unusedPart, childPart, scopedPart]);
|
|
706
727
|
});
|
|
707
728
|
});
|
|
708
729
|
describe("sub-model hierarchy", () => {
|
|
@@ -729,18 +750,18 @@ describe("DeleteDefinitionElements", () => {
|
|
|
729
750
|
const containerId = insertDefinitionContainer();
|
|
730
751
|
const partA = insertDefinitionElement(txn, undefined, containerId);
|
|
731
752
|
const partB = insertDefinitionElement(txn, undefined, containerId);
|
|
732
|
-
executeTestCase(txn, "unused elements in container sub-model", [partA, partB], [partA, partB],
|
|
753
|
+
executeTestCase(txn, "unused elements in container sub-model", [partA, partB], [partA, partB], success, [], false);
|
|
733
754
|
assertModelExists(containerId, "DefinitionContainer must survive when container is not in the input set");
|
|
734
755
|
});
|
|
735
756
|
it("deletes a DefinitionContainer and all definition elements inside it", () => {
|
|
736
757
|
const containerId = insertDefinitionContainer();
|
|
737
758
|
const defElem1 = insertDefinitionElement(txn, undefined, containerId);
|
|
738
759
|
const defElem2 = insertDefinitionElement(txn, undefined, containerId);
|
|
739
|
-
executeTestCase(txn, "container + sub-model contents deleted via container id", [containerId], [containerId, defElem1, defElem2],
|
|
760
|
+
executeTestCase(txn, "container + sub-model contents deleted via container id", [containerId], [containerId, defElem1, defElem2], success, []);
|
|
740
761
|
// Sub-model row is deleted once the container element is successfully purged.
|
|
741
762
|
assertModelDeleted(containerId, "DefinitionModel sub-model row must be deleted after container is purged");
|
|
742
763
|
});
|
|
743
|
-
it("
|
|
764
|
+
it("reports an in-use element inside a DefinitionContainer sub-model as a failure", () => {
|
|
744
765
|
const containerId = insertDefinitionContainer();
|
|
745
766
|
const usedPart = insertDefinitionElement(txn, undefined, containerId);
|
|
746
767
|
const unusedPart = insertDefinitionElement(txn, undefined, containerId);
|
|
@@ -754,10 +775,10 @@ describe("DeleteDefinitionElements", () => {
|
|
|
754
775
|
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1), spatialCategoryId, undefined, undefined, usedPart),
|
|
755
776
|
};
|
|
756
777
|
const physElemId = txn.insertElement(physElemProps);
|
|
757
|
-
executeTestCase(txn, "one used + one unused in container sub-model", [usedPart, unusedPart], [
|
|
758
|
-
assertModelExists(containerId, "DefinitionContainer sub-model row must survive when
|
|
778
|
+
executeTestCase(txn, "one used + one unused in container sub-model", [usedPart, unusedPart], [], constraintViolation(definitionInUse(usedPart)), [usedPart, unusedPart, physElemId], false);
|
|
779
|
+
assertModelExists(containerId, "DefinitionContainer sub-model row must survive when the call is rejected");
|
|
759
780
|
});
|
|
760
|
-
it("
|
|
781
|
+
it("call rejected when the child of a requested parent inside a DefinitionContainer sub-model is in use", () => {
|
|
761
782
|
const containerId = insertDefinitionContainer();
|
|
762
783
|
const parent = insertDefinitionElement(txn, undefined, containerId);
|
|
763
784
|
const child = insertDefinitionElement(txn, parent, containerId);
|
|
@@ -771,8 +792,9 @@ describe("DeleteDefinitionElements", () => {
|
|
|
771
792
|
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1), spatialCategoryId, undefined, undefined, child),
|
|
772
793
|
};
|
|
773
794
|
const physElemId = txn.insertElement(physElemProps);
|
|
774
|
-
|
|
775
|
-
|
|
795
|
+
// child (pulled into the set via parent) is a DefinitionInUse violator; only the direct violator is reported.
|
|
796
|
+
executeTestCase(txn, "parent rejected when child inside container sub-model is in use", [parent], [], constraintViolation(definitionInUse(child)), [parent, child, physElemId], false);
|
|
797
|
+
assertModelExists(containerId, "DefinitionContainer sub-model row must survive when the call is rejected");
|
|
776
798
|
});
|
|
777
799
|
it("deletes two elements inside a DefinitionContainer sub-model related by intra-delete-set code scope", () => {
|
|
778
800
|
const containerId = insertDefinitionContainer();
|
|
@@ -785,10 +807,10 @@ describe("DeleteDefinitionElements", () => {
|
|
|
785
807
|
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1)),
|
|
786
808
|
};
|
|
787
809
|
const partB = txn.insertElement(partBProps);
|
|
788
|
-
executeTestCase(txn, "intra-set code scope in container sub-model", [partA, partB], [partA, partB],
|
|
810
|
+
executeTestCase(txn, "intra-set code scope in container sub-model", [partA, partB], [partA, partB], success, [], false);
|
|
789
811
|
assertModelExists(containerId, "DefinitionContainer sub-model row must survive when container is not in the input set");
|
|
790
812
|
});
|
|
791
|
-
it("element inside a DefinitionContainer sub-model
|
|
813
|
+
it("element inside a DefinitionContainer sub-model rejects the call when used as external code scope", () => {
|
|
792
814
|
const containerId = insertDefinitionContainer();
|
|
793
815
|
const scopeSpecId = iModelDb.codeSpecs.insert(txn, "ExtScopeSpec", CodeScopeSpec.Type.RelatedElement);
|
|
794
816
|
const partA = insertDefinitionElement(txn, undefined, containerId);
|
|
@@ -801,8 +823,8 @@ describe("DeleteDefinitionElements", () => {
|
|
|
801
823
|
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1)),
|
|
802
824
|
};
|
|
803
825
|
const externalId = txn.insertElement(externalProps);
|
|
804
|
-
executeTestCase(txn, "external code scope
|
|
805
|
-
assertModelExists(containerId, "DefinitionContainer sub-model row must survive when
|
|
826
|
+
executeTestCase(txn, "external code scope rejects element inside container sub-model", [partA, partB], [], constraintViolation(codeScopeViolator(partA)), [partA, partB, externalId], false);
|
|
827
|
+
assertModelExists(containerId, "DefinitionContainer sub-model row must survive when the call is rejected");
|
|
806
828
|
});
|
|
807
829
|
it("cross-container intra-set code scope: both elements deleted cleanly", () => {
|
|
808
830
|
const container1 = insertDefinitionContainer();
|
|
@@ -816,11 +838,11 @@ describe("DeleteDefinitionElements", () => {
|
|
|
816
838
|
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1)),
|
|
817
839
|
};
|
|
818
840
|
const partB = txn.insertElement(partBProps);
|
|
819
|
-
executeTestCase(txn, "cross-container intra-set code scope", [partA, partB], [partA, partB],
|
|
841
|
+
executeTestCase(txn, "cross-container intra-set code scope", [partA, partB], [partA, partB], success, [], false);
|
|
820
842
|
assertModelExists(container1, "container1 sub-model row must survive when container is not in the input set");
|
|
821
843
|
assertModelExists(container2, "container2 sub-model row must survive when container is not in the input set");
|
|
822
844
|
});
|
|
823
|
-
it("mixed elements where in-use ones
|
|
845
|
+
it("mixed elements where in-use ones reject the call and nothing is deleted", () => {
|
|
824
846
|
const containerId = insertDefinitionContainer();
|
|
825
847
|
const topUnused = insertDefinitionElement(txn, undefined, definitionModelId);
|
|
826
848
|
const topUsed = insertDefinitionElement(txn, undefined, definitionModelId);
|
|
@@ -839,8 +861,141 @@ describe("DeleteDefinitionElements", () => {
|
|
|
839
861
|
};
|
|
840
862
|
const topUsedPhysElem = makeUser(topUsed);
|
|
841
863
|
const nestedUsedPhysElem = makeUser(nestedUsed);
|
|
842
|
-
executeTestCase(txn, "mixed top-level and container sub-model: used
|
|
843
|
-
assertModelExists(containerId, "DefinitionContainer sub-model row must survive when
|
|
864
|
+
executeTestCase(txn, "mixed top-level and container sub-model: used reported, nothing deleted", [topUnused, topUsed, nestedUnused, nestedUsed], [], constraintViolation(definitionInUse(topUsed), definitionInUse(nestedUsed)), [topUnused, topUsed, nestedUnused, nestedUsed, topUsedPhysElem, nestedUsedPhysElem], false);
|
|
865
|
+
assertModelExists(containerId, "DefinitionContainer sub-model row must survive when the call is rejected");
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
describe("definition and its users deleted together", () => {
|
|
869
|
+
let txn;
|
|
870
|
+
beforeEach(() => {
|
|
871
|
+
txn = new EditTxn(iModelDb, "delete definition + users");
|
|
872
|
+
txn.start();
|
|
873
|
+
});
|
|
874
|
+
afterEach(() => {
|
|
875
|
+
txn.end("abandon");
|
|
876
|
+
});
|
|
877
|
+
const insertPhysicalObject = (opts = {}) => {
|
|
878
|
+
const category = opts.categoryId ?? spatialCategoryId;
|
|
879
|
+
// A material is only recorded in the geometry stream together with a sub-category.
|
|
880
|
+
const subCategory = opts.subCategoryId ?? (opts.renderMaterialId ? IModel.getDefaultSubCategoryId(category) : undefined);
|
|
881
|
+
return txn.insertElement({
|
|
882
|
+
classFullName: "Generic:PhysicalObject",
|
|
883
|
+
model: physicalModelId,
|
|
884
|
+
category,
|
|
885
|
+
code: Code.createEmpty(),
|
|
886
|
+
placement: { origin: [0, 0, 0], angles: { yaw: 0, pitch: 0, roll: 0 } },
|
|
887
|
+
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1), category, subCategory, opts.renderMaterialId, opts.geometryPartId),
|
|
888
|
+
});
|
|
889
|
+
};
|
|
890
|
+
it("a GeometryPart with two users is still in use when only one user is in the delete set", () => {
|
|
891
|
+
const part = insertDefinitionElement(txn);
|
|
892
|
+
const user1 = insertPhysicalObject({ geometryPartId: part });
|
|
893
|
+
const user2 = insertPhysicalObject({ geometryPartId: part });
|
|
894
|
+
executeTestCase(txn, "part + one of two users", [part, user1], [], constraintViolation(definitionInUse(part)), [part, user1, user2]);
|
|
895
|
+
});
|
|
896
|
+
it("a DisplayStyle shared by two views is reported in use when only one view is in the delete set", () => {
|
|
897
|
+
const categorySelectorB = CategorySelector.insert(txn, definitionModelId, "CategorySelectorB", [spatialCategoryId]);
|
|
898
|
+
const modelSelectorB = ModelSelector.insert(txn, definitionModelId, "ModelSelectorB", [physicalModelId]);
|
|
899
|
+
const viewB = OrthographicViewDefinition.insert(txn, definitionModelId, "ViewB", modelSelectorB, categorySelectorB, displayStyle3dId, new Range3d(0, 0, 0, 1, 1, 1));
|
|
900
|
+
executeTestCase(txn, "view A + its selectors + shared style", [viewId, spatialCategorySelectorId, modelSelectorId, displayStyle3dId], [], constraintViolation(definitionInUse(displayStyle3dId)), [viewId, spatialCategorySelectorId, modelSelectorId, displayStyle3dId, viewB, categorySelectorB, modelSelectorB], false);
|
|
901
|
+
// Deleting both views releases the shared style.
|
|
902
|
+
executeTestCase(txn, "both views + everything they reference", [viewId, viewB, spatialCategorySelectorId, modelSelectorId, categorySelectorB, modelSelectorB, displayStyle3dId], [viewId, viewB, spatialCategorySelectorId, modelSelectorId, categorySelectorB, modelSelectorB, displayStyle3dId], success, []);
|
|
903
|
+
});
|
|
904
|
+
it("an element referenced through several foreign keys is reported once", () => {
|
|
905
|
+
// A fresh category used by a physical object (CategoryId FK) AND used as the Code scope of a GeometryPart (CodeScopeId FK).
|
|
906
|
+
const category = SpatialCategory.insert(txn, definitionModelId, "DoublyReferencedCategory", {});
|
|
907
|
+
const user = insertPhysicalObject({ categoryId: category });
|
|
908
|
+
const scopeSpecId = iModelDb.codeSpecs.insert(txn, "CategoryScopedSpec", CodeScopeSpec.Type.RelatedElement);
|
|
909
|
+
const scopedPart = txn.insertElement({
|
|
910
|
+
classFullName: GeometryPart.classFullName,
|
|
911
|
+
model: definitionModelId,
|
|
912
|
+
code: { spec: scopeSpecId, scope: category, value: "scoped-part" },
|
|
913
|
+
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1)),
|
|
914
|
+
});
|
|
915
|
+
const result = txn.deleteElements([category]);
|
|
916
|
+
assert.equal(result.status, BulkDeleteElementsStatus.ConstraintViolation);
|
|
917
|
+
assert.equal(result.failures.length, 1, `expected a single failure, got ${JSON.stringify(result.failures)}`);
|
|
918
|
+
assert.equal(result.failures[0].elementId, category);
|
|
919
|
+
assert.equal(result.failures[0].reason, BulkDeleteFailureReason.ReferencedByForeignKey);
|
|
920
|
+
for (const id of [category, user, scopedPart])
|
|
921
|
+
assert.isDefined(iModelDb.elements.tryGetElement(id));
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
describe("all-or-nothing semantics with definition elements", () => {
|
|
925
|
+
let txn;
|
|
926
|
+
beforeEach(() => {
|
|
927
|
+
txn = new EditTxn(iModelDb, "all-or-nothing");
|
|
928
|
+
txn.start();
|
|
929
|
+
});
|
|
930
|
+
afterEach(() => {
|
|
931
|
+
txn.end("abandon");
|
|
932
|
+
});
|
|
933
|
+
it("DefinitionInUse is a constraint violation: nothing is deleted, including unused elements in the same call", () => {
|
|
934
|
+
const unusedPart = insertDefinitionElement(txn);
|
|
935
|
+
const result = txn.deleteElements([geometryPartId, unusedPart]);
|
|
936
|
+
assertResult(result, constraintViolation(definitionInUse(geometryPartId)), [], [geometryPartId, unusedPart], iModelDb);
|
|
937
|
+
});
|
|
938
|
+
it("a used Category is a constraint violation and every violator in the expanded set is reported", () => {
|
|
939
|
+
const unusedPart = insertDefinitionElement(txn);
|
|
940
|
+
const result = txn.deleteElements([spatialCategoryId, unusedPart]);
|
|
941
|
+
assertResult(result, constraintViolation(...usedSpatialCategoryViolations()), [], [spatialCategoryId, subCategoryId, filteredSubCategoryId, unusedPart], iModelDb);
|
|
942
|
+
});
|
|
943
|
+
it("reports the in-use cascaded child (not the requested parent) when the violator is a child", () => {
|
|
944
|
+
const parent = insertDefinitionElement(txn);
|
|
945
|
+
const child = insertDefinitionElement(txn, parent);
|
|
946
|
+
const user = txn.insertElement({
|
|
947
|
+
classFullName: "Generic:PhysicalObject",
|
|
948
|
+
model: physicalModelId,
|
|
949
|
+
category: spatialCategoryId,
|
|
950
|
+
code: Code.createEmpty(),
|
|
951
|
+
placement: { origin: [0, 0, 0], angles: { yaw: 0, pitch: 0, roll: 0 } },
|
|
952
|
+
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1), spatialCategoryId, undefined, undefined, child),
|
|
953
|
+
});
|
|
954
|
+
const result = txn.deleteElements([parent]);
|
|
955
|
+
assertResult(result, constraintViolation(definitionInUse(child)), [], [parent, child, user], iModelDb);
|
|
956
|
+
});
|
|
957
|
+
it("deprecated skipFKConstraintValidations flag is ignored and DefinitionInUse is still detected", () => {
|
|
958
|
+
// There is no way to bypass the definition usage check: geometry-stream references are not SQLite foreign keys, so
|
|
959
|
+
// skipping validation would silently leave dangling references. The deprecated flag is ignored.
|
|
960
|
+
const unusedPart = insertDefinitionElement(txn);
|
|
961
|
+
for (const skipFKConstraintValidations of [true, false]) {
|
|
962
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
963
|
+
const result = txn.deleteElements([geometryPartId, unusedPart], { skipFKConstraintValidations });
|
|
964
|
+
assertResult(result, constraintViolation(definitionInUse(geometryPartId)), [], [geometryPartId, unusedPart], iModelDb);
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
it("ConstraintViolation for a used Category (hard FK) leaves the txn clean", () => {
|
|
968
|
+
txn.saveChanges();
|
|
969
|
+
const result = txn.deleteElements([spatialCategoryId]);
|
|
970
|
+
assertResult(result, constraintViolation(...usedSpatialCategoryViolations()), [], [spatialCategoryId], iModelDb);
|
|
971
|
+
assert.isFalse(iModelDb[_nativeDb].hasUnsavedChanges(), "a rejected delete must not dirty the txn");
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
describe("cache coherence for cascaded definition elements", () => {
|
|
975
|
+
let txn;
|
|
976
|
+
beforeEach(() => {
|
|
977
|
+
txn = new EditTxn(iModelDb, "cache");
|
|
978
|
+
txn.start();
|
|
979
|
+
});
|
|
980
|
+
afterEach(() => {
|
|
981
|
+
txn.end("abandon");
|
|
982
|
+
});
|
|
983
|
+
it("a cached default SubCategory is not readable after its Category is deleted", () => {
|
|
984
|
+
const category = SpatialCategory.insert(txn, definitionModelId, "CachedCategory", {});
|
|
985
|
+
const defaultSubCategory = iModelDb.elements.getElement(category, SpatialCategory).myDefaultSubCategoryId();
|
|
986
|
+
assert.isDefined(iModelDb.elements.getElement(defaultSubCategory)); // prime the cache
|
|
987
|
+
executeTestCase(txn, "category with cached default subcategory", [category], [category, defaultSubCategory], success, [], false);
|
|
988
|
+
assert.isUndefined(iModelDb.elements.tryGetElementProps(defaultSubCategory), "cached default SubCategory props must not be served after deletion");
|
|
989
|
+
});
|
|
990
|
+
it("cached contents of a deleted DefinitionContainer are not readable afterwards", () => {
|
|
991
|
+
const codeSpecId = iModelDb.codeSpecs.insert(txn, "CachedContainerSpec", CodeScopeSpec.Type.Model);
|
|
992
|
+
const containerId = DefinitionContainer.insert(txn, definitionModelId, new Code({ spec: codeSpecId, scope: definitionModelId, value: "CachedContainer" }));
|
|
993
|
+
const inner = insertDefinitionElement(txn, undefined, containerId);
|
|
994
|
+
assert.isDefined(iModelDb.elements.getElement(inner));
|
|
995
|
+
assert.isDefined(iModelDb.models.getModelProps(containerId));
|
|
996
|
+
executeTestCase(txn, "container with cached contents", [containerId], [containerId, inner], success, [], false);
|
|
997
|
+
assert.isUndefined(iModelDb.elements.tryGetElementProps(inner));
|
|
998
|
+
assert.isUndefined(iModelDb.models.tryGetModelProps(containerId));
|
|
844
999
|
});
|
|
845
1000
|
});
|
|
846
1001
|
});
|
|
@@ -961,10 +1116,9 @@ describe("DeleteDefinitionElements", () => {
|
|
|
961
1116
|
it("TypeDefinitionId is NOT NULLed when deleteElements deletes the type and NULLs the reference", () => {
|
|
962
1117
|
const physTypeId = insertPhysicalType();
|
|
963
1118
|
const physObjId = insertPhysicalObjectWithType(physTypeId);
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
assert.isDefined(iModelDb.elements.tryGetElement(physObjId), "PhysicalObject should still exist");
|
|
1119
|
+
const result = txn.deleteElements([physTypeId]);
|
|
1120
|
+
// TypeDefinitionId refs do not block deleteElements - the type is deleted, the referencing object survives, and no failures are reported.
|
|
1121
|
+
assertResult(result, success, [physTypeId], [physObjId], iModelDb);
|
|
968
1122
|
assert.isUndefined(getTypeDefinitionId3d(physObjId), "TypeDefinitionId should be NULLed since the type was deleted");
|
|
969
1123
|
});
|
|
970
1124
|
});
|