@itwin/core-backend 5.6.0-dev.8 → 5.7.0-dev.2

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  3. package/lib/cjs/BriefcaseManager.js +37 -14
  4. package/lib/cjs/BriefcaseManager.js.map +1 -1
  5. package/lib/cjs/IModelDb.d.ts +33 -24
  6. package/lib/cjs/IModelDb.d.ts.map +1 -1
  7. package/lib/cjs/IModelDb.js +42 -4
  8. package/lib/cjs/IModelDb.js.map +1 -1
  9. package/lib/cjs/SQLiteDb.d.ts.map +1 -1
  10. package/lib/cjs/SQLiteDb.js +0 -1
  11. package/lib/cjs/SQLiteDb.js.map +1 -1
  12. package/lib/cjs/SqliteChangesetReader.d.ts +5 -0
  13. package/lib/cjs/SqliteChangesetReader.d.ts.map +1 -1
  14. package/lib/cjs/SqliteChangesetReader.js +7 -0
  15. package/lib/cjs/SqliteChangesetReader.js.map +1 -1
  16. package/lib/cjs/StashManager.d.ts +1 -2
  17. package/lib/cjs/StashManager.d.ts.map +1 -1
  18. package/lib/cjs/StashManager.js.map +1 -1
  19. package/lib/cjs/TxnManager.d.ts +74 -52
  20. package/lib/cjs/TxnManager.d.ts.map +1 -1
  21. package/lib/cjs/TxnManager.js +114 -26
  22. package/lib/cjs/TxnManager.js.map +1 -1
  23. package/lib/cjs/annotations/TextAnnotationElement.d.ts +1 -1
  24. package/lib/cjs/annotations/TextAnnotationElement.d.ts.map +1 -1
  25. package/lib/cjs/annotations/TextAnnotationElement.js +14 -1
  26. package/lib/cjs/annotations/TextAnnotationElement.js.map +1 -1
  27. package/lib/cjs/annotations/TextAnnotationGeometry.d.ts +14 -0
  28. package/lib/cjs/annotations/TextAnnotationGeometry.d.ts.map +1 -1
  29. package/lib/cjs/annotations/TextAnnotationGeometry.js +29 -10
  30. package/lib/cjs/annotations/TextAnnotationGeometry.js.map +1 -1
  31. package/lib/cjs/annotations/TextBlockLayout.js +6 -6
  32. package/lib/cjs/annotations/TextBlockLayout.js.map +1 -1
  33. package/lib/cjs/rpc-impl/RpcBriefcaseUtility.d.ts.map +1 -1
  34. package/lib/cjs/rpc-impl/RpcBriefcaseUtility.js.map +1 -1
  35. package/lib/esm/BriefcaseManager.d.ts.map +1 -1
  36. package/lib/esm/BriefcaseManager.js +37 -14
  37. package/lib/esm/BriefcaseManager.js.map +1 -1
  38. package/lib/esm/IModelDb.d.ts +33 -24
  39. package/lib/esm/IModelDb.d.ts.map +1 -1
  40. package/lib/esm/IModelDb.js +42 -4
  41. package/lib/esm/IModelDb.js.map +1 -1
  42. package/lib/esm/SQLiteDb.d.ts.map +1 -1
  43. package/lib/esm/SQLiteDb.js +0 -1
  44. package/lib/esm/SQLiteDb.js.map +1 -1
  45. package/lib/esm/SqliteChangesetReader.d.ts +5 -0
  46. package/lib/esm/SqliteChangesetReader.d.ts.map +1 -1
  47. package/lib/esm/SqliteChangesetReader.js +7 -0
  48. package/lib/esm/SqliteChangesetReader.js.map +1 -1
  49. package/lib/esm/StashManager.d.ts +1 -2
  50. package/lib/esm/StashManager.d.ts.map +1 -1
  51. package/lib/esm/StashManager.js.map +1 -1
  52. package/lib/esm/TxnManager.d.ts +74 -52
  53. package/lib/esm/TxnManager.d.ts.map +1 -1
  54. package/lib/esm/TxnManager.js +114 -26
  55. package/lib/esm/TxnManager.js.map +1 -1
  56. package/lib/esm/annotations/TextAnnotationElement.d.ts +1 -1
  57. package/lib/esm/annotations/TextAnnotationElement.d.ts.map +1 -1
  58. package/lib/esm/annotations/TextAnnotationElement.js +14 -1
  59. package/lib/esm/annotations/TextAnnotationElement.js.map +1 -1
  60. package/lib/esm/annotations/TextAnnotationGeometry.d.ts +14 -0
  61. package/lib/esm/annotations/TextAnnotationGeometry.d.ts.map +1 -1
  62. package/lib/esm/annotations/TextAnnotationGeometry.js +29 -10
  63. package/lib/esm/annotations/TextAnnotationGeometry.js.map +1 -1
  64. package/lib/esm/annotations/TextBlockLayout.js +6 -6
  65. package/lib/esm/annotations/TextBlockLayout.js.map +1 -1
  66. package/lib/esm/rpc-impl/RpcBriefcaseUtility.d.ts.map +1 -1
  67. package/lib/esm/rpc-impl/RpcBriefcaseUtility.js.map +1 -1
  68. package/lib/esm/test/annotations/LeaderGeometry.test.js +2 -3
  69. package/lib/esm/test/annotations/LeaderGeometry.test.js.map +1 -1
  70. package/lib/esm/test/annotations/TextAnnotation.test.js +39 -4
  71. package/lib/esm/test/annotations/TextAnnotation.test.js.map +1 -1
  72. package/lib/esm/test/annotations/TextBlock.test.js +49 -42
  73. package/lib/esm/test/annotations/TextBlock.test.js.map +1 -1
  74. package/lib/esm/test/hubaccess/Rebase.test.js +546 -22
  75. package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -1
  76. package/lib/esm/test/imodel/SchemaXmlImport.test.js +87 -44
  77. package/lib/esm/test/imodel/SchemaXmlImport.test.js.map +1 -1
  78. package/lib/esm/test/schema/SchemaImportCallbacks.test.js +4 -4
  79. package/lib/esm/test/schema/SchemaImportCallbacks.test.js.map +1 -1
  80. package/lib/esm/test/standalone/ChangeMerge.test.js +9 -9
  81. package/lib/esm/test/standalone/ChangeMerge.test.js.map +1 -1
  82. package/package.json +14 -14
