@itwin/core-backend 5.14.2 → 5.14.4
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 +13 -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/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/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/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/package.json +16 -16
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElementSubModelInsert.test.d.ts","sourceRoot":"","sources":["../../../../src/test/element/ElementSubModelInsert.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
3
|
+
* See LICENSE.md in the repository root for full copyright notice.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import { expect } from "chai";
|
|
6
|
+
import { spy as sinonSpy } from "sinon";
|
|
7
|
+
import { Id64, IModelStatus } from "@itwin/core-bentley";
|
|
8
|
+
import { Code, IModel, IModelError } from "@itwin/core-common";
|
|
9
|
+
import { ClassRegistry } from "../../ClassRegistry";
|
|
10
|
+
import { withEditTxn } from "../../EditTxn";
|
|
11
|
+
import { Document, Drawing, SectionDrawing, Sheet, TemplateRecipe2d } from "../../Element";
|
|
12
|
+
import { GenericSchema } from "../../domains/GenericSchema";
|
|
13
|
+
import { SnapshotDb } from "../../IModelDb";
|
|
14
|
+
import { DefinitionModel, DocumentListModel, DrawingModel, SectionDrawingModel, SheetModel } from "../../Model";
|
|
15
|
+
import { Schema, Schemas } from "../../Schema";
|
|
16
|
+
import { IModelTestUtils } from "../IModelTestUtils";
|
|
17
|
+
class TestSubModelSchema extends Schema {
|
|
18
|
+
static get schemaName() { return "TestSubModel"; }
|
|
19
|
+
}
|
|
20
|
+
/** A Sheet subclass that overrides the hook and defers to the inherited validation via `super`. */
|
|
21
|
+
class TestSheetSpy extends Sheet {
|
|
22
|
+
static get className() { return "TestSheetSpy"; }
|
|
23
|
+
static onSubModelInsert(arg) {
|
|
24
|
+
super.onSubModelInsert(arg);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** A Sheet subclass that widens the inherited rule to also permit a DrawingModel. */
|
|
28
|
+
class TestSheetWidened extends Sheet {
|
|
29
|
+
static get className() { return "TestSheetWidened"; }
|
|
30
|
+
static get allowedSubModelClasses() { return [SheetModel, DrawingModel]; }
|
|
31
|
+
}
|
|
32
|
+
/** A Sheet subclass that replaces the hook without calling `super`, opting out of validation. */
|
|
33
|
+
class TestSheetUnvalidated extends Sheet {
|
|
34
|
+
static get className() { return "TestSheetUnvalidated"; }
|
|
35
|
+
static onSubModelInsert(_arg) { }
|
|
36
|
+
}
|
|
37
|
+
/** A domain ISubModeledElement unrelated to Sheet/Drawing that opts in by overriding only the getter. */
|
|
38
|
+
class TestRestrictedDocument extends Document {
|
|
39
|
+
static get className() { return "TestRestrictedDocument"; }
|
|
40
|
+
static get allowedSubModelClasses() { return [DrawingModel]; }
|
|
41
|
+
}
|
|
42
|
+
const testSchemaXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
43
|
+
<ECSchema schemaName="TestSubModel" alias="tsm" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
|
|
44
|
+
<ECSchemaReference name="BisCore" version="01.00.04" alias="bis"/>
|
|
45
|
+
<ECCustomAttributes>
|
|
46
|
+
<SchemaHasBehavior xmlns="BisCore.01.00.00"/>
|
|
47
|
+
</ECCustomAttributes>
|
|
48
|
+
<ECEntityClass typeName="TestSheetModel">
|
|
49
|
+
<BaseClass>bis:SheetModel</BaseClass>
|
|
50
|
+
</ECEntityClass>
|
|
51
|
+
<ECEntityClass typeName="TestSheetGenerated">
|
|
52
|
+
<BaseClass>bis:Sheet</BaseClass>
|
|
53
|
+
</ECEntityClass>
|
|
54
|
+
<ECEntityClass typeName="TestSheetSpy">
|
|
55
|
+
<BaseClass>bis:Sheet</BaseClass>
|
|
56
|
+
</ECEntityClass>
|
|
57
|
+
<ECEntityClass typeName="TestSheetWidened">
|
|
58
|
+
<BaseClass>bis:Sheet</BaseClass>
|
|
59
|
+
</ECEntityClass>
|
|
60
|
+
<ECEntityClass typeName="TestSheetUnvalidated">
|
|
61
|
+
<BaseClass>bis:Sheet</BaseClass>
|
|
62
|
+
</ECEntityClass>
|
|
63
|
+
<ECEntityClass typeName="TestHostDocument">
|
|
64
|
+
<BaseClass>bis:Document</BaseClass>
|
|
65
|
+
<BaseClass>bis:ISubModeledElement</BaseClass>
|
|
66
|
+
</ECEntityClass>
|
|
67
|
+
<ECEntityClass typeName="TestRestrictedDocument">
|
|
68
|
+
<BaseClass>bis:Document</BaseClass>
|
|
69
|
+
<BaseClass>bis:ISubModeledElement</BaseClass>
|
|
70
|
+
</ECEntityClass>
|
|
71
|
+
</ECSchema>`;
|
|
72
|
+
describe("Sub-model insert validation", () => {
|
|
73
|
+
let imodel;
|
|
74
|
+
let documentListModelId;
|
|
75
|
+
before(async () => {
|
|
76
|
+
GenericSchema.registerSchema();
|
|
77
|
+
Schemas.registerSchema(TestSubModelSchema);
|
|
78
|
+
ClassRegistry.register(TestSheetSpy, TestSubModelSchema);
|
|
79
|
+
ClassRegistry.register(TestSheetWidened, TestSubModelSchema);
|
|
80
|
+
ClassRegistry.register(TestSheetUnvalidated, TestSubModelSchema);
|
|
81
|
+
ClassRegistry.register(TestRestrictedDocument, TestSubModelSchema);
|
|
82
|
+
const iModelPath = IModelTestUtils.prepareOutputFile("SubModelInsert", "SubModelInsert.bim");
|
|
83
|
+
imodel = SnapshotDb.createEmpty(iModelPath, { rootSubject: { name: "SubModelInsertTest" } });
|
|
84
|
+
await imodel.importSchemaStrings([testSchemaXml]);
|
|
85
|
+
documentListModelId = withEditTxn(imodel, (txn) => DocumentListModel.insert(txn, SnapshotDb.rootSubjectId, "DocumentList"));
|
|
86
|
+
});
|
|
87
|
+
after(() => {
|
|
88
|
+
imodel.close();
|
|
89
|
+
Schemas.unregisterSchema(TestSubModelSchema.schemaName);
|
|
90
|
+
});
|
|
91
|
+
function insertHost(classFullName, modelId = documentListModelId) {
|
|
92
|
+
const id = withEditTxn(imodel, (txn) => txn.insertElement({ classFullName, model: modelId, code: Code.createEmpty() }));
|
|
93
|
+
expect(Id64.isValidId64(id)).to.be.true;
|
|
94
|
+
return id;
|
|
95
|
+
}
|
|
96
|
+
function insertSubModel(modelClassFullName, modeledElementId) {
|
|
97
|
+
return withEditTxn(imodel, (txn) => txn.insertModel({ classFullName: modelClassFullName, modeledElement: { id: modeledElementId } }));
|
|
98
|
+
}
|
|
99
|
+
function expectAccepted(hostClassFullName, modelClassFullName, hostModelId) {
|
|
100
|
+
const hostId = insertHost(hostClassFullName, hostModelId);
|
|
101
|
+
const modelId = insertSubModel(modelClassFullName, hostId);
|
|
102
|
+
expect(modelId).to.equal(hostId);
|
|
103
|
+
expect(imodel.models.getModel(modelId).classFullName).to.equal(modelClassFullName);
|
|
104
|
+
}
|
|
105
|
+
function expectRejected(hostClassFullName, modelClassFullName, hostModelId) {
|
|
106
|
+
const hostId = insertHost(hostClassFullName, hostModelId);
|
|
107
|
+
let error;
|
|
108
|
+
try {
|
|
109
|
+
insertSubModel(modelClassFullName, hostId);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
error = err;
|
|
113
|
+
}
|
|
114
|
+
expect(error).to.be.instanceOf(IModelError);
|
|
115
|
+
expect(error.errorNumber).to.equal(IModelStatus.WrongModel);
|
|
116
|
+
expect(error.message).to.contain("must be sub-modeled by");
|
|
117
|
+
expect(imodel.models.tryGetModel(hostId)).to.be.undefined;
|
|
118
|
+
}
|
|
119
|
+
describe("accepts", () => {
|
|
120
|
+
it("Sheet + SheetModel", () => expectAccepted(Sheet.classFullName, SheetModel.classFullName));
|
|
121
|
+
it("Drawing + DrawingModel", () => expectAccepted(Drawing.classFullName, DrawingModel.classFullName));
|
|
122
|
+
it("SectionDrawing + DrawingModel", () => expectAccepted(SectionDrawing.classFullName, DrawingModel.classFullName));
|
|
123
|
+
it("SectionDrawing + SectionDrawingModel", () => expectAccepted(SectionDrawing.classFullName, SectionDrawingModel.classFullName));
|
|
124
|
+
it("SectionDrawing + Generic:GraphicalModel3d", () => expectAccepted(SectionDrawing.classFullName, "Generic:GraphicalModel3d"));
|
|
125
|
+
it("TemplateRecipe2d + DrawingModel", () => expectAccepted(TemplateRecipe2d.classFullName, DrawingModel.classFullName, IModel.dictionaryId));
|
|
126
|
+
it("Sheet + SheetModel subclass without a JS class", () => expectAccepted(Sheet.classFullName, "TestSubModel:TestSheetModel"));
|
|
127
|
+
it("no allowedSubModelClasses → any model accepted", () => {
|
|
128
|
+
expectAccepted("TestSubModel:TestHostDocument", SheetModel.classFullName);
|
|
129
|
+
expectAccepted("TestSubModel:TestHostDocument", DrawingModel.classFullName);
|
|
130
|
+
});
|
|
131
|
+
it("Sheet subclass that widens allowedSubModelClasses + DrawingModel", () => expectAccepted(TestSheetWidened.classFullName, DrawingModel.classFullName));
|
|
132
|
+
it("Sheet subclass that overrides onSubModelInsert without super + DrawingModel", () => expectAccepted(TestSheetUnvalidated.classFullName, DrawingModel.classFullName));
|
|
133
|
+
it("Document subclass with allowedSubModelClasses + DrawingModel", () => expectAccepted(TestRestrictedDocument.classFullName, DrawingModel.classFullName));
|
|
134
|
+
});
|
|
135
|
+
describe("rejects with IModelStatus.WrongModel", () => {
|
|
136
|
+
it("Sheet + DrawingModel", () => expectRejected(Sheet.classFullName, DrawingModel.classFullName));
|
|
137
|
+
it("Drawing + SheetModel", () => expectRejected(Drawing.classFullName, SheetModel.classFullName));
|
|
138
|
+
it("SectionDrawing + SheetModel", () => expectRejected(SectionDrawing.classFullName, SheetModel.classFullName));
|
|
139
|
+
it("TemplateRecipe2d + SheetModel", () => expectRejected(TemplateRecipe2d.classFullName, SheetModel.classFullName, IModel.dictionaryId));
|
|
140
|
+
it("Sheet subclass without a JS class + DrawingModel", () => expectRejected("TestSubModel:TestSheetGenerated", DrawingModel.classFullName));
|
|
141
|
+
it("Document subclass with allowedSubModelClasses + SheetModel", () => expectRejected(TestRestrictedDocument.classFullName, SheetModel.classFullName));
|
|
142
|
+
});
|
|
143
|
+
it("invokes an overriding subclass hook that defers to the inherited validation", () => {
|
|
144
|
+
const spy = sinonSpy(TestSheetSpy, "onSubModelInsert");
|
|
145
|
+
try {
|
|
146
|
+
expectAccepted(TestSheetSpy.classFullName, SheetModel.classFullName);
|
|
147
|
+
expect(spy.calledOnce).to.be.true;
|
|
148
|
+
expect(spy.getCall(0).args[0].subModelProps.classFullName).to.equal(SheetModel.classFullName);
|
|
149
|
+
expectRejected(TestSheetSpy.classFullName, DrawingModel.classFullName);
|
|
150
|
+
expect(spy.calledTwice).to.be.true;
|
|
151
|
+
expect(spy.getCall(1).threw()).to.be.true;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
spy.restore();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
describe("missing required behavior", () => {
|
|
158
|
+
it("rejects sub-model insertion", () => {
|
|
159
|
+
const hostId = insertHost(TestRestrictedDocument.classFullName);
|
|
160
|
+
Schemas.unregisterSchema(TestSubModelSchema.schemaName);
|
|
161
|
+
try {
|
|
162
|
+
let error;
|
|
163
|
+
try {
|
|
164
|
+
insertSubModel(DefinitionModel.classFullName, hostId);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
error = err;
|
|
168
|
+
}
|
|
169
|
+
expect(error).to.be.instanceOf(IModelError);
|
|
170
|
+
expect(error.errorNumber).to.equal(IModelStatus.WrongHandler);
|
|
171
|
+
expect(imodel.models.tryGetModel(hostId)).to.be.undefined;
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
Schemas.registerSchema(TestSubModelSchema);
|
|
175
|
+
ClassRegistry.register(TestSheetSpy, TestSubModelSchema);
|
|
176
|
+
ClassRegistry.register(TestSheetWidened, TestSubModelSchema);
|
|
177
|
+
ClassRegistry.register(TestSheetUnvalidated, TestSubModelSchema);
|
|
178
|
+
ClassRegistry.register(TestRestrictedDocument, TestSubModelSchema);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
//# sourceMappingURL=ElementSubModelInsert.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElementSubModelInsert.test.js","sourceRoot":"","sources":["../../../../src/test/element/ElementSubModelInsert.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the repository root for full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\nimport { expect } from \"chai\";\r\nimport { spy as sinonSpy } from \"sinon\";\r\nimport { Id64, Id64String, IModelStatus } from \"@itwin/core-bentley\";\r\nimport { Code, IModel, IModelError } from \"@itwin/core-common\";\r\nimport { ClassRegistry } from \"../../ClassRegistry\";\r\nimport { withEditTxn } from \"../../EditTxn\";\r\nimport { Document, Drawing, OnSubModelPropsArg, SectionDrawing, Sheet, TemplateRecipe2d } from \"../../Element\";\r\nimport { EntityClassType } from \"../../Entity\";\r\nimport { GenericSchema } from \"../../domains/GenericSchema\";\r\nimport { SnapshotDb } from \"../../IModelDb\";\r\nimport { DefinitionModel, DocumentListModel, DrawingModel, Model, SectionDrawingModel, SheetModel } from \"../../Model\";\r\nimport { Schema, Schemas } from \"../../Schema\";\r\nimport { IModelTestUtils } from \"../IModelTestUtils\";\r\n\r\nclass TestSubModelSchema extends Schema {\r\n public static override get schemaName(): string { return \"TestSubModel\"; }\r\n}\r\n\r\n/** A Sheet subclass that overrides the hook and defers to the inherited validation via `super`. */\r\nclass TestSheetSpy extends Sheet {\r\n public static override get className(): string { return \"TestSheetSpy\"; }\r\n public static override onSubModelInsert(arg: OnSubModelPropsArg): void {\r\n super.onSubModelInsert(arg);\r\n }\r\n}\r\n\r\n/** A Sheet subclass that widens the inherited rule to also permit a DrawingModel. */\r\nclass TestSheetWidened extends Sheet {\r\n public static override get className(): string { return \"TestSheetWidened\"; }\r\n protected static override get allowedSubModelClasses(): Array<EntityClassType<Model>> { return [SheetModel, DrawingModel]; }\r\n}\r\n\r\n/** A Sheet subclass that replaces the hook without calling `super`, opting out of validation. */\r\nclass TestSheetUnvalidated extends Sheet {\r\n public static override get className(): string { return \"TestSheetUnvalidated\"; }\r\n public static override onSubModelInsert(_arg: OnSubModelPropsArg): void { }\r\n}\r\n\r\n/** A domain ISubModeledElement unrelated to Sheet/Drawing that opts in by overriding only the getter. */\r\nclass TestRestrictedDocument extends Document {\r\n public static override get className(): string { return \"TestRestrictedDocument\"; }\r\n protected static override get allowedSubModelClasses(): Array<EntityClassType<Model>> { return [DrawingModel]; }\r\n}\r\n\r\nconst testSchemaXml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<ECSchema schemaName=\"TestSubModel\" alias=\"tsm\" version=\"01.00.00\" xmlns=\"http://www.bentley.com/schemas/Bentley.ECXML.3.2\">\r\n <ECSchemaReference name=\"BisCore\" version=\"01.00.04\" alias=\"bis\"/>\r\n <ECCustomAttributes>\r\n <SchemaHasBehavior xmlns=\"BisCore.01.00.00\"/>\r\n </ECCustomAttributes>\r\n <ECEntityClass typeName=\"TestSheetModel\">\r\n <BaseClass>bis:SheetModel</BaseClass>\r\n </ECEntityClass>\r\n <ECEntityClass typeName=\"TestSheetGenerated\">\r\n <BaseClass>bis:Sheet</BaseClass>\r\n </ECEntityClass>\r\n <ECEntityClass typeName=\"TestSheetSpy\">\r\n <BaseClass>bis:Sheet</BaseClass>\r\n </ECEntityClass>\r\n <ECEntityClass typeName=\"TestSheetWidened\">\r\n <BaseClass>bis:Sheet</BaseClass>\r\n </ECEntityClass>\r\n <ECEntityClass typeName=\"TestSheetUnvalidated\">\r\n <BaseClass>bis:Sheet</BaseClass>\r\n </ECEntityClass>\r\n <ECEntityClass typeName=\"TestHostDocument\">\r\n <BaseClass>bis:Document</BaseClass>\r\n <BaseClass>bis:ISubModeledElement</BaseClass>\r\n </ECEntityClass>\r\n <ECEntityClass typeName=\"TestRestrictedDocument\">\r\n <BaseClass>bis:Document</BaseClass>\r\n <BaseClass>bis:ISubModeledElement</BaseClass>\r\n </ECEntityClass>\r\n</ECSchema>`;\r\n\r\ndescribe(\"Sub-model insert validation\", () => {\r\n let imodel: SnapshotDb;\r\n let documentListModelId: Id64String;\r\n\r\n before(async () => {\r\n GenericSchema.registerSchema();\r\n Schemas.registerSchema(TestSubModelSchema);\r\n ClassRegistry.register(TestSheetSpy, TestSubModelSchema);\r\n ClassRegistry.register(TestSheetWidened, TestSubModelSchema);\r\n ClassRegistry.register(TestSheetUnvalidated, TestSubModelSchema);\r\n ClassRegistry.register(TestRestrictedDocument, TestSubModelSchema);\r\n\r\n const iModelPath = IModelTestUtils.prepareOutputFile(\"SubModelInsert\", \"SubModelInsert.bim\");\r\n imodel = SnapshotDb.createEmpty(iModelPath, { rootSubject: { name: \"SubModelInsertTest\" } });\r\n await imodel.importSchemaStrings([testSchemaXml]);\r\n documentListModelId = withEditTxn(imodel, (txn) => DocumentListModel.insert(txn, SnapshotDb.rootSubjectId, \"DocumentList\"));\r\n });\r\n\r\n after(() => {\r\n imodel.close();\r\n Schemas.unregisterSchema(TestSubModelSchema.schemaName);\r\n });\r\n\r\n function insertHost(classFullName: string, modelId: Id64String = documentListModelId): Id64String {\r\n const id = withEditTxn(imodel, (txn) => txn.insertElement({ classFullName, model: modelId, code: Code.createEmpty() }));\r\n expect(Id64.isValidId64(id)).to.be.true;\r\n return id;\r\n }\r\n\r\n function insertSubModel(modelClassFullName: string, modeledElementId: Id64String): Id64String {\r\n return withEditTxn(imodel, (txn) => txn.insertModel({ classFullName: modelClassFullName, modeledElement: { id: modeledElementId } }));\r\n }\r\n\r\n function expectAccepted(hostClassFullName: string, modelClassFullName: string, hostModelId?: Id64String): void {\r\n const hostId = insertHost(hostClassFullName, hostModelId);\r\n const modelId = insertSubModel(modelClassFullName, hostId);\r\n expect(modelId).to.equal(hostId);\r\n expect(imodel.models.getModel(modelId).classFullName).to.equal(modelClassFullName);\r\n }\r\n\r\n function expectRejected(hostClassFullName: string, modelClassFullName: string, hostModelId?: Id64String): void {\r\n const hostId = insertHost(hostClassFullName, hostModelId);\r\n let error: unknown;\r\n try {\r\n insertSubModel(modelClassFullName, hostId);\r\n } catch (err) {\r\n error = err;\r\n }\r\n\r\n expect(error).to.be.instanceOf(IModelError);\r\n expect((error as IModelError).errorNumber).to.equal(IModelStatus.WrongModel);\r\n expect((error as IModelError).message).to.contain(\"must be sub-modeled by\");\r\n expect(imodel.models.tryGetModel(hostId)).to.be.undefined;\r\n }\r\n\r\n describe(\"accepts\", () => {\r\n it(\"Sheet + SheetModel\", () => expectAccepted(Sheet.classFullName, SheetModel.classFullName));\r\n it(\"Drawing + DrawingModel\", () => expectAccepted(Drawing.classFullName, DrawingModel.classFullName));\r\n it(\"SectionDrawing + DrawingModel\", () => expectAccepted(SectionDrawing.classFullName, DrawingModel.classFullName));\r\n it(\"SectionDrawing + SectionDrawingModel\", () => expectAccepted(SectionDrawing.classFullName, SectionDrawingModel.classFullName));\r\n it(\"SectionDrawing + Generic:GraphicalModel3d\", () => expectAccepted(SectionDrawing.classFullName, \"Generic:GraphicalModel3d\"));\r\n it(\"TemplateRecipe2d + DrawingModel\", () => expectAccepted(TemplateRecipe2d.classFullName, DrawingModel.classFullName, IModel.dictionaryId));\r\n it(\"Sheet + SheetModel subclass without a JS class\", () => expectAccepted(Sheet.classFullName, \"TestSubModel:TestSheetModel\"));\r\n it(\"no allowedSubModelClasses → any model accepted\", () => {\r\n expectAccepted(\"TestSubModel:TestHostDocument\", SheetModel.classFullName);\r\n expectAccepted(\"TestSubModel:TestHostDocument\", DrawingModel.classFullName);\r\n });\r\n it(\"Sheet subclass that widens allowedSubModelClasses + DrawingModel\", () => expectAccepted(TestSheetWidened.classFullName, DrawingModel.classFullName));\r\n it(\"Sheet subclass that overrides onSubModelInsert without super + DrawingModel\", () => expectAccepted(TestSheetUnvalidated.classFullName, DrawingModel.classFullName));\r\n it(\"Document subclass with allowedSubModelClasses + DrawingModel\", () => expectAccepted(TestRestrictedDocument.classFullName, DrawingModel.classFullName));\r\n });\r\n\r\n describe(\"rejects with IModelStatus.WrongModel\", () => {\r\n it(\"Sheet + DrawingModel\", () => expectRejected(Sheet.classFullName, DrawingModel.classFullName));\r\n it(\"Drawing + SheetModel\", () => expectRejected(Drawing.classFullName, SheetModel.classFullName));\r\n it(\"SectionDrawing + SheetModel\", () => expectRejected(SectionDrawing.classFullName, SheetModel.classFullName));\r\n it(\"TemplateRecipe2d + SheetModel\", () => expectRejected(TemplateRecipe2d.classFullName, SheetModel.classFullName, IModel.dictionaryId));\r\n it(\"Sheet subclass without a JS class + DrawingModel\", () => expectRejected(\"TestSubModel:TestSheetGenerated\", DrawingModel.classFullName));\r\n it(\"Document subclass with allowedSubModelClasses + SheetModel\", () => expectRejected(TestRestrictedDocument.classFullName, SheetModel.classFullName));\r\n });\r\n\r\n it(\"invokes an overriding subclass hook that defers to the inherited validation\", () => {\r\n const spy = sinonSpy(TestSheetSpy, \"onSubModelInsert\");\r\n try {\r\n expectAccepted(TestSheetSpy.classFullName, SheetModel.classFullName);\r\n expect(spy.calledOnce).to.be.true;\r\n expect(spy.getCall(0).args[0].subModelProps.classFullName).to.equal(SheetModel.classFullName);\r\n\r\n expectRejected(TestSheetSpy.classFullName, DrawingModel.classFullName);\r\n expect(spy.calledTwice).to.be.true;\r\n expect(spy.getCall(1).threw()).to.be.true;\r\n } finally {\r\n spy.restore();\r\n }\r\n });\r\n\r\n describe(\"missing required behavior\", () => {\r\n it(\"rejects sub-model insertion\", () => {\r\n const hostId = insertHost(TestRestrictedDocument.classFullName);\r\n Schemas.unregisterSchema(TestSubModelSchema.schemaName);\r\n\r\n try {\r\n let error: unknown;\r\n try {\r\n insertSubModel(DefinitionModel.classFullName, hostId);\r\n } catch (err) {\r\n error = err;\r\n }\r\n\r\n expect(error).to.be.instanceOf(IModelError);\r\n expect((error as IModelError).errorNumber).to.equal(IModelStatus.WrongHandler);\r\n expect(imodel.models.tryGetModel(hostId)).to.be.undefined;\r\n } finally {\r\n Schemas.registerSchema(TestSubModelSchema);\r\n ClassRegistry.register(TestSheetSpy, TestSubModelSchema);\r\n ClassRegistry.register(TestSheetWidened, TestSubModelSchema);\r\n ClassRegistry.register(TestSheetUnvalidated, TestSubModelSchema);\r\n ClassRegistry.register(TestRestrictedDocument, TestSubModelSchema);\r\n }\r\n });\r\n });\r\n});\r\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaImportOrphanColumns.test.d.ts","sourceRoot":"","sources":["../../../../src/test/hubaccess/SchemaImportOrphanColumns.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
3
|
+
* See LICENSE.md in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import { DbResult } from "@itwin/core-bentley";
|
|
6
|
+
import * as chai from "chai";
|
|
7
|
+
import * as chaiAsPromised from "chai-as-promised";
|
|
8
|
+
import { HubWrappers, KnownTestLocations } from "..";
|
|
9
|
+
import { HubMock } from "../../internal/HubMock";
|
|
10
|
+
chai.use(chaiAsPromised);
|
|
11
|
+
/**
|
|
12
|
+
* Repro for Bug 2115705: "DbTable 'bis_ElementMultiAspect' has N non-virtual columns, but the physical table has M columns."
|
|
13
|
+
*
|
|
14
|
+
* No semantic rebase, schema sync or dynamic schema involved. A schema adds an ElementMultiAspect subclass wide enough to
|
|
15
|
+
* ALTER TABLE ADD shared columns to bis_ElementMultiAspect. discardChanges() then reverses the EcSchema txn (ec_* rows)
|
|
16
|
+
* but skips the Ddl txn (TxnManager::ApplyTxnChanges returns early for TxnType::Ddl), so the physical columns remain
|
|
17
|
+
* with no ec_Column rows. The next regular schema import used to fail the strict column-count check; it must now succeed.
|
|
18
|
+
*
|
|
19
|
+
* The dynamic-schema tests show that deleting properties/classes (allowed for dynamic schemas) is not a cause.
|
|
20
|
+
*/
|
|
21
|
+
describe("Schema import discard leaves orphan physical columns (Bug 2115705)", function () {
|
|
22
|
+
this.timeout(90000);
|
|
23
|
+
const tableName = "bis_ElementMultiAspect";
|
|
24
|
+
const extraColumns = 6;
|
|
25
|
+
let briefcase;
|
|
26
|
+
before(() => HubMock.startup("SchemaImportOrphanColumns", KnownTestLocations.outputDir));
|
|
27
|
+
after(() => HubMock.shutdown());
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
briefcase?.close();
|
|
30
|
+
briefcase = undefined;
|
|
31
|
+
});
|
|
32
|
+
const physicalColumns = (db) => db.withSqliteStatement(`PRAGMA table_info([${tableName}])`, (stmt) => {
|
|
33
|
+
const names = [];
|
|
34
|
+
while (stmt.step() === DbResult.BE_SQLITE_ROW)
|
|
35
|
+
names.push(stmt.getValue(1).getString());
|
|
36
|
+
return names;
|
|
37
|
+
});
|
|
38
|
+
const mappedNonVirtualColumnCount = (db) => db.withSqliteStatement(`SELECT COUNT(*) FROM ec_Column c JOIN ec_Table t ON t.Id = c.TableId WHERE t.Name = '${tableName}' AND c.IsVirtual = 0`, (stmt) => {
|
|
39
|
+
chai.expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);
|
|
40
|
+
return stmt.getValue(0).getInteger();
|
|
41
|
+
});
|
|
42
|
+
const schemaWithWideMultiAspect = (propCount) => `<?xml version="1.0" encoding="UTF-8"?>
|
|
43
|
+
<ECSchema schemaName="WideAspect" alias="wa" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
|
|
44
|
+
<ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
|
|
45
|
+
<ECEntityClass typeName="WideMultiAspect">
|
|
46
|
+
<BaseClass>bis:ElementMultiAspect</BaseClass>
|
|
47
|
+
${Array.from({ length: propCount }, (_, i) => `<ECProperty propertyName="P${i + 1}" typeName="string"/>`).join("\n ")}
|
|
48
|
+
</ECEntityClass>
|
|
49
|
+
</ECSchema>`;
|
|
50
|
+
const unrelatedSchema = `<?xml version="1.0" encoding="UTF-8"?>
|
|
51
|
+
<ECSchema schemaName="Unrelated" alias="unr" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
|
|
52
|
+
<ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
|
|
53
|
+
<ECEntityClass typeName="Thing">
|
|
54
|
+
<BaseClass>bis:GraphicalElement2d</BaseClass>
|
|
55
|
+
<ECProperty propertyName="Name" typeName="string"/>
|
|
56
|
+
</ECEntityClass>
|
|
57
|
+
</ECSchema>`;
|
|
58
|
+
const dynamicSchema = (version, propCount) => `<?xml version="1.0" encoding="UTF-8"?>
|
|
59
|
+
<ECSchema schemaName="DynTest" alias="dyn" version="${version}" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
|
|
60
|
+
<ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
|
|
61
|
+
<ECSchemaReference name="CoreCustomAttributes" version="01.00.03" alias="CoreCA"/>
|
|
62
|
+
<ECCustomAttributes>
|
|
63
|
+
<DynamicSchema xmlns="CoreCustomAttributes.01.00.03"/>
|
|
64
|
+
</ECCustomAttributes>
|
|
65
|
+
${propCount === undefined ? "" : `<ECEntityClass typeName="WideMultiAspect">
|
|
66
|
+
<BaseClass>bis:ElementMultiAspect</BaseClass>
|
|
67
|
+
${Array.from({ length: propCount }, (_, i) => `<ECProperty propertyName="P${i + 1}" typeName="string"/>`).join("\n ")}
|
|
68
|
+
</ECEntityClass>`}
|
|
69
|
+
</ECSchema>`;
|
|
70
|
+
it("deleting dynamic-schema properties/classes keeps ec_Column and physical columns in sync", async () => {
|
|
71
|
+
const accessToken = "user1";
|
|
72
|
+
const iModelId = await HubMock.createNewIModel({ accessToken, iTwinId: HubMock.iTwinId, iModelName: "DynDelete" });
|
|
73
|
+
briefcase = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId: HubMock.iTwinId, iModelId });
|
|
74
|
+
const db = briefcase;
|
|
75
|
+
const assertInSync = (step) => chai.expect(physicalColumns(db).length).to.equal(mappedNonVirtualColumnCount(db), `out of sync after ${step}`);
|
|
76
|
+
const sharedColumnCount = physicalColumns(db).filter((name) => /^ps\d+$/i.test(name)).length;
|
|
77
|
+
const wide = sharedColumnCount + extraColumns;
|
|
78
|
+
await db.importSchemaStrings([dynamicSchema("01.00.00", wide)]);
|
|
79
|
+
assertInSync("wide import");
|
|
80
|
+
await db.importSchemaStrings([dynamicSchema("02.00.00", 1)]); // delete P2..Pn
|
|
81
|
+
assertInSync("property deletion");
|
|
82
|
+
await db.importSchemaStrings([dynamicSchema("03.00.00", undefined)]); // delete the class
|
|
83
|
+
assertInSync("class deletion");
|
|
84
|
+
await db.importSchemaStrings([unrelatedSchema]); // strict validation passes
|
|
85
|
+
assertInSync("unrelated import");
|
|
86
|
+
});
|
|
87
|
+
it("dynamic-schema deletions pushed and pulled keep the pulling briefcase in sync", async () => {
|
|
88
|
+
const iModelId = await HubMock.createNewIModel({ accessToken: "user1", iTwinId: HubMock.iTwinId, iModelName: "DynDeletePull" });
|
|
89
|
+
const writer = await HubWrappers.downloadAndOpenBriefcase({ accessToken: "user1", iTwinId: HubMock.iTwinId, iModelId });
|
|
90
|
+
const reader = await HubWrappers.downloadAndOpenBriefcase({ accessToken: "user2", iTwinId: HubMock.iTwinId, iModelId });
|
|
91
|
+
try {
|
|
92
|
+
const assertInSync = (db, step) => chai.expect(physicalColumns(db).length).to.equal(mappedNonVirtualColumnCount(db), `${db === writer ? "writer" : "reader"} out of sync after ${step}`);
|
|
93
|
+
const wide = physicalColumns(writer).filter((name) => /^ps\d+$/i.test(name)).length + extraColumns;
|
|
94
|
+
const steps = [
|
|
95
|
+
["wide import", dynamicSchema("01.00.00", wide)],
|
|
96
|
+
["property deletion", dynamicSchema("02.00.00", 1)],
|
|
97
|
+
["class deletion", dynamicSchema("03.00.00", undefined)],
|
|
98
|
+
];
|
|
99
|
+
for (const [step, xml] of steps) {
|
|
100
|
+
await writer.importSchemaStrings([xml]);
|
|
101
|
+
await writer.pushChanges({ description: step });
|
|
102
|
+
assertInSync(writer, step);
|
|
103
|
+
await reader.pullChanges();
|
|
104
|
+
assertInSync(reader, step);
|
|
105
|
+
}
|
|
106
|
+
// Strict validation on the pulling briefcase passes.
|
|
107
|
+
await reader.importSchemaStrings([unrelatedSchema]);
|
|
108
|
+
assertInSync(reader, "unrelated import");
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
writer.close();
|
|
112
|
+
reader.close();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
it("schema import succeeds after discarding a schema import that added columns", async () => {
|
|
116
|
+
const accessToken = "user1";
|
|
117
|
+
const iModelId = await HubMock.createNewIModel({ accessToken, iTwinId: HubMock.iTwinId, iModelName: "DiscardOrphan" });
|
|
118
|
+
briefcase = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId: HubMock.iTwinId, iModelId });
|
|
119
|
+
const baselineColumns = physicalColumns(briefcase);
|
|
120
|
+
chai.expect(baselineColumns.length).to.equal(mappedNonVirtualColumnCount(briefcase), "baseline must be consistent");
|
|
121
|
+
// Plain (non-dynamic) schema whose aspect needs more shared columns than the table has -> ALTER TABLE ADD COLUMN.
|
|
122
|
+
const sharedColumnCount = baselineColumns.filter((name) => /^ps\d+$/i.test(name)).length;
|
|
123
|
+
await briefcase.importSchemaStrings([schemaWithWideMultiAspect(sharedColumnCount + extraColumns)]);
|
|
124
|
+
chai.expect(physicalColumns(briefcase).length).to.equal(baselineColumns.length + extraColumns, "import should add physical columns");
|
|
125
|
+
chai.expect(mappedNonVirtualColumnCount(briefcase)).to.equal(baselineColumns.length + extraColumns);
|
|
126
|
+
// Abandon the un-pushed schema import: ec_* is reverted, the DDL is not.
|
|
127
|
+
await briefcase.discardChanges();
|
|
128
|
+
const schemaCount = briefcase.withSqliteStatement("SELECT COUNT(*) FROM ec_Schema WHERE Name = 'WideAspect'", (stmt) => {
|
|
129
|
+
stmt.step();
|
|
130
|
+
return stmt.getValue(0).getInteger();
|
|
131
|
+
});
|
|
132
|
+
chai.expect(schemaCount).to.equal(0, "schema should be gone after discardChanges");
|
|
133
|
+
const mapped = mappedNonVirtualColumnCount(briefcase);
|
|
134
|
+
const physical = physicalColumns(briefcase).length;
|
|
135
|
+
chai.expect(mapped).to.equal(baselineColumns.length);
|
|
136
|
+
chai.expect(physical - mapped).to.equal(extraColumns, `orphan columns left in ${tableName}`);
|
|
137
|
+
// A subsequent regular schema import must tolerate the unused shared columns left behind.
|
|
138
|
+
await briefcase.importSchemaStrings([unrelatedSchema]);
|
|
139
|
+
chai.expect(physicalColumns(briefcase).length).to.equal(physical);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=SchemaImportOrphanColumns.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaImportOrphanColumns.test.js","sourceRoot":"","sources":["../../../../src/test/hubaccess/SchemaImportOrphanColumns.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license information.\r\n*--------------------------------------------------------------------------------------------*/\r\nimport { DbResult } from \"@itwin/core-bentley\";\r\nimport * as chai from \"chai\";\r\nimport * as chaiAsPromised from \"chai-as-promised\";\r\nimport { Suite } from \"mocha\";\r\nimport { HubWrappers, KnownTestLocations } from \"..\";\r\nimport { BriefcaseDb } from \"../../core-backend\";\r\nimport { HubMock } from \"../../internal/HubMock\";\r\n\r\nchai.use(chaiAsPromised);\r\n\r\n/**\r\n * Repro for Bug 2115705: \"DbTable 'bis_ElementMultiAspect' has N non-virtual columns, but the physical table has M columns.\"\r\n *\r\n * No semantic rebase, schema sync or dynamic schema involved. A schema adds an ElementMultiAspect subclass wide enough to\r\n * ALTER TABLE ADD shared columns to bis_ElementMultiAspect. discardChanges() then reverses the EcSchema txn (ec_* rows)\r\n * but skips the Ddl txn (TxnManager::ApplyTxnChanges returns early for TxnType::Ddl), so the physical columns remain\r\n * with no ec_Column rows. The next regular schema import used to fail the strict column-count check; it must now succeed.\r\n *\r\n * The dynamic-schema tests show that deleting properties/classes (allowed for dynamic schemas) is not a cause.\r\n */\r\ndescribe(\"Schema import discard leaves orphan physical columns (Bug 2115705)\", function (this: Suite) {\r\n this.timeout(90000);\r\n const tableName = \"bis_ElementMultiAspect\";\r\n const extraColumns = 6;\r\n let briefcase: BriefcaseDb | undefined;\r\n\r\n before(() => HubMock.startup(\"SchemaImportOrphanColumns\", KnownTestLocations.outputDir));\r\n after(() => HubMock.shutdown());\r\n afterEach(() => {\r\n briefcase?.close();\r\n briefcase = undefined;\r\n });\r\n\r\n const physicalColumns = (db: BriefcaseDb): string[] =>\r\n db.withSqliteStatement(`PRAGMA table_info([${tableName}])`, (stmt) => {\r\n const names: string[] = [];\r\n while (stmt.step() === DbResult.BE_SQLITE_ROW)\r\n names.push(stmt.getValue(1).getString());\r\n return names;\r\n });\r\n\r\n const mappedNonVirtualColumnCount = (db: BriefcaseDb): number =>\r\n db.withSqliteStatement(\r\n `SELECT COUNT(*) FROM ec_Column c JOIN ec_Table t ON t.Id = c.TableId WHERE t.Name = '${tableName}' AND c.IsVirtual = 0`,\r\n (stmt) => {\r\n chai.expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);\r\n return stmt.getValue(0).getInteger();\r\n });\r\n\r\n const schemaWithWideMultiAspect = (propCount: number) => `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n <ECSchema schemaName=\"WideAspect\" alias=\"wa\" version=\"01.00.00\" xmlns=\"http://www.bentley.com/schemas/Bentley.ECXML.3.2\">\r\n <ECSchemaReference name=\"BisCore\" version=\"01.00.00\" alias=\"bis\"/>\r\n <ECEntityClass typeName=\"WideMultiAspect\">\r\n <BaseClass>bis:ElementMultiAspect</BaseClass>\r\n ${Array.from({ length: propCount }, (_, i) => `<ECProperty propertyName=\"P${i + 1}\" typeName=\"string\"/>`).join(\"\\n \")}\r\n </ECEntityClass>\r\n </ECSchema>`;\r\n\r\n const unrelatedSchema = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n <ECSchema schemaName=\"Unrelated\" alias=\"unr\" version=\"01.00.00\" xmlns=\"http://www.bentley.com/schemas/Bentley.ECXML.3.2\">\r\n <ECSchemaReference name=\"BisCore\" version=\"01.00.00\" alias=\"bis\"/>\r\n <ECEntityClass typeName=\"Thing\">\r\n <BaseClass>bis:GraphicalElement2d</BaseClass>\r\n <ECProperty propertyName=\"Name\" typeName=\"string\"/>\r\n </ECEntityClass>\r\n </ECSchema>`;\r\n\r\n const dynamicSchema = (version: string, propCount: number | undefined) => `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n <ECSchema schemaName=\"DynTest\" alias=\"dyn\" version=\"${version}\" xmlns=\"http://www.bentley.com/schemas/Bentley.ECXML.3.2\">\r\n <ECSchemaReference name=\"BisCore\" version=\"01.00.00\" alias=\"bis\"/>\r\n <ECSchemaReference name=\"CoreCustomAttributes\" version=\"01.00.03\" alias=\"CoreCA\"/>\r\n <ECCustomAttributes>\r\n <DynamicSchema xmlns=\"CoreCustomAttributes.01.00.03\"/>\r\n </ECCustomAttributes>\r\n ${propCount === undefined ? \"\" : `<ECEntityClass typeName=\"WideMultiAspect\">\r\n <BaseClass>bis:ElementMultiAspect</BaseClass>\r\n ${Array.from({ length: propCount }, (_, i) => `<ECProperty propertyName=\"P${i + 1}\" typeName=\"string\"/>`).join(\"\\n \")}\r\n </ECEntityClass>`}\r\n </ECSchema>`;\r\n\r\n it(\"deleting dynamic-schema properties/classes keeps ec_Column and physical columns in sync\", async () => {\r\n const accessToken = \"user1\";\r\n const iModelId = await HubMock.createNewIModel({ accessToken, iTwinId: HubMock.iTwinId, iModelName: \"DynDelete\" });\r\n briefcase = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId: HubMock.iTwinId, iModelId });\r\n const db = briefcase;\r\n const assertInSync = (step: string) =>\r\n chai.expect(physicalColumns(db).length).to.equal(mappedNonVirtualColumnCount(db), `out of sync after ${step}`);\r\n\r\n const sharedColumnCount = physicalColumns(db).filter((name) => /^ps\\d+$/i.test(name)).length;\r\n const wide = sharedColumnCount + extraColumns;\r\n\r\n await db.importSchemaStrings([dynamicSchema(\"01.00.00\", wide)]);\r\n assertInSync(\"wide import\");\r\n await db.importSchemaStrings([dynamicSchema(\"02.00.00\", 1)]); // delete P2..Pn\r\n assertInSync(\"property deletion\");\r\n await db.importSchemaStrings([dynamicSchema(\"03.00.00\", undefined)]); // delete the class\r\n assertInSync(\"class deletion\");\r\n await db.importSchemaStrings([unrelatedSchema]); // strict validation passes\r\n assertInSync(\"unrelated import\");\r\n });\r\n\r\n it(\"dynamic-schema deletions pushed and pulled keep the pulling briefcase in sync\", async () => {\r\n const iModelId = await HubMock.createNewIModel({ accessToken: \"user1\", iTwinId: HubMock.iTwinId, iModelName: \"DynDeletePull\" });\r\n const writer = await HubWrappers.downloadAndOpenBriefcase({ accessToken: \"user1\", iTwinId: HubMock.iTwinId, iModelId });\r\n const reader = await HubWrappers.downloadAndOpenBriefcase({ accessToken: \"user2\", iTwinId: HubMock.iTwinId, iModelId });\r\n try {\r\n const assertInSync = (db: BriefcaseDb, step: string) =>\r\n chai.expect(physicalColumns(db).length).to.equal(mappedNonVirtualColumnCount(db), `${db === writer ? \"writer\" : \"reader\"} out of sync after ${step}`);\r\n\r\n const wide = physicalColumns(writer).filter((name) => /^ps\\d+$/i.test(name)).length + extraColumns;\r\n const steps: Array<[string, string]> = [\r\n [\"wide import\", dynamicSchema(\"01.00.00\", wide)],\r\n [\"property deletion\", dynamicSchema(\"02.00.00\", 1)],\r\n [\"class deletion\", dynamicSchema(\"03.00.00\", undefined)],\r\n ];\r\n for (const [step, xml] of steps) {\r\n await writer.importSchemaStrings([xml]);\r\n await writer.pushChanges({ description: step });\r\n assertInSync(writer, step);\r\n await reader.pullChanges();\r\n assertInSync(reader, step);\r\n }\r\n\r\n // Strict validation on the pulling briefcase passes.\r\n await reader.importSchemaStrings([unrelatedSchema]);\r\n assertInSync(reader, \"unrelated import\");\r\n } finally {\r\n writer.close();\r\n reader.close();\r\n }\r\n });\r\n\r\n it(\"schema import succeeds after discarding a schema import that added columns\", async () => {\r\n const accessToken = \"user1\";\r\n const iModelId = await HubMock.createNewIModel({ accessToken, iTwinId: HubMock.iTwinId, iModelName: \"DiscardOrphan\" });\r\n briefcase = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId: HubMock.iTwinId, iModelId });\r\n\r\n const baselineColumns = physicalColumns(briefcase);\r\n chai.expect(baselineColumns.length).to.equal(mappedNonVirtualColumnCount(briefcase), \"baseline must be consistent\");\r\n\r\n // Plain (non-dynamic) schema whose aspect needs more shared columns than the table has -> ALTER TABLE ADD COLUMN.\r\n const sharedColumnCount = baselineColumns.filter((name) => /^ps\\d+$/i.test(name)).length;\r\n await briefcase.importSchemaStrings([schemaWithWideMultiAspect(sharedColumnCount + extraColumns)]);\r\n chai.expect(physicalColumns(briefcase).length).to.equal(baselineColumns.length + extraColumns, \"import should add physical columns\");\r\n chai.expect(mappedNonVirtualColumnCount(briefcase)).to.equal(baselineColumns.length + extraColumns);\r\n\r\n // Abandon the un-pushed schema import: ec_* is reverted, the DDL is not.\r\n await briefcase.discardChanges();\r\n const schemaCount = briefcase.withSqliteStatement(\"SELECT COUNT(*) FROM ec_Schema WHERE Name = 'WideAspect'\", (stmt) => {\r\n stmt.step();\r\n return stmt.getValue(0).getInteger();\r\n });\r\n chai.expect(schemaCount).to.equal(0, \"schema should be gone after discardChanges\");\r\n\r\n const mapped = mappedNonVirtualColumnCount(briefcase);\r\n const physical = physicalColumns(briefcase).length;\r\n chai.expect(mapped).to.equal(baselineColumns.length);\r\n chai.expect(physical - mapped).to.equal(extraColumns, `orphan columns left in ${tableName}`);\r\n\r\n // A subsequent regular schema import must tolerate the unused shared columns left behind.\r\n await briefcase.importSchemaStrings([unrelatedSchema]);\r\n chai.expect(physicalColumns(briefcase).length).to.equal(physical);\r\n });\r\n});\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itwin/core-backend",
|
|
3
|
-
"version": "5.14.
|
|
3
|
+
"version": "5.14.4",
|
|
4
4
|
"description": "iTwin.js backend components",
|
|
5
5
|
"main": "lib/cjs/core-backend.js",
|
|
6
6
|
"module": "lib/esm/core-backend.js",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@opentelemetry/api": "^1.0.4",
|
|
30
|
-
"@itwin/core-bentley": "5.14.
|
|
31
|
-
"@itwin/core-common": "5.14.
|
|
32
|
-
"@itwin/core-geometry": "5.14.
|
|
33
|
-
"@itwin/core-quantity": "5.14.
|
|
34
|
-
"@itwin/ecschema-metadata": "5.14.
|
|
30
|
+
"@itwin/core-bentley": "5.14.4",
|
|
31
|
+
"@itwin/core-common": "5.14.4",
|
|
32
|
+
"@itwin/core-geometry": "5.14.4",
|
|
33
|
+
"@itwin/core-quantity": "5.14.4",
|
|
34
|
+
"@itwin/ecschema-metadata": "5.14.4"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {
|
|
37
37
|
"@opentelemetry/api": {
|
|
@@ -71,19 +71,19 @@
|
|
|
71
71
|
"typescript": "~5.6.2",
|
|
72
72
|
"marked": "^14.1.3",
|
|
73
73
|
"sql-formatter": "^15.4.6",
|
|
74
|
-
"vite": "^6.4.
|
|
75
|
-
"@itwin/core-
|
|
76
|
-
"@itwin/
|
|
77
|
-
"@itwin/
|
|
78
|
-
"@itwin/
|
|
79
|
-
"@itwin/
|
|
80
|
-
"@itwin/core-
|
|
81
|
-
"@itwin/ecschema-metadata": "5.14.
|
|
82
|
-
"@itwin/ecsql-common": "5.14.
|
|
74
|
+
"vite": "^6.4.4",
|
|
75
|
+
"@itwin/core-bentley": "5.14.4",
|
|
76
|
+
"@itwin/core-quantity": "5.14.4",
|
|
77
|
+
"@itwin/ecschema-locaters": "5.14.4",
|
|
78
|
+
"@itwin/build-tools": "5.14.4",
|
|
79
|
+
"@itwin/core-geometry": "5.14.4",
|
|
80
|
+
"@itwin/core-common": "5.14.4",
|
|
81
|
+
"@itwin/ecschema-metadata": "5.14.4",
|
|
82
|
+
"@itwin/ecsql-common": "5.14.4",
|
|
83
83
|
"internal-tools": "3.0.0-dev.69"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@bentley/imodeljs-native": "5.14.
|
|
86
|
+
"@bentley/imodeljs-native": "5.14.55",
|
|
87
87
|
"@itwin/object-storage-azure": "^3.0.4",
|
|
88
88
|
"@azure/storage-blob": "^12.28.0",
|
|
89
89
|
"form-data": "^4.0.6",
|