@@ -3,15 +3,16 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import { DbResult, Guid } from "@itwin/core-bentley";
6
- import { Code, IModel, QueryBinder, SubCategoryAppearance } from "@itwin/core-common";
6
+ import { Code, GeometryStreamBuilder, IModel, QueryBinder, SubCategoryAppearance } from "@itwin/core-common";
7
7
  import * as chai from "chai";
8
8
  import * as chaiAsPromised from "chai-as-promised";
9
9
  import { HubWrappers, IModelTestUtils, KnownTestLocations } from "..";
10
- import { BriefcaseManager, ChangesetECAdaptor, ChannelControl, DrawingCategory, ElementGroupsMembers, IModelHost, SqliteChangesetReader } from "../../core-backend";
10
+ import { _nativeDb, BriefcaseManager, ChangesetECAdaptor, ChannelControl, DrawingCategory, ElementGroupsMembers, IModelHost, SqliteChangesetReader } from "../../core-backend";
11
11
  import { HubMock } from "../../internal/HubMock";
12
12
  import { StashManager } from "../../StashManager";
13
13
  import { existsSync, unlinkSync, writeFileSync } from "fs";
14
14
  import * as path from "path";
15
+ import { LineSegment3d, Point3d } from "@itwin/core-geometry";
15
16
  chai.use(chaiAsPromised);
16
17
  class TestIModel {
17
18
  iModelId = "";
@@ -33,6 +34,10 @@ class TestIModel {
33
34
  <BaseClass>bis:GraphicalElement2d</BaseClass>
34
35
  <ECProperty propertyName="prop1" typeName="string" />
35
36
  </ECEntityClass>
37
+ <ECEntityClass typeName="A1Recipe2d">
38
+ <BaseClass>bis:TemplateRecipe2d</BaseClass>
39
+ <ECProperty propertyName="prop1" typeName="string" />
40
+ </ECEntityClass>
36
41
  <ECRelationshipClass typeName="A1OwnsA1" modifier="None" strength="embedding">
37
42
  <BaseClass>bis:ElementOwnsChildElements</BaseClass>
38
43
  <Source multiplicity="(0..1)" roleLabel="owns" polymorphic="true">
@@ -65,31 +70,73 @@ class TestIModel {
65
70
  this.briefcases.push(b);
66
71
  return b;
67
72
  }
73
+ async insertRecipe2d(b, markAsIndirect) {
74
+ await b.locks.acquireLocks({ shared: [IModel.dictionaryId] });
75
+ const baseProps = {
76
+ classFullName: "TestDomain:A1Recipe2d",
77
+ model: IModel.dictionaryId,
78
+ code: Code.createEmpty(),
79
+ };
80
+ let id = "";
81
+ if (markAsIndirect) {
82
+ b.txns.withIndirectTxnMode(() => {
83
+ id = b.elements.insertElement({ ...baseProps, prop1: `${this._data++}` });
84
+ });
85
+ return id;
86
+ }
87
+ return b.elements.insertElement({ ...baseProps, prop1: `${this._data++}` });
88
+ }
89
+ async updateRecipe2d(b, id, markAsIndirect) {
90
+ await b.locks.acquireLocks({ shared: [IModel.dictionaryId], exclusive: [id] });
91
+ const elProps = b.elements.getElementProps(id);
92
+ if (markAsIndirect) {
93
+ b.txns.withIndirectTxnMode(() => {
94
+ b.elements.updateElement({ ...elProps, prop1: `${this._data++}` });
95
+ });
96
+ }
97
+ else {
98
+ b.elements.updateElement({ ...elProps, prop1: `${this._data++}` });
99
+ }
100
+ }
68
101
  async insertElement(b, markAsIndirect) {
69
102
  await b.locks.acquireLocks({ shared: [this.drawingModelId] });
103
+ const builder = new GeometryStreamBuilder();
104
+ const p1 = Point3d.createZero();
105
+ const p2 = Point3d.createFrom({ x: Math.random() * 10.0 + 5.0, y: 0.0, z: 0.0 });
106
+ const circle = LineSegment3d.create(p1, p2);
107
+ builder.appendGeometry(circle);
70
108
  const baseProps = {
71
109
  classFullName: "TestDomain:a1",
72
110
  model: this.drawingModelId,
73
111
  category: this.drawingCategoryId,
74
112
  code: Code.createEmpty(),
113
+ geom: builder.geometryStream,
114
+ prop1: `${this._data++}`,
75
115
  };
76
116
  let id = "";
77
117
  if (markAsIndirect) {
78
118
  b.txns.withIndirectTxnMode(() => {
79
- id = b.elements.insertElement({ ...baseProps, prop1: `${this._data++}` });
119
+ id = b.elements.insertElement(baseProps);
80
120
  });
81
121
  return id;
82
122
  }
83
- return b.elements.insertElement({ ...baseProps, prop1: `${this._data++}` });
123
+ baseProps.prop1 = `${this._data++}`;
124
+ return b.elements.insertElement(baseProps);
84
125
  }
85
- async insertElement2(b, args) {
126
+ async insertElementEx(b, args) {
86
127
  await b.locks.acquireLocks({ shared: [this.drawingModelId] });
128
+ const builder = new GeometryStreamBuilder();
129
+ const p1 = Point3d.createZero();
130
+ const p2 = Point3d.createFrom({ x: Math.random() * 10.0 + 5.0, y: 0.0, z: 0.0 });
131
+ const circle = LineSegment3d.create(p1, p2);
132
+ builder.appendGeometry(circle);
87
133
  const props = {
88
134
  classFullName: "TestDomain:a1",
89
135
  model: this.drawingModelId,
90
136
  category: this.drawingCategoryId,
91
137
  code: Code.createEmpty(),
92
138
  parent: args?.parent,
139
+ geom: builder.geometryStream,
93
140
  prop1: args?.prop1 ?? `${this._data++}`
94
141
  };
95
142
  let id = "";
@@ -101,9 +148,17 @@ class TestIModel {
101
148
  }
102
149
  return b.elements.insertElement(props);
103
150
  }
104
- async updateElement(b, id, markAsIndirect) {
151
+ async updateElement(b, id, markAsIndirect, updateGeom) {
105
152
  await b.locks.acquireLocks({ shared: [this.drawingModelId], exclusive: [id] });
106
153
  const elProps = b.elements.getElementProps(id);
154
+ if (updateGeom) {
155
+ const builder = new GeometryStreamBuilder();
156
+ const p1 = Point3d.createZero();
157
+ const p2 = Point3d.createFrom({ x: Math.random() * 10.0 + 10.0, y: 0.0, z: 0.0 });
158
+ const circle = LineSegment3d.create(p1, p2);
159
+ builder.appendGeometry(circle);
160
+ elProps.geom = builder.geometryStream;
161
+ }
107
162
  if (markAsIndirect) {
108
163
  b.txns.withIndirectTxnMode(() => {
109
164
  b.elements.updateElement({ ...elProps, prop1: `${this._data++}` });
@@ -129,6 +184,18 @@ class TestIModel {
129
184
  HubMock.shutdown();
130
185
  }
131
186
  }
187
+ const removePropertyRecursive = (obj, prop) => {
188
+ if (obj && typeof obj === "object") {
189
+ Object.keys(obj).forEach((key) => {
190
+ if (key === prop) {
191
+ delete obj[key];
192
+ }
193
+ else {
194
+ removePropertyRecursive(obj[key], prop);
195
+ }
196
+ });
197
+ }
198
+ };
132
199
  describe("rebase changes & stashing api", function () {
133
200
  let testIModel;
134
201
  before(async () => {
@@ -428,7 +495,7 @@ describe("rebase changes & stashing api", function () {
428
495
  b1.saveChanges();
429
496
  await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
430
497
  await b1.pushChanges({ description: "test" });
431
- })).to.be.rejectedWith("Cannot push changeset while in an indirect change scope");
498
+ })).to.be.rejectedWith("Cannot pull and apply changeset while in an indirect change scope");
432
499
  await b1.pushChanges({ description: "test" });
433
500
  });
434
501
  it("should fail to saveFileProperty/deleteFileProperty in indirect scope", async () => {
@@ -669,11 +736,11 @@ describe("rebase changes & stashing api", function () {
669
736
  await b2.pullChanges();
670
737
  chai.expect(b2.changeset.index).to.equals(4);
671
738
  const elBefore = b2.elements.tryGetElementProps(e1);
672
- chai.expect(elBefore.prop1).to.equals("2");
739
+ chai.expect(elBefore.prop1).to.equals("3");
673
740
  // restore stash should succeed as now it can obtain lock
674
741
  await StashManager.restore({ db: b2, stash: b2Stash1 });
675
742
  const elAfter = b2.elements.tryGetElementProps(e1);
676
- chai.expect(elAfter.prop1).to.equals("1");
743
+ chai.expect(elAfter.prop1).to.equals("2");
677
744
  await b2.pushChanges({ description: `${e1} updated` });
678
745
  });
679
746
  it("schema change should not be stashed", async () => {
@@ -686,6 +753,10 @@ describe("rebase changes & stashing api", function () {
686
753
  <ECProperty propertyName="prop1" typeName="string" />
687
754
  <ECProperty propertyName="prop2" typeName="string" />
688
755
  </ECEntityClass>
756
+ <ECEntityClass typeName="A1Recipe2d">
757
+ <BaseClass>bis:TemplateRecipe2d</BaseClass>
758
+ <ECProperty propertyName="prop1" typeName="string" />
759
+ </ECEntityClass>
689
760
  <ECRelationshipClass typeName="A1OwnsA1" modifier="None" strength="embedding">
690
761
  <BaseClass>bis:ElementOwnsChildElements</BaseClass>
691
762
  <Source multiplicity="(0..1)" roleLabel="owns" polymorphic="true">
@@ -783,7 +854,7 @@ describe("rebase changes & stashing api", function () {
783
854
  const b1 = await testIModel.openBriefcase();
784
855
  const b2 = await testIModel.openBriefcase();
785
856
  const parentId = await testIModel.insertElement(b1);
786
- const childId = await testIModel.insertElement2(b1, { parent: { id: parentId, relClassName: "TestDomain:A1OwnsA1" } });
857
+ const childId = await testIModel.insertElementEx(b1, { parent: { id: parentId, relClassName: "TestDomain:A1OwnsA1" } });
787
858
  b1.saveChanges("insert parent and child");
788
859
  await b1.pushChanges({ description: `inserted parent ${parentId} and child ${childId}` });
789
860
  await b2.pullChanges();
@@ -791,7 +862,7 @@ describe("rebase changes & stashing api", function () {
791
862
  await testIModel.deleteElement(b1, childId);
792
863
  b1.saveChanges("delete child");
793
864
  // no exclusive lock required on child1
794
- const grandChildId = await testIModel.insertElement2(b2, { parent: { id: childId, relClassName: "TestDomain:A1OwnsA1" }, markAsIndirect: true });
865
+ const grandChildId = await testIModel.insertElementEx(b2, { parent: { id: childId, relClassName: "TestDomain:A1OwnsA1" }, markAsIndirect: true });
795
866
  b2.saveChanges("delete child and insert grandchild");
796
867
  await b1.pushChanges({ description: `deleted child ${childId}` });
797
868
  // should fail to pull and rebase changes.
@@ -919,7 +990,7 @@ describe("rebase changes & stashing api", function () {
919
990
  onRebase: {
920
991
  beginCount: 0,
921
992
  endCount: 0,
922
- beginIds: [],
993
+ beginTxns: [],
923
994
  },
924
995
  onRebaseTxn: {
925
996
  beginTxns: [],
@@ -928,44 +999,174 @@ describe("rebase changes & stashing api", function () {
928
999
  rebaseHandler: {
929
1000
  shouldReinstate: [],
930
1001
  recompute: [],
931
- }
1002
+ },
1003
+ pullMerge: {
1004
+ beginCount: 0,
1005
+ endCount: 0,
1006
+ beginChangeset: [],
1007
+ endChangeset: [],
1008
+ },
1009
+ applyIncomingChanges: {
1010
+ beginCount: 0,
1011
+ endCount: 0,
1012
+ beginChangesets: [],
1013
+ endChangesets: [],
1014
+ },
1015
+ reverseLocalChanges: {
1016
+ beginCount: 0,
1017
+ endCount: 0,
1018
+ txns: [],
1019
+ },
1020
+ downloadChangesets: {
1021
+ beginCount: 0,
1022
+ endCount: 0,
1023
+ },
932
1024
  };
933
1025
  const resetEvent = () => {
934
1026
  events.onRebase.beginCount = 0;
935
1027
  events.onRebase.endCount = 0;
936
- events.onRebase.beginIds = [];
1028
+ events.onRebase.beginTxns = [];
937
1029
  events.onRebaseTxn.beginTxns = [];
938
1030
  events.onRebaseTxn.endTxns = [];
939
1031
  events.rebaseHandler.shouldReinstate = [];
940
1032
  events.rebaseHandler.recompute = [];
1033
+ events.pullMerge.beginCount = 0;
1034
+ events.pullMerge.endCount = 0;
1035
+ events.pullMerge.beginChangeset = [];
1036
+ events.pullMerge.endChangeset = [];
1037
+ events.applyIncomingChanges.beginCount = 0;
1038
+ events.applyIncomingChanges.endCount = 0;
1039
+ events.applyIncomingChanges.beginChangesets = [];
1040
+ events.applyIncomingChanges.endChangesets = [];
1041
+ events.reverseLocalChanges.beginCount = 0;
1042
+ events.reverseLocalChanges.endCount = 0;
1043
+ events.reverseLocalChanges.txns = [];
1044
+ events.downloadChangesets.beginCount = 0;
1045
+ events.downloadChangesets.endCount = 0;
941
1046
  };
942
- b2.txns.onRebaseBegin.addListener((ids) => {
1047
+ // onPullMergeXXXX
1048
+ b2.txns.rebaser.onPullMergeBegin.addListener((changeset) => {
1049
+ events.pullMerge.beginCount++;
1050
+ events.pullMerge.beginChangeset.push(changeset);
1051
+ });
1052
+ b2.txns.rebaser.onPullMergeEnd.addListener((changeset) => {
1053
+ events.pullMerge.endCount++;
1054
+ events.pullMerge.endChangeset.push(changeset);
1055
+ });
1056
+ // onApplyIncomingChangesXXXX
1057
+ b2.txns.rebaser.onApplyIncomingChangesBegin.addListener((changesets) => {
1058
+ events.applyIncomingChanges.beginCount++;
1059
+ events.applyIncomingChanges.beginChangesets.push(...changesets);
1060
+ });
1061
+ b2.txns.rebaser.onApplyIncomingChangesEnd.addListener((changesets) => {
1062
+ events.applyIncomingChanges.endCount++;
1063
+ events.applyIncomingChanges.endChangesets.push(...changesets);
1064
+ });
1065
+ // onReverseLocalChangesXXXX
1066
+ b2.txns.rebaser.onReverseLocalChangesBegin.addListener(() => {
1067
+ events.reverseLocalChanges.beginCount++;
1068
+ });
1069
+ b2.txns.rebaser.onReverseLocalChangesEnd.addListener((txns) => {
1070
+ events.reverseLocalChanges.endCount++;
1071
+ removePropertyRecursive(txns, "timestamp"); // it changes on each run, so remove it for comparison
1072
+ events.reverseLocalChanges.txns.push(...txns);
1073
+ });
1074
+ // onDownloadChangesetsXXXX
1075
+ b2.txns.rebaser.onDownloadChangesetsBegin.addListener(() => {
1076
+ events.downloadChangesets.beginCount++;
1077
+ });
1078
+ b2.txns.rebaser.onDownloadChangesetsEnd.addListener(() => {
1079
+ events.downloadChangesets.endCount++;
1080
+ });
1081
+ // onRebaseXXXX
1082
+ b2.txns.rebaser.onRebaseBegin.addListener((txns) => {
943
1083
  events.onRebase.beginCount++;
944
- events.onRebase.beginIds.push(...ids);
1084
+ removePropertyRecursive(txns, "timestamp"); // it changes on each run, so remove it for comparison
1085
+ events.onRebase.beginTxns.push(...txns);
945
1086
  });
946
- b2.txns.onRebaseEnd.addListener(() => {
1087
+ b2.txns.rebaser.onRebaseEnd.addListener(() => {
947
1088
  events.onRebase.endCount++;
948
1089
  });
949
- b2.txns.onRebaseTxnBegin.addListener((txn) => {
1090
+ // onRebaseTxnXXXX
1091
+ b2.txns.rebaser.onRebaseTxnBegin.addListener((txn) => {
1092
+ removePropertyRecursive(txn, "timestamp"); // it changes on each run, so remove it for comparison
950
1093
  events.onRebaseTxn.beginTxns.push(txn);
951
1094
  });
952
- b2.txns.onRebaseTxnEnd.addListener((txn) => {
1095
+ b2.txns.rebaser.onRebaseTxnEnd.addListener((txn) => {
1096
+ removePropertyRecursive(txn, "timestamp"); // it changes on each run, so remove it for comparison
953
1097
  events.onRebaseTxn.endTxns.push(txn);
954
1098
  });
955
1099
  b2.txns.rebaser.setCustomHandler({
956
1100
  shouldReinstate: (_txn) => {
1101
+ // shouldReinstate
1102
+ removePropertyRecursive(_txn, "timestamp"); // it changes on each run, so remove it for comparison
957
1103
  events.rebaseHandler.shouldReinstate.push(_txn);
958
1104
  return true;
959
1105
  },
960
1106
  recompute: async (_txn) => {
1107
+ // recompute
1108
+ removePropertyRecursive(_txn, "timestamp"); // it changes on each run, so remove it for comparison
961
1109
  events.rebaseHandler.recompute.push(_txn);
962
1110
  },
963
1111
  });
964
1112
  resetEvent();
965
1113
  await b2.pullChanges();
1114
+ // pullMerge events
1115
+ chai.expect(events.pullMerge.beginCount).to.equal(1);
1116
+ chai.expect(events.pullMerge.endCount).to.equal(1);
1117
+ chai.expect((events.pullMerge.beginChangeset[0].index)).to.equal(3);
1118
+ chai.expect((events.pullMerge.endChangeset[0].index)).to.equal(4);
1119
+ // applyIncomingChanges events
1120
+ chai.expect(events.applyIncomingChanges.beginCount).to.equal(1);
1121
+ chai.expect(events.applyIncomingChanges.endCount).to.equal(1);
1122
+ chai.expect(events.applyIncomingChanges.beginChangesets.map((cs) => cs.index)).to.deep.equal([4]);
1123
+ chai.expect(events.applyIncomingChanges.endChangesets.map((cs) => cs.index)).to.deep.equal([4]);
1124
+ // downloadChangesets events
1125
+ chai.expect(events.downloadChangesets.beginCount).to.equal(1);
1126
+ chai.expect(events.downloadChangesets.endCount).to.equal(1);
1127
+ // reverseLocalChanges events
1128
+ chai.expect(events.reverseLocalChanges.beginCount).to.equal(1);
1129
+ chai.expect(events.reverseLocalChanges.endCount).to.equal(1);
1130
+ chai.expect(events.reverseLocalChanges.txns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1131
+ // rebase events
966
1132
  chai.expect(events.onRebase.beginCount).to.equal(1);
967
1133
  chai.expect(events.onRebase.endCount).to.equal(1);
968
- chai.expect(events.onRebase.beginIds).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1134
+ chai.expect(events.onRebase.beginTxns).to.deep.equal([
1135
+ {
1136
+ grouped: false,
1137
+ id: "0x100000000",
1138
+ nextId: "0x100000001",
1139
+ props: {
1140
+ description: "first change"
1141
+ },
1142
+ reversed: true,
1143
+ sessionId: 1,
1144
+ type: "Data"
1145
+ },
1146
+ {
1147
+ grouped: false,
1148
+ id: "0x100000001",
1149
+ nextId: "0x100000002",
1150
+ prevId: "0x100000000",
1151
+ props: {
1152
+ description: "second change",
1153
+ },
1154
+ reversed: true,
1155
+ sessionId: 1,
1156
+ type: "Data"
1157
+ },
1158
+ {
1159
+ grouped: false,
1160
+ id: "0x100000002",
1161
+ prevId: "0x100000001",
1162
+ props: {
1163
+ description: "third change"
1164
+ },
1165
+ reversed: true,
1166
+ sessionId: 1,
1167
+ type: "Data"
1168
+ }
1169
+ ]);
969
1170
  chai.expect(events.onRebaseTxn.beginTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
970
1171
  chai.expect(events.onRebaseTxn.endTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
971
1172
  chai.expect(events.rebaseHandler.shouldReinstate.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
@@ -976,17 +1177,243 @@ describe("rebase changes & stashing api", function () {
976
1177
  await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
977
1178
  await testIModel.insertElement(b2);
978
1179
  await testIModel.insertElement(b2, true);
979
- b2.saveChanges("forth change");
1180
+ b2.saveChanges("fourth change");
980
1181
  resetEvent();
981
1182
  await b2.pullChanges();
982
1183
  chai.expect(events.onRebase.beginCount).to.equal(1);
983
1184
  chai.expect(events.onRebase.endCount).to.equal(1);
984
- chai.expect(events.onRebase.beginIds).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1185
+ chai.expect(events.onRebase.beginTxns).to.deep.equal([
1186
+ {
1187
+ grouped: false,
1188
+ id: "0x100000000",
1189
+ nextId: "0x100000001",
1190
+ props: {
1191
+ description: "first change"
1192
+ },
1193
+ reversed: true,
1194
+ sessionId: 1,
1195
+ type: "Data"
1196
+ },
1197
+ {
1198
+ grouped: false,
1199
+ id: "0x100000001",
1200
+ nextId: "0x100000002",
1201
+ prevId: "0x100000000",
1202
+ props: {
1203
+ description: "second change"
1204
+ },
1205
+ reversed: true,
1206
+ sessionId: 1,
1207
+ type: "Data"
1208
+ },
1209
+ {
1210
+ grouped: false,
1211
+ id: "0x100000002",
1212
+ nextId: "0x100000003",
1213
+ prevId: "0x100000001",
1214
+ props: {
1215
+ description: "third change"
1216
+ },
1217
+ reversed: true,
1218
+ sessionId: 1,
1219
+ type: "Data"
1220
+ },
1221
+ {
1222
+ grouped: false,
1223
+ id: "0x100000003",
1224
+ prevId: "0x100000002",
1225
+ props: {
1226
+ description: "fourth change"
1227
+ },
1228
+ reversed: true,
1229
+ sessionId: 1,
1230
+ type: "Data"
1231
+ }
1232
+ ]);
985
1233
  chai.expect(events.onRebaseTxn.beginTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
986
1234
  chai.expect(events.onRebaseTxn.endTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
987
1235
  chai.expect(events.rebaseHandler.shouldReinstate.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
988
1236
  chai.expect(events.rebaseHandler.recompute.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
989
1237
  });
1238
+ it("onModelGeometryChanged() not fired during rebase/pullMerge with no local change", async () => {
1239
+ const b1 = await testIModel.openBriefcase();
1240
+ const b2 = await testIModel.openBriefcase();
1241
+ const pushChangeFromB2 = async () => {
1242
+ await b2.pullChanges();
1243
+ await testIModel.insertElement(b2);
1244
+ b2.saveChanges();
1245
+ await b2.pushChanges({ description: "insert element on b2" });
1246
+ };
1247
+ const events = {
1248
+ modelGeometryChanged: [],
1249
+ };
1250
+ const getGeometryGuidFromB1 = (modelId) => {
1251
+ const modelProps = b1.models.tryGetModelProps(modelId);
1252
+ return modelProps?.geometryGuid;
1253
+ };
1254
+ const clearEvents = () => {
1255
+ events.modelGeometryChanged = [];
1256
+ };
1257
+ b1.txns.onModelGeometryChanged.addListener((changes) => {
1258
+ events.modelGeometryChanged.push(changes);
1259
+ });
1260
+ clearEvents();
1261
+ b1.txns.rebaser.setCustomHandler({
1262
+ shouldReinstate: (_txn) => {
1263
+ return true;
1264
+ },
1265
+ recompute: async (_txn) => {
1266
+ },
1267
+ });
1268
+ await pushChangeFromB2();
1269
+ clearEvents();
1270
+ const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1271
+ chai.expect(geomGuidBeforePull).is.undefined;
1272
+ await b1.pushChanges({ description: "push changes on b1" });
1273
+ const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1274
+ chai.expect(geomGuidAfterPull).is.undefined;
1275
+ chai.expect(events.modelGeometryChanged.length).to.equal(0);
1276
+ });
1277
+ it("onModelGeometryChanged() fired during rebase with geometric local change", async () => {
1278
+ const b1 = await testIModel.openBriefcase();
1279
+ const b2 = await testIModel.openBriefcase();
1280
+ const pushChangeFromB2 = async () => {
1281
+ await b2.pullChanges();
1282
+ await testIModel.insertElement(b2);
1283
+ b2.saveChanges();
1284
+ await b2.pushChanges({ description: "insert element on b2" });
1285
+ };
1286
+ const events = {
1287
+ modelGeometryChanged: [],
1288
+ onGeometryChanged: [],
1289
+ };
1290
+ const getGeometryGuidFromB1 = (modelId) => {
1291
+ const modelProps = b1.models.tryGetModelProps(modelId);
1292
+ return modelProps?.geometryGuid;
1293
+ };
1294
+ const clearEvents = () => {
1295
+ events.modelGeometryChanged = [];
1296
+ events.onGeometryChanged = [];
1297
+ };
1298
+ b1.txns.onModelGeometryChanged.addListener((changes) => {
1299
+ events.modelGeometryChanged.push(changes);
1300
+ });
1301
+ b1.txns.onGeometryChanged.addListener((changes) => {
1302
+ events.onGeometryChanged.push(changes);
1303
+ });
1304
+ clearEvents();
1305
+ const e1 = await testIModel.insertElement(b1);
1306
+ const e2 = await testIModel.insertElement(b1, true);
1307
+ chai.expect(e1).to.exist;
1308
+ chai.expect(e2).to.exist;
1309
+ b1.saveChanges(`insert element ${e1} and ${e2}`);
1310
+ chai.expect(events.modelGeometryChanged.length).to.equal(1);
1311
+ chai.expect(events.modelGeometryChanged[0].length).to.equal(1);
1312
+ chai.expect(events.modelGeometryChanged[0][0].id).to.equal("0x20000000001");
1313
+ chai.assert(Guid.isGuid(events.modelGeometryChanged[0][0].guid));
1314
+ b1.txns.rebaser.setCustomHandler({
1315
+ shouldReinstate: (_txn) => {
1316
+ return true;
1317
+ },
1318
+ recompute: async (_txn) => {
1319
+ await testIModel.updateElement(b1, e1);
1320
+ await testIModel.updateElement(b1, e2);
1321
+ },
1322
+ });
1323
+ await pushChangeFromB2();
1324
+ clearEvents();
1325
+ const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1326
+ await b1.pushChanges({ description: "push changes on b1" });
1327
+ const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1328
+ chai.expect(geomGuidBeforePull).to.not.equal(geomGuidAfterPull);
1329
+ chai.expect(events.modelGeometryChanged.length).to.equal(4);
1330
+ });
1331
+ it("onModelGeometryChanged() fired during rebase with non-geometric local change", async () => {
1332
+ const b1 = await testIModel.openBriefcase();
1333
+ const b2 = await testIModel.openBriefcase();
1334
+ const pushChangeFromB2 = async () => {
1335
+ await b2.pullChanges();
1336
+ await testIModel.insertElement(b2);
1337
+ b2.saveChanges();
1338
+ await b2.pushChanges({ description: "insert element on b2" });
1339
+ };
1340
+ const events = {
1341
+ modelGeometryChanged: [],
1342
+ };
1343
+ const getGeometryGuidFromB1 = (modelId) => {
1344
+ const modelProps = b1.models.tryGetModelProps(modelId);
1345
+ return modelProps?.geometryGuid;
1346
+ };
1347
+ const clearEvents = () => {
1348
+ events.modelGeometryChanged = [];
1349
+ };
1350
+ b1.txns.onModelGeometryChanged.addListener((changes) => {
1351
+ events.modelGeometryChanged.push(changes);
1352
+ });
1353
+ clearEvents();
1354
+ b1.txns.rebaser.setCustomHandler({
1355
+ shouldReinstate: (_txn) => {
1356
+ return true;
1357
+ },
1358
+ recompute: async (_txn) => {
1359
+ },
1360
+ });
1361
+ await pushChangeFromB2();
1362
+ await testIModel.insertRecipe2d(b1);
1363
+ b1.saveChanges();
1364
+ clearEvents();
1365
+ const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1366
+ chai.expect(geomGuidBeforePull).is.undefined;
1367
+ await b1.pushChanges({ description: "push changes on b1" });
1368
+ const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1369
+ chai.expect(geomGuidAfterPull).to.exist;
1370
+ chai.expect(events.modelGeometryChanged.length).to.equal(1);
1371
+ });
1372
+ it("onModelGeometryChanged() fired during rebase with geometric local change", async () => {
1373
+ const b1 = await testIModel.openBriefcase();
1374
+ const b2 = await testIModel.openBriefcase();
1375
+ const pushChangeFromB2 = async () => {
1376
+ await b2.pullChanges();
1377
+ await testIModel.insertRecipe2d(b2);
1378
+ b2.saveChanges();
1379
+ await b2.pushChanges({ description: "insert element on b2" });
1380
+ };
1381
+ const events = {
1382
+ modelGeometryChanged: [],
1383
+ onGeometryChanged: [],
1384
+ };
1385
+ const getGeometryGuidFromB1 = (modelId) => {
1386
+ const modelProps = b1.models.tryGetModelProps(modelId);
1387
+ return modelProps?.geometryGuid;
1388
+ };
1389
+ const clearEvents = () => {
1390
+ events.modelGeometryChanged = [];
1391
+ events.onGeometryChanged = [];
1392
+ };
1393
+ b1.txns.onModelGeometryChanged.addListener((changes) => {
1394
+ events.modelGeometryChanged.push(changes);
1395
+ });
1396
+ b1.txns.onGeometryChanged.addListener((changes) => {
1397
+ events.onGeometryChanged.push(changes);
1398
+ });
1399
+ clearEvents();
1400
+ b1.txns.rebaser.setCustomHandler({
1401
+ shouldReinstate: (_txn) => {
1402
+ return true;
1403
+ },
1404
+ recompute: async (_txn) => {
1405
+ await testIModel.insertElement(b1);
1406
+ },
1407
+ });
1408
+ await pushChangeFromB2();
1409
+ clearEvents();
1410
+ const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1411
+ chai.expect(geomGuidBeforePull).is.undefined;
1412
+ await b1.pushChanges({ description: "push changes on b1" });
1413
+ const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1414
+ chai.expect(geomGuidAfterPull).is.undefined;
1415
+ chai.expect(events.modelGeometryChanged.length).to.equal(0);
1416
+ });
990
1417
  it("rebase multi txn", async () => {
991
1418
  const b1 = await testIModel.openBriefcase();
992
1419
  const b2 = await testIModel.openBriefcase();
@@ -1349,5 +1776,102 @@ describe("rebase changes & stashing api", function () {
1349
1776
  chai.expect(b1.elements.tryGetElementProps(e4)).to.exist;
1350
1777
  chai.expect(b1.elements.tryGetElementProps(e7)).to.exist;
1351
1778
  });
1779
+ it("changeset DDL error are ignored and ec_* tables are used to reconstruct the sqlite tables", async () => {
1780
+ const b1 = await testIModel.openBriefcase();
1781
+ const b2 = await testIModel.openBriefcase();
1782
+ const iModelId = testIModel.iModelId;
1783
+ const targetDir = path.join(KnownTestLocations.outputDir, iModelId, "changesets");
1784
+ let ver = 0;
1785
+ let props = 0;
1786
+ const tblGeom2d = "bis_GeometricElement2d";
1787
+ const geom2dBaseColumnList = [
1788
+ "ElementId",
1789
+ "ECClassId",
1790
+ "CategoryId",
1791
+ "Origin_X",
1792
+ "Origin_Y",
1793
+ "Rotation",
1794
+ "BBoxLow_X",
1795
+ "BBoxLow_Y",
1796
+ "BBoxHigh_X",
1797
+ "BBoxHigh_Y",
1798
+ "GeometryStream",
1799
+ "TypeDefinitionId",
1800
+ "TypeDefinitionRelECClassId",
1801
+ "js1",
1802
+ "js2",
1803
+ ];
1804
+ const generateSchema = (noOfNewPropsToAdd) => {
1805
+ props += noOfNewPropsToAdd;
1806
+ return `<?xml version="1.0" encoding="UTF-8"?>
1807
+ <ECSchema schemaName="TestDomain1" alias="ts1" version="01.00.${ver++}" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
1808
+ <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
1809
+ <ECEntityClass typeName="test">
1810
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
1811
+ <ECProperty propertyName="prop1" typeName="string" />
1812
+ ${Array.from({ length: props - 1 }, (_, i) => `<ECProperty propertyName="prop${i + 2}" typeName="string" />`).join("\n ")}
1813
+ </ECEntityClass>
1814
+ </ECSchema>`;
1815
+ };
1816
+ const getColumnNames = (b, tableName) => {
1817
+ return b.withSqliteStatement(`PRAGMA table_info(${tableName})`, (stmt) => {
1818
+ const columnNames = [];
1819
+ while (stmt.step() === DbResult.BE_SQLITE_ROW) {
1820
+ columnNames.push(stmt.getValue(1).getString());
1821
+ }
1822
+ return columnNames;
1823
+ });
1824
+ };
1825
+ const withLatestChangeset = async (cb) => {
1826
+ const csInfo = await HubMock.getLatestChangeset({ iModelId });
1827
+ const info = await HubMock.downloadChangeset({
1828
+ iModelId,
1829
+ changeset: { id: csInfo.id },
1830
+ targetDir,
1831
+ });
1832
+ const reader = SqliteChangesetReader.openFile({ db: b1, fileName: info.pathname });
1833
+ try {
1834
+ await cb(reader);
1835
+ }
1836
+ finally {
1837
+ reader.close();
1838
+ }
1839
+ };
1840
+ // Verify initial columns
1841
+ chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals(geom2dBaseColumnList);
1842
+ chai.expect(getColumnNames(b2, tblGeom2d)).deep.equals(geom2dBaseColumnList);
1843
+ // Import schema that add 5 new properties that should add 3 new shared columns
1844
+ await b1.importSchemaStrings([generateSchema(5)]);
1845
+ await b1.pushChanges({ description: `imported schema version 1.0.${ver - 1}` });
1846
+ // Verify columns after schema import
1847
+ chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5"]);
1848
+ //verify changeset has schema changes
1849
+ await withLatestChangeset(async (reader) => {
1850
+ const schemaChanges = reader.getDdlChanges()?.split(";");
1851
+ chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js3] BLOB");
1852
+ chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js4] BLOB");
1853
+ chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js5] BLOB");
1854
+ });
1855
+ await b2.pullChanges();
1856
+ chai.expect(getColumnNames(b2, "bis_GeometricElement2d")).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5"]);
1857
+ // Import schema that add 5 new properties that should add 3 new shared columns
1858
+ await b1.importSchemaStrings([generateSchema(1)]);
1859
+ await b1.pushChanges({ description: `imported schema version 1.0.${ver - 1}` });
1860
+ // Verify columns after schema import
1861
+ chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5", "js6"]);
1862
+ //verify changeset has schema changes
1863
+ await withLatestChangeset(async (reader) => {
1864
+ const schemaChanges = reader.getDdlChanges()?.split(";");
1865
+ chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js6] BLOB");
1866
+ });
1867
+ // delete the table so DDL apply should fail
1868
+ b2[_nativeDb].executeSql(`DROP TABLE ${tblGeom2d}`);
1869
+ // this would fail before this PR but should succeed as DDL error are ignored and table reconstruction is attempted using ec_* tables
1870
+ await b2.pullChanges();
1871
+ // Verify columns after schema import
1872
+ chai.expect(getColumnNames(b2, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5", "js6"]);
1873
+ b1.close();
1874
+ b2.close();
1875
+ });
1352
1876
  });
1353
1877
  //# sourceMappingURL=Rebase.test.js.map