@itwin/core-backend 5.13.0-dev.8 → 5.13.0
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 +53 -1
- package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
- package/lib/cjs/BriefcaseManager.js +11 -5
- package/lib/cjs/BriefcaseManager.js.map +1 -1
- package/lib/cjs/Category.d.ts +4 -4
- package/lib/cjs/Category.js.map +1 -1
- package/lib/cjs/ChangesetECAdaptor.d.ts +5 -0
- package/lib/cjs/ChangesetECAdaptor.d.ts.map +1 -1
- package/lib/cjs/ChangesetECAdaptor.js +13 -3
- package/lib/cjs/ChangesetECAdaptor.js.map +1 -1
- package/lib/cjs/CloudSqlite.d.ts.map +1 -1
- package/lib/cjs/CloudSqlite.js +9 -2
- package/lib/cjs/CloudSqlite.js.map +1 -1
- package/lib/cjs/CodeSpecs.d.ts +3 -3
- package/lib/cjs/CodeSpecs.js.map +1 -1
- package/lib/cjs/DisplayStyle.d.ts +2 -2
- package/lib/cjs/DisplayStyle.js.map +1 -1
- package/lib/cjs/ECDb.d.ts.map +1 -1
- package/lib/cjs/ECDb.js +1 -0
- package/lib/cjs/ECDb.js.map +1 -1
- package/lib/cjs/EditTxn.d.ts.map +1 -1
- package/lib/cjs/EditTxn.js +1 -0
- package/lib/cjs/EditTxn.js.map +1 -1
- package/lib/cjs/Element.d.ts +48 -12
- package/lib/cjs/Element.d.ts.map +1 -1
- package/lib/cjs/Element.js +120 -24
- package/lib/cjs/Element.js.map +1 -1
- package/lib/cjs/IModelDb.d.ts +72 -22
- package/lib/cjs/IModelDb.d.ts.map +1 -1
- package/lib/cjs/IModelDb.js +226 -89
- package/lib/cjs/IModelDb.js.map +1 -1
- package/lib/cjs/Model.d.ts +5 -5
- package/lib/cjs/Model.d.ts.map +1 -1
- package/lib/cjs/Model.js.map +1 -1
- package/lib/cjs/PropertyStore.js +1 -1
- package/lib/cjs/PropertyStore.js.map +1 -1
- package/lib/cjs/SchemaSync.d.ts +158 -22
- package/lib/cjs/SchemaSync.d.ts.map +1 -1
- package/lib/cjs/SchemaSync.js +417 -76
- package/lib/cjs/SchemaSync.js.map +1 -1
- package/lib/cjs/SynchronousChannel.d.ts +72 -0
- package/lib/cjs/SynchronousChannel.d.ts.map +1 -0
- package/lib/cjs/SynchronousChannel.js +11 -0
- package/lib/cjs/SynchronousChannel.js.map +1 -0
- package/lib/cjs/Texture.d.ts +1 -1
- package/lib/cjs/Texture.js.map +1 -1
- package/lib/cjs/TxnManager.d.ts +44 -4
- package/lib/cjs/TxnManager.d.ts.map +1 -1
- package/lib/cjs/TxnManager.js +80 -19
- package/lib/cjs/TxnManager.js.map +1 -1
- package/lib/cjs/ViewDefinition.d.ts +6 -6
- package/lib/cjs/ViewDefinition.d.ts.map +1 -1
- package/lib/cjs/ViewDefinition.js.map +1 -1
- package/lib/cjs/annotations/LeaderGeometry.d.ts +22 -7
- package/lib/cjs/annotations/LeaderGeometry.d.ts.map +1 -1
- package/lib/cjs/annotations/LeaderGeometry.js +104 -36
- package/lib/cjs/annotations/LeaderGeometry.js.map +1 -1
- package/lib/cjs/annotations/TextAnnotationElement.d.ts +1 -1
- package/lib/cjs/annotations/TextAnnotationElement.d.ts.map +1 -1
- package/lib/cjs/annotations/TextAnnotationElement.js +25 -10
- package/lib/cjs/annotations/TextAnnotationElement.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/IntegrityCheck.d.ts +22 -0
- package/lib/cjs/internal/IntegrityCheck.d.ts.map +1 -1
- package/lib/cjs/internal/IntegrityCheck.js +13 -0
- package/lib/cjs/internal/IntegrityCheck.js.map +1 -1
- package/lib/cjs/internal/NoReservations.d.ts +6 -0
- package/lib/cjs/internal/NoReservations.d.ts.map +1 -0
- package/lib/cjs/internal/NoReservations.js +21 -0
- package/lib/cjs/internal/NoReservations.js.map +1 -0
- package/lib/cjs/internal/SchemaSyncReservations.d.ts +4 -0
- package/lib/cjs/internal/SchemaSyncReservations.d.ts.map +1 -0
- package/lib/cjs/internal/SchemaSyncReservations.js +128 -0
- package/lib/cjs/internal/SchemaSyncReservations.js.map +1 -0
- package/lib/cjs/internal/Symbols.d.ts +1 -0
- package/lib/cjs/internal/Symbols.d.ts.map +1 -1
- package/lib/cjs/internal/Symbols.js +2 -1
- package/lib/cjs/internal/Symbols.js.map +1 -1
- package/lib/esm/BriefcaseManager.d.ts.map +1 -1
- package/lib/esm/BriefcaseManager.js +11 -5
- package/lib/esm/BriefcaseManager.js.map +1 -1
- package/lib/esm/Category.d.ts +4 -4
- package/lib/esm/Category.js.map +1 -1
- package/lib/esm/ChangesetECAdaptor.d.ts +5 -0
- package/lib/esm/ChangesetECAdaptor.d.ts.map +1 -1
- package/lib/esm/ChangesetECAdaptor.js +13 -3
- package/lib/esm/ChangesetECAdaptor.js.map +1 -1
- package/lib/esm/CloudSqlite.d.ts.map +1 -1
- package/lib/esm/CloudSqlite.js +10 -3
- package/lib/esm/CloudSqlite.js.map +1 -1
- package/lib/esm/CodeSpecs.d.ts +3 -3
- package/lib/esm/CodeSpecs.js.map +1 -1
- package/lib/esm/DisplayStyle.d.ts +2 -2
- package/lib/esm/DisplayStyle.js.map +1 -1
- package/lib/esm/ECDb.d.ts.map +1 -1
- package/lib/esm/ECDb.js +1 -0
- package/lib/esm/ECDb.js.map +1 -1
- package/lib/esm/EditTxn.d.ts.map +1 -1
- package/lib/esm/EditTxn.js +1 -0
- package/lib/esm/EditTxn.js.map +1 -1
- package/lib/esm/Element.d.ts +48 -12
- package/lib/esm/Element.d.ts.map +1 -1
- package/lib/esm/Element.js +121 -25
- package/lib/esm/Element.js.map +1 -1
- package/lib/esm/IModelDb.d.ts +72 -22
- package/lib/esm/IModelDb.d.ts.map +1 -1
- package/lib/esm/IModelDb.js +227 -90
- package/lib/esm/IModelDb.js.map +1 -1
- package/lib/esm/Model.d.ts +5 -5
- package/lib/esm/Model.d.ts.map +1 -1
- package/lib/esm/Model.js.map +1 -1
- package/lib/esm/PropertyStore.js +1 -1
- package/lib/esm/PropertyStore.js.map +1 -1
- package/lib/esm/SchemaSync.d.ts +158 -22
- package/lib/esm/SchemaSync.d.ts.map +1 -1
- package/lib/esm/SchemaSync.js +419 -78
- package/lib/esm/SchemaSync.js.map +1 -1
- package/lib/esm/SynchronousChannel.d.ts +72 -0
- package/lib/esm/SynchronousChannel.d.ts.map +1 -0
- package/lib/esm/SynchronousChannel.js +9 -0
- package/lib/esm/SynchronousChannel.js.map +1 -0
- package/lib/esm/Texture.d.ts +1 -1
- package/lib/esm/Texture.js.map +1 -1
- package/lib/esm/TxnManager.d.ts +44 -4
- package/lib/esm/TxnManager.d.ts.map +1 -1
- package/lib/esm/TxnManager.js +81 -20
- package/lib/esm/TxnManager.js.map +1 -1
- package/lib/esm/ViewDefinition.d.ts +6 -6
- package/lib/esm/ViewDefinition.d.ts.map +1 -1
- package/lib/esm/ViewDefinition.js.map +1 -1
- package/lib/esm/annotations/LeaderGeometry.d.ts +22 -7
- package/lib/esm/annotations/LeaderGeometry.d.ts.map +1 -1
- package/lib/esm/annotations/LeaderGeometry.js +104 -36
- package/lib/esm/annotations/LeaderGeometry.js.map +1 -1
- package/lib/esm/annotations/TextAnnotationElement.d.ts +1 -1
- package/lib/esm/annotations/TextAnnotationElement.d.ts.map +1 -1
- package/lib/esm/annotations/TextAnnotationElement.js +25 -10
- package/lib/esm/annotations/TextAnnotationElement.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/IntegrityCheck.d.ts +22 -0
- package/lib/esm/internal/IntegrityCheck.d.ts.map +1 -1
- package/lib/esm/internal/IntegrityCheck.js +13 -0
- package/lib/esm/internal/IntegrityCheck.js.map +1 -1
- package/lib/esm/internal/NoReservations.d.ts +6 -0
- package/lib/esm/internal/NoReservations.d.ts.map +1 -0
- package/lib/esm/internal/NoReservations.js +18 -0
- package/lib/esm/internal/NoReservations.js.map +1 -0
- package/lib/esm/internal/SchemaSyncReservations.d.ts +4 -0
- package/lib/esm/internal/SchemaSyncReservations.d.ts.map +1 -0
- package/lib/esm/internal/SchemaSyncReservations.js +125 -0
- package/lib/esm/internal/SchemaSyncReservations.js.map +1 -0
- package/lib/esm/internal/Symbols.d.ts +1 -0
- package/lib/esm/internal/Symbols.d.ts.map +1 -1
- package/lib/esm/internal/Symbols.js +1 -0
- package/lib/esm/internal/Symbols.js.map +1 -1
- package/lib/esm/test/IModelTestUtils.d.ts +1 -1
- package/lib/esm/test/IModelTestUtils.d.ts.map +1 -1
- package/lib/esm/test/IModelTestUtils.js +8 -3
- package/lib/esm/test/IModelTestUtils.js.map +1 -1
- package/lib/esm/test/annotations/LeaderGeometry.test.js +178 -2
- package/lib/esm/test/annotations/LeaderGeometry.test.js.map +1 -1
- package/lib/esm/test/annotations/TextAnnotation.test.js +48 -18
- package/lib/esm/test/annotations/TextAnnotation.test.js.map +1 -1
- package/lib/esm/test/ecdb/ECSqlStatement.test.js +177 -0
- package/lib/esm/test/ecdb/ECSqlStatement.test.js.map +1 -1
- package/lib/esm/test/ecdb/QueryReaders.test.js +2 -16
- package/lib/esm/test/ecdb/QueryReaders.test.js.map +1 -1
- package/lib/esm/test/ecsql/src/ECSqlTestRunner.test.js +2 -2
- package/lib/esm/test/ecsql/src/ECSqlTestRunner.test.js.map +1 -1
- package/lib/esm/test/element/ElementRoundTrip.test.js +80 -2
- package/lib/esm/test/element/ElementRoundTrip.test.js.map +1 -1
- package/lib/esm/test/hubaccess/Rebase.test.js +3 -1
- package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -1
- package/lib/esm/test/hubaccess/SemanticRebase.test.js +29 -28
- package/lib/esm/test/hubaccess/SemanticRebase.test.js.map +1 -1
- package/lib/esm/test/hubaccess/SemanticRebaseTestUtils.d.ts +3 -0
- package/lib/esm/test/hubaccess/SemanticRebaseTestUtils.d.ts.map +1 -0
- package/lib/esm/test/hubaccess/SemanticRebaseTestUtils.js +15 -0
- package/lib/esm/test/hubaccess/SemanticRebaseTestUtils.js.map +1 -0
- package/lib/esm/test/imodel/IModelECSql.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelECSql.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelECSql.test.js +367 -0
- package/lib/esm/test/imodel/IModelECSql.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelElements.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelElements.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelElements.test.js +969 -0
- package/lib/esm/test/imodel/IModelElements.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.js +539 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelLifecycle.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelLifecycle.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelLifecycle.test.js +499 -0
- package/lib/esm/test/imodel/IModelLifecycle.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelMetadata.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelMetadata.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelMetadata.test.js +274 -0
- package/lib/esm/test/imodel/IModelMetadata.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelModels.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelModels.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelModels.test.js +357 -0
- package/lib/esm/test/imodel/IModelModels.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelRelationships.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelRelationships.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelRelationships.test.js +407 -0
- package/lib/esm/test/imodel/IModelRelationships.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelTestFixtures.d.ts +53 -0
- package/lib/esm/test/imodel/IModelTestFixtures.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelTestFixtures.js +107 -0
- package/lib/esm/test/imodel/IModelTestFixtures.js.map +1 -0
- package/lib/esm/test/imodel/IModelViews.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelViews.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelViews.test.js +199 -0
- package/lib/esm/test/imodel/IModelViews.test.js.map +1 -0
- package/lib/esm/test/index.d.ts +0 -1
- package/lib/esm/test/index.d.ts.map +1 -1
- package/lib/esm/test/index.js +0 -1
- package/lib/esm/test/index.js.map +1 -1
- package/lib/esm/test/standalone/ChangesetReader.test.js +8 -26
- package/lib/esm/test/standalone/ChangesetReader.test.js.map +1 -1
- package/lib/esm/test/standalone/DgnDomainMergeConflict.test.d.ts +2 -0
- package/lib/esm/test/standalone/DgnDomainMergeConflict.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/DgnDomainMergeConflict.test.js +263 -0
- package/lib/esm/test/standalone/DgnDomainMergeConflict.test.js.map +1 -0
- package/lib/esm/test/standalone/GeometryStream.test.js +1 -3
- package/lib/esm/test/standalone/GeometryStream.test.js.map +1 -1
- package/lib/esm/test/standalone/IModelWrite.test.d.ts.map +1 -1
- package/lib/esm/test/standalone/IModelWrite.test.js +78 -1
- package/lib/esm/test/standalone/IModelWrite.test.js.map +1 -1
- package/lib/esm/test/standalone/IntegrityCheck.test.js +29 -10
- package/lib/esm/test/standalone/IntegrityCheck.test.js.map +1 -1
- package/lib/esm/test/standalone/SQliteChangesetReaderAndChangesetECAdaptor.test.js +23 -0
- package/lib/esm/test/standalone/SQliteChangesetReaderAndChangesetECAdaptor.test.js.map +1 -1
- package/lib/esm/test/standalone/SchemaSyncDb.test.d.ts +2 -0
- package/lib/esm/test/standalone/SchemaSyncDb.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/SchemaSyncDb.test.js +201 -0
- package/lib/esm/test/standalone/SchemaSyncDb.test.js.map +1 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.d.ts +2 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.js +460 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.js.map +1 -0
- package/lib/esm/test/standalone/SnapshotDb.test.js +1 -0
- package/lib/esm/test/standalone/SnapshotDb.test.js.map +1 -1
- package/lib/esm/test/standalone/TxnManager.test.js +108 -8
- package/lib/esm/test/standalone/TxnManager.test.js.map +1 -1
- package/package.json +14 -17
- package/lib/esm/test/RevisionUtility.d.ts +0 -33
- package/lib/esm/test/RevisionUtility.d.ts.map +0 -1
- package/lib/esm/test/RevisionUtility.js +0 -52
- package/lib/esm/test/RevisionUtility.js.map +0 -1
- package/lib/esm/test/imodel/IModel.test.d.ts +0 -2
- package/lib/esm/test/imodel/IModel.test.d.ts.map +0 -1
- package/lib/esm/test/imodel/IModel.test.js +0 -3277
- package/lib/esm/test/imodel/IModel.test.js.map +0 -1
package/lib/esm/SchemaSync.js
CHANGED
|
@@ -5,127 +5,462 @@
|
|
|
5
5
|
/** @packageDocumentation
|
|
6
6
|
* @module SQLiteDb
|
|
7
7
|
*/
|
|
8
|
+
import * as semver from "semver";
|
|
8
9
|
import { CloudSqlite } from "./CloudSqlite";
|
|
9
10
|
import { VersionedSqliteDb } from "./SQLiteDb";
|
|
10
11
|
import { BriefcaseDb, IModelDb } from "./IModelDb";
|
|
11
|
-
import { DbResult, OpenMode } from "@itwin/core-bentley";
|
|
12
|
-
import { IModelError } from "@itwin/core-common";
|
|
12
|
+
import { BentleyError, ChangeSetStatus, DbResult, Guid, Id64, IModelStatus, Logger, OpenMode } from "@itwin/core-bentley";
|
|
13
|
+
import { BriefcaseIdValue, Code, ElementReservationError, IModelError } from "@itwin/core-common";
|
|
13
14
|
import { IModelNative } from "./internal/NativePlatform";
|
|
14
15
|
import { _implicitTxn, _nativeDb } from "./internal/Symbols";
|
|
15
16
|
/** @internal */
|
|
16
17
|
export var SchemaSync;
|
|
17
18
|
(function (SchemaSync) {
|
|
18
|
-
|
|
19
|
+
/** How long a briefcase waits for another briefcase's schema import to finish before giving up.
|
|
20
|
+
*
|
|
21
|
+
* `nRetries` alone does not bound the wait: when the count runs out `CloudSqlite.withLockedDb` asks
|
|
22
|
+
* `onFailure` what to do, and anything other than "stop" restarts the whole cycle. Without an
|
|
23
|
+
* `onFailure` the caller waits forever with no way to give up, so this supplies one.
|
|
24
|
+
*/
|
|
25
|
+
const lockParams = {
|
|
26
|
+
retryDelayMs: 6000,
|
|
27
|
+
nRetries: 10,
|
|
28
|
+
onFailure: async (lockedBy, expires) => {
|
|
29
|
+
Logger.logInfo("SchemaSync", `schema sync container is held by ${lockedBy} until ${expires}; giving up`);
|
|
30
|
+
return "stop";
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const reservedElementsTableName = "reserved_elements";
|
|
34
|
+
const maxLocalIdExclusive = 0x10000000000; // 2^40
|
|
35
|
+
const idSequenceProp = { namespace: "schemasync", name: "nextReservedElementLocalId" };
|
|
19
36
|
/** A CloudSqlite database for synchronizing schema changes across briefcases. */
|
|
20
37
|
class SchemaSyncDb extends VersionedSqliteDb {
|
|
21
|
-
|
|
22
|
-
|
|
38
|
+
_supportsReservations;
|
|
39
|
+
myVersion = "5.0.0";
|
|
40
|
+
createDDL() {
|
|
41
|
+
this.ensureReservedElementsTable();
|
|
42
|
+
}
|
|
43
|
+
openDb(dbName, openMode, container) {
|
|
44
|
+
super.openDb(dbName, openMode, container);
|
|
45
|
+
this._supportsReservations = semver.lte(this.myVersion, semver.minVersion(this.getRequiredVersions().readVersion) ?? "0.0.0");
|
|
46
|
+
}
|
|
47
|
+
ensureReservedElementsTable() {
|
|
48
|
+
if (this._supportsReservations)
|
|
49
|
+
return;
|
|
50
|
+
this.executeSQL(`
|
|
51
|
+
CREATE TABLE IF NOT EXISTS ${reservedElementsTableName} (
|
|
52
|
+
federationGuid BLOB PRIMARY KEY,
|
|
53
|
+
elementId INTEGER NOT NULL UNIQUE,
|
|
54
|
+
ecClassId INTEGER NOT NULL,
|
|
55
|
+
codeSpecId INTEGER NOT NULL,
|
|
56
|
+
codeScope TEXT NOT NULL,
|
|
57
|
+
codeValue TEXT COLLATE NOCASE
|
|
58
|
+
)`);
|
|
59
|
+
this.executeSQL(`CREATE UNIQUE INDEX IF NOT EXISTS idx_reserved_elem_code ON ${reservedElementsTableName}(codeSpecId, codeScope, codeValue)`);
|
|
60
|
+
const minVersion = `^${this.myVersion}`;
|
|
61
|
+
this.setRequiredVersions({ readVersion: minVersion, writeVersion: minVersion });
|
|
62
|
+
this._supportsReservations = true;
|
|
63
|
+
}
|
|
64
|
+
findReservedElement(federationGuid) {
|
|
65
|
+
if (!this._supportsReservations)
|
|
66
|
+
return undefined;
|
|
67
|
+
return this.withPreparedSqliteStatement(`SELECT elementId, ecClassId, codeSpecId, codeScope, codeValue FROM ${reservedElementsTableName} WHERE federationGuid=?`, (stmt) => {
|
|
68
|
+
stmt.bindGuid(1, federationGuid);
|
|
69
|
+
if (!stmt.nextRow())
|
|
70
|
+
return undefined;
|
|
71
|
+
return {
|
|
72
|
+
federationGuid,
|
|
73
|
+
elementId: stmt.getValueId(0),
|
|
74
|
+
ecClassId: stmt.getValueId(1),
|
|
75
|
+
code: new Code({
|
|
76
|
+
spec: stmt.getValueId(2),
|
|
77
|
+
scope: stmt.getValueString(3),
|
|
78
|
+
value: stmt.getValueStringMaybe(4),
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
insertReservedElement(id, elementId) {
|
|
84
|
+
this.withPreparedSqliteStatement(`INSERT INTO ${reservedElementsTableName} (federationGuid, elementId, ecClassId, codeSpecId, codeScope, codeValue) VALUES (?, ?, ?, ?, ?, ?)`, (stmt) => {
|
|
85
|
+
stmt.bindGuid(1, id.federationGuid);
|
|
86
|
+
stmt.bindId(2, elementId);
|
|
87
|
+
stmt.bindId(3, id.ecClassId);
|
|
88
|
+
stmt.bindId(4, id.code.spec);
|
|
89
|
+
stmt.bindString(5, id.code.scope);
|
|
90
|
+
if (id.code.value === "")
|
|
91
|
+
stmt.bindNull(6);
|
|
92
|
+
else
|
|
93
|
+
stmt.bindString(6, id.code.value);
|
|
94
|
+
stmt.stepForWrite();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async reserveElements(elements) {
|
|
98
|
+
this.ensureReservedElementsTable();
|
|
99
|
+
// Insert new reservations as we go, so later entries in `elements` can dedupe against earlier ones
|
|
100
|
+
// through the shared (still-uncommitted) transaction. The caller commits on success and abandons on error.
|
|
101
|
+
let nextLocalId = this.getNextReservedElementLocalId();
|
|
102
|
+
const firstLocalId = nextLocalId;
|
|
103
|
+
for (const def of elements) {
|
|
104
|
+
if (!def.federationGuid || !Guid.isGuid(def.federationGuid)) {
|
|
105
|
+
ElementReservationError.throwError("invalid-reservation", {
|
|
106
|
+
message: "Element reservation requires an explicit, valid federationGuid",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Identity is always the federationGuid. If a reservation already exists for it, it must match exactly.
|
|
110
|
+
const existing = this.findReservedElement(def.federationGuid);
|
|
111
|
+
if (existing) {
|
|
112
|
+
if (!this.existingMatches(existing, def)) {
|
|
113
|
+
ElementReservationError.throwError("reservation-conflict", {
|
|
114
|
+
message: `Element reservation conflict for federationGuid ${existing.federationGuid}: existing row does not match requested class/code`,
|
|
115
|
+
federationGuid: existing.federationGuid,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const elementId = Id64.fromLocalAndBriefcaseIds(nextLocalId, BriefcaseIdValue.SchemaSyncElementReserved);
|
|
121
|
+
this.insertReservedElement(def, elementId);
|
|
122
|
+
// skip a local id for each reserved category because category inserts always trigger a second insert for default subcategory
|
|
123
|
+
nextLocalId += def.isCategory ? 2 : 1;
|
|
124
|
+
if (nextLocalId >= maxLocalIdExclusive) {
|
|
125
|
+
this.abandonChanges();
|
|
126
|
+
ElementReservationError.throwError("id-sequence-exhausted", { message: `SchemaSync reserved-element local-id sequence exhausted` });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (nextLocalId !== firstLocalId)
|
|
130
|
+
this.setNextReservedElementLocalId(nextLocalId);
|
|
131
|
+
}
|
|
132
|
+
existingMatches(existing, id) {
|
|
133
|
+
return existing.federationGuid === id.federationGuid
|
|
134
|
+
&& existing.ecClassId === id.ecClassId
|
|
135
|
+
&& existing.code.equals(id.code);
|
|
136
|
+
}
|
|
137
|
+
getNextReservedElementLocalId() {
|
|
138
|
+
const stored = this[_nativeDb].queryFileProperty(idSequenceProp, true);
|
|
139
|
+
const current = stored ? Number(stored) : 1;
|
|
140
|
+
if (!Number.isInteger(current) || current < 1)
|
|
141
|
+
ElementReservationError.throwError("corrupt-reservation-data", { message: `Corrupt SchemaSync reserved-element local-id counter: '${stored}'` });
|
|
142
|
+
return current;
|
|
143
|
+
}
|
|
144
|
+
setNextReservedElementLocalId(next) {
|
|
145
|
+
this[_nativeDb].saveFileProperty(idSequenceProp, String(next));
|
|
146
|
+
}
|
|
23
147
|
}
|
|
24
148
|
SchemaSync.SchemaSyncDb = SchemaSyncDb;
|
|
25
149
|
const syncProperty = { namespace: "itwinjs", name: "SchemaSync" };
|
|
26
150
|
const defaultDbName = "SchemaSyncDb";
|
|
151
|
+
/** The `containerType` recorded on a BlobContainer that holds an iModel's `SchemaSyncDb`. */
|
|
152
|
+
SchemaSync.containerType = "schema-sync";
|
|
27
153
|
const testSyncCachePropKey = "test.schema_sync.cache_name";
|
|
28
154
|
// for tests only
|
|
29
|
-
|
|
155
|
+
function setTestCache(iModel, cacheName) {
|
|
30
156
|
if (cacheName)
|
|
31
157
|
iModel[_nativeDb].saveLocalValue(testSyncCachePropKey, cacheName);
|
|
32
158
|
else
|
|
33
159
|
iModel[_nativeDb].deleteLocalValue(testSyncCachePropKey);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
nativeDb = new IModelNative.platform.DgnDb();
|
|
43
|
-
nativeDb.openIModel(arg.fileName, OpenMode.Readonly);
|
|
44
|
-
}
|
|
45
|
-
const propsString = nativeDb.queryFileProperty(syncProperty, true);
|
|
46
|
-
if (!propsString)
|
|
47
|
-
throw new Error("iModel does not have a SchemaSyncDb");
|
|
160
|
+
}
|
|
161
|
+
SchemaSync.setTestCache = setTestCache;
|
|
162
|
+
/** Read from an open iModel, or from a closed file opened readonly for the duration of `operation`. */
|
|
163
|
+
function readFromIModel(arg, operation) {
|
|
164
|
+
if (arg instanceof IModelDb)
|
|
165
|
+
return operation(arg[_nativeDb]);
|
|
166
|
+
const nativeDb = new IModelNative.platform.DgnDb();
|
|
167
|
+
nativeDb.openIModel(arg.fileName, OpenMode.Readonly);
|
|
48
168
|
try {
|
|
49
|
-
|
|
50
|
-
const accessToken = await CloudSqlite.requestToken(props);
|
|
51
|
-
const access = new CloudAccess({ ...props, accessToken });
|
|
52
|
-
Object.assign(access.lockParams, lockParams);
|
|
53
|
-
const testSyncCache = nativeDb.queryLocalValue(testSyncCachePropKey);
|
|
54
|
-
if (testSyncCache)
|
|
55
|
-
access.setCache(CloudSqlite.CloudCaches.getCache({ cacheName: testSyncCache }));
|
|
56
|
-
return access;
|
|
169
|
+
return operation(nativeDb);
|
|
57
170
|
}
|
|
58
171
|
finally {
|
|
59
|
-
|
|
60
|
-
|
|
172
|
+
nativeDb.closeFile();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Whether this iModel's ECSchemas are governed by a `SchemaSyncDb`.
|
|
176
|
+
*
|
|
177
|
+
* This is the single question every schema operation branches on. It reads one `be_Prop` row written when
|
|
178
|
+
* schema sync was enabled and carried to every other briefcase by that changeset - no cloud access, and it
|
|
179
|
+
* works on a readonly briefcase, a checkpoint, or a closed file.
|
|
180
|
+
* @note This says nothing about whether the container is reachable, or even named. Use
|
|
181
|
+
* [[queryContainerProps]] for that. The two are separate on purpose: a file that says it is governed by a
|
|
182
|
+
* sync db must never fall back to importing schemas on its own, whatever state the container is in.
|
|
183
|
+
*/
|
|
184
|
+
function isEnabled(arg) {
|
|
185
|
+
return readFromIModel(arg, (nativeDb) => nativeDb.schemaSyncEnabled());
|
|
186
|
+
}
|
|
187
|
+
SchemaSync.isEnabled = isEnabled;
|
|
188
|
+
/** One pass over the values schema sync keeps in the file: which container holds the sync db, plus the
|
|
189
|
+
* test-only cache override. Read together so a closed file is opened once.
|
|
190
|
+
*/
|
|
191
|
+
function readLocalSyncProps(arg) {
|
|
192
|
+
return readFromIModel(arg, (nativeDb) => {
|
|
193
|
+
const propsString = nativeDb.queryFileProperty(syncProperty, true);
|
|
194
|
+
let containerProps;
|
|
195
|
+
if (propsString !== undefined) {
|
|
196
|
+
try {
|
|
197
|
+
containerProps = JSON.parse(propsString);
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
// Deliberately not swallowed into `undefined`: that would read as "schema sync was never
|
|
201
|
+
// enabled" and let this briefcase import schemas on its own.
|
|
202
|
+
throw new IModelError(DbResult.BE_SQLITE_CORRUPT, `iModel names a SchemaSyncDb container but the property cannot be read: ${BentleyError.getErrorMessage(e)}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
containerProps,
|
|
207
|
+
testCacheName: nativeDb.queryLocalValue(testSyncCachePropKey),
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/** The container holding this iModel's `SchemaSyncDb`, or `undefined` if schema sync was never enabled.
|
|
212
|
+
*
|
|
213
|
+
* Local read of the same `be_Prop` file property that [[initializeForIModel]] wrote. Requesting an access
|
|
214
|
+
* token for the container is a separate, asynchronous step.
|
|
215
|
+
*/
|
|
216
|
+
function queryContainerProps(arg) {
|
|
217
|
+
return readLocalSyncProps(arg).containerProps;
|
|
218
|
+
}
|
|
219
|
+
SchemaSync.queryContainerProps = queryContainerProps;
|
|
220
|
+
const sharedAccessByContainer = new Map();
|
|
221
|
+
const sharedAccessByInstance = new WeakMap();
|
|
222
|
+
async function getCloudAccess(arg) {
|
|
223
|
+
const { containerProps, testCacheName } = readLocalSyncProps(arg);
|
|
224
|
+
if (undefined === containerProps)
|
|
225
|
+
throw new IModelError(DbResult.BE_SQLITE_NOTFOUND, "iModel does not have a SchemaSyncDb");
|
|
226
|
+
const sharedAccessKey = JSON.stringify(containerProps) + (testCacheName ?? "");
|
|
227
|
+
let shared = sharedAccessByContainer.get(sharedAccessKey);
|
|
228
|
+
if (undefined === shared) {
|
|
229
|
+
const pending = (async () => {
|
|
230
|
+
const accessToken = await CloudSqlite.requestToken(containerProps);
|
|
231
|
+
const access = new CloudAccess({ ...containerProps, accessToken });
|
|
232
|
+
Object.assign(access.lockParams, lockParams);
|
|
233
|
+
if (testCacheName)
|
|
234
|
+
access.setCache(CloudSqlite.CloudCaches.getCache({ cacheName: testCacheName }));
|
|
235
|
+
const created = { access, key: sharedAccessKey, referenceCount: 0 };
|
|
236
|
+
sharedAccessByInstance.set(access, created);
|
|
237
|
+
return created;
|
|
238
|
+
})();
|
|
239
|
+
sharedAccessByContainer.set(sharedAccessKey, pending);
|
|
240
|
+
try {
|
|
241
|
+
shared = await pending;
|
|
242
|
+
if (sharedAccessByContainer.get(sharedAccessKey) === pending)
|
|
243
|
+
sharedAccessByContainer.set(sharedAccessKey, shared);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
if (sharedAccessByContainer.get(sharedAccessKey) === pending)
|
|
247
|
+
sharedAccessByContainer.delete(sharedAccessKey);
|
|
248
|
+
throw error;
|
|
61
249
|
}
|
|
62
250
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const access = await getCloudAccess(iModel);
|
|
66
|
-
try {
|
|
67
|
-
await access.withLockedDb(args, async () => operation(access));
|
|
251
|
+
else if (shared instanceof Promise) {
|
|
252
|
+
shared = await shared;
|
|
68
253
|
}
|
|
69
|
-
|
|
254
|
+
++shared.referenceCount;
|
|
255
|
+
return shared.access;
|
|
256
|
+
}
|
|
257
|
+
SchemaSync.getCloudAccess = getCloudAccess;
|
|
258
|
+
/** Release an access obtained through [[getCloudAccess]]. */
|
|
259
|
+
function releaseCloudAccess(access) {
|
|
260
|
+
const shared = sharedAccessByInstance.get(access);
|
|
261
|
+
if (undefined === shared) {
|
|
70
262
|
access.close();
|
|
263
|
+
return;
|
|
71
264
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
265
|
+
if (--shared.referenceCount > 0)
|
|
266
|
+
return;
|
|
267
|
+
sharedAccessByInstance.delete(access);
|
|
268
|
+
if (sharedAccessByContainer.get(shared.key) === shared)
|
|
269
|
+
sharedAccessByContainer.delete(shared.key);
|
|
270
|
+
access.close();
|
|
271
|
+
}
|
|
272
|
+
SchemaSync.releaseCloudAccess = releaseCloudAccess;
|
|
273
|
+
async function withLockedAccess(iModel, args, operation) {
|
|
274
|
+
const access = await SchemaSync.getCloudAccess(iModel);
|
|
77
275
|
try {
|
|
78
|
-
await operation(access);
|
|
276
|
+
await access.withLockedDb(args, async () => operation(access));
|
|
79
277
|
}
|
|
80
278
|
finally {
|
|
81
|
-
access
|
|
279
|
+
releaseCloudAccess(access);
|
|
82
280
|
}
|
|
83
|
-
}
|
|
84
|
-
SchemaSync.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (iModel
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
iModel[_nativeDb].schemaSyncPull(schemaSyncDbUri);
|
|
94
|
-
iModel[_implicitTxn].saveChanges("schema synchronized with cloud container");
|
|
95
|
-
});
|
|
281
|
+
}
|
|
282
|
+
SchemaSync.withLockedAccess = withLockedAccess;
|
|
283
|
+
/** Build the tables and indexes the briefcase's `ec_` rows describe. A merged schema changeset carries
|
|
284
|
+
* those rows but no DDL, so the physical columns are missing until this runs. Needs no cloud access.
|
|
285
|
+
*/
|
|
286
|
+
function updateDbSchema(iModel) {
|
|
287
|
+
if (isEnabled(iModel) && !iModel.isReadonly) {
|
|
288
|
+
iModel.clearCaches();
|
|
289
|
+
iModel[_nativeDb].schemaSyncUpdateDbSchema();
|
|
290
|
+
iModel[_implicitTxn].saveChanges("materialized db schema from ec_ tables");
|
|
96
291
|
}
|
|
97
|
-
}
|
|
98
|
-
SchemaSync.
|
|
99
|
-
|
|
292
|
+
}
|
|
293
|
+
SchemaSync.updateDbSchema = updateDbSchema;
|
|
294
|
+
/** Whether a failed [[IModelDb.importSchemas]] can be retried through the upgrade path.
|
|
295
|
+
*
|
|
296
|
+
* The update tier refuses two kinds of change: one that has to move data between columns, and one
|
|
297
|
+
* that destroys instances or property values. They arrive as different statuses because they are
|
|
298
|
+
* different changes, but they mean one thing to a caller - this needs
|
|
299
|
+
* [[BriefcaseDb.upgradeSchemas]], which takes the exclusive schema lock.
|
|
300
|
+
*
|
|
301
|
+
* The retry is the app's decision, not the platform's: taking that lock disturbs everyone else, so
|
|
302
|
+
* only the app knows whether to do it now, schedule it, or tell the user.
|
|
303
|
+
* ```ts
|
|
304
|
+
* try {
|
|
305
|
+
* await db.importSchemas(files);
|
|
306
|
+
* } catch (e) {
|
|
307
|
+
* if (!SchemaSync.requiresUpgrade(e)) throw e;
|
|
308
|
+
* await db.upgradeSchemas(files, { description: "..." });
|
|
309
|
+
* }
|
|
310
|
+
* ```
|
|
311
|
+
* @internal
|
|
312
|
+
*/
|
|
313
|
+
function requiresUpgrade(error) {
|
|
314
|
+
const errorNumber = error?.errorNumber;
|
|
315
|
+
return errorNumber === DbResult.BE_SQLITE_ERROR_DataTransformRequired
|
|
316
|
+
|| errorNumber === DbResult.BE_SQLITE_ERROR_DataDeletionRequired;
|
|
317
|
+
}
|
|
318
|
+
SchemaSync.requiresUpgrade = requiresUpgrade;
|
|
319
|
+
/** Create a cloud container to hold this iModel's `SchemaSyncDb`, and initialize it as empty.
|
|
320
|
+
*
|
|
321
|
+
* The container is scoped to the iModel, so the service deletes it when the iModel is deleted. Pass the
|
|
322
|
+
* returned props to [[initializeForIModel]] - the two calls together are what enables schema sync on an
|
|
323
|
+
* iModel that was created without it.
|
|
324
|
+
* @note The current user must be authorized to create containers for the iTwin.
|
|
325
|
+
*/
|
|
326
|
+
async function createContainerForIModel(arg) {
|
|
100
327
|
const iModel = arg.iModel;
|
|
328
|
+
const iTwinId = iModel.iTwinId;
|
|
329
|
+
if (undefined === iTwinId)
|
|
330
|
+
throw new IModelError(DbResult.BE_SQLITE_ERROR, "Cannot create a SchemaSync container for an iModel that has no iTwin");
|
|
331
|
+
return CloudAccess.createNewContainer({
|
|
332
|
+
scope: { iTwinId, iModelId: iModel.iModelId },
|
|
333
|
+
metadata: { label: arg.label ?? `SchemaSync for ${iModel.name}`, description: arg.description },
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
SchemaSync.createContainerForIModel = createContainerForIModel;
|
|
337
|
+
/** Refuse initialization before a caller provisions a container, then hold the exclusive schema lock and pull to the tip. */
|
|
338
|
+
async function prepareToInitializeForIModel(iModel, overrideContainer) {
|
|
101
339
|
const briefcase = iModel instanceof BriefcaseDb ? iModel : undefined;
|
|
340
|
+
if (briefcase && !briefcase.locks.isServerBased)
|
|
341
|
+
throw new IModelError(DbResult.BE_SQLITE_ERROR, "Cannot enable SchemaSync without server-based locking");
|
|
342
|
+
if (briefcase && (iModel[_nativeDb].hasUnsavedChanges() || briefcase.txns.hasLocalChanges))
|
|
343
|
+
throw new IModelError(ChangeSetStatus.HasLocalChanges, "Cannot enable SchemaSync while there are local changes");
|
|
344
|
+
const assertContainerCanBeInitialized = () => {
|
|
345
|
+
const localInfo = iModel[_nativeDb].schemaSyncGetLocalDbInfo();
|
|
346
|
+
if (localInfo && !overrideContainer)
|
|
347
|
+
throw new IModelError(DbResult.BE_SQLITE_ERROR, `Local db already initialized to schema sync (container-id: ${localInfo.id})`);
|
|
348
|
+
};
|
|
349
|
+
assertContainerCanBeInitialized();
|
|
102
350
|
await iModel.acquireSchemaLock();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
351
|
+
await briefcase?.pullChanges();
|
|
352
|
+
assertContainerCanBeInitialized();
|
|
353
|
+
return briefcase;
|
|
354
|
+
}
|
|
355
|
+
async function initializeAfterPreflight(arg, briefcase) {
|
|
356
|
+
const props = { baseUri: arg.containerProps.baseUri, containerId: arg.containerProps.containerId, storageType: arg.containerProps.storageType }; // sanitize to only known properties
|
|
357
|
+
const iModel = arg.iModel;
|
|
358
|
+
const description = arg.overrideContainer
|
|
359
|
+
? `Overriding SchemaSync for iModel with container-id: ${props.containerId}`
|
|
360
|
+
: `Enable SchemaSync for iModel with container-id: ${props.containerId}`;
|
|
361
|
+
const txnBeforeInit = briefcase?.txns.getCurrentTxnId();
|
|
109
362
|
try {
|
|
110
363
|
iModel[_implicitTxn].saveFileProperty(syncProperty, JSON.stringify(props));
|
|
111
|
-
|
|
112
|
-
iModel
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
364
|
+
try {
|
|
365
|
+
await withLockedAccess(iModel, { operationName: "initialize schemaSync", openMode: OpenMode.Readonly }, async (syncAccess) => {
|
|
366
|
+
iModel[_nativeDb].schemaSyncInit(syncAccess.getUri(), props.containerId, arg.overrideContainer ?? false);
|
|
367
|
+
iModel[_implicitTxn].saveChanges(description);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
iModel[_implicitTxn].abandonChanges();
|
|
372
|
+
if (briefcase && txnBeforeInit !== undefined && briefcase.txns.getCurrentTxnId() !== txnBeforeInit) {
|
|
373
|
+
const status = briefcase[_nativeDb].cancelTo(txnBeforeInit, true);
|
|
374
|
+
if (status !== IModelStatus.Success)
|
|
375
|
+
Logger.logError("SchemaSync", `Failed to roll back schema sync initialization after the container upload failed: ${IModelStatus[status] ?? status}`);
|
|
376
|
+
}
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
// Upload the initialized container before publishing the property that tells every briefcase to use it.
|
|
380
|
+
// If the push fails, this briefcase keeps the local txn and exclusive schema lock so the push can be retried.
|
|
381
|
+
await briefcase?.pushChanges({ description });
|
|
118
382
|
}
|
|
119
383
|
finally {
|
|
120
384
|
iModel[_implicitTxn].abandonChanges();
|
|
121
385
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
386
|
+
await iModel.initializeSharedElementReservations();
|
|
387
|
+
}
|
|
388
|
+
/** Enable schema sync for an iModel, seeding the container from this briefcase.
|
|
389
|
+
* @note Takes the exclusive schema lock, refuses local changes, and pulls the briefcase to the tip before writing the container.
|
|
390
|
+
* If this operation fails after acquiring the schema lock, it leaves the lock held.
|
|
391
|
+
*/
|
|
392
|
+
async function initializeForIModel(arg) {
|
|
393
|
+
const briefcase = await prepareToInitializeForIModel(arg.iModel, arg.overrideContainer ?? false);
|
|
394
|
+
await initializeAfterPreflight(arg, briefcase);
|
|
395
|
+
}
|
|
396
|
+
SchemaSync.initializeForIModel = initializeForIModel;
|
|
397
|
+
const nativeRepairScope = {
|
|
398
|
+
schemaMetadata: 0,
|
|
399
|
+
schemaMetadataAndProfile: 1,
|
|
128
400
|
};
|
|
401
|
+
/** Restore the schema-owned portion of the SchemaSyncDb from a briefcase at the tip of the timeline.
|
|
402
|
+
*
|
|
403
|
+
* This operation takes the exclusive schema lock and refuses to pull or modify the briefcase. Element
|
|
404
|
+
* reservations and all other target-only state in the SchemaSyncDb are preserved.
|
|
405
|
+
* @alpha
|
|
406
|
+
*/
|
|
407
|
+
async function repairForIModel(arg) {
|
|
408
|
+
const iModel = arg.iModel;
|
|
409
|
+
if (!isEnabled(iModel))
|
|
410
|
+
throw new IModelError(DbResult.BE_SQLITE_ERROR, "Cannot repair SchemaSync because it is not enabled for this iModel");
|
|
411
|
+
if (iModel[_nativeDb].hasUnsavedChanges() || iModel.txns.hasLocalChanges)
|
|
412
|
+
throw new IModelError(ChangeSetStatus.HasLocalChanges, "Cannot repair SchemaSync while there are local changes");
|
|
413
|
+
if (!iModel.locks.isServerBased)
|
|
414
|
+
throw new IModelError(DbResult.BE_SQLITE_ERROR, "Cannot repair SchemaSync without server-based locking");
|
|
415
|
+
if (arg.scope !== undefined && arg.scope !== "schemaMetadata" && arg.scope !== "schemaMetadataAndProfile")
|
|
416
|
+
throw new IModelError(DbResult.BE_SQLITE_ERROR, "Unknown SchemaSync repair scope");
|
|
417
|
+
const briefcaseManagerModule = await import("./BriefcaseManager");
|
|
418
|
+
const assertBriefcaseIsAtTip = async () => {
|
|
419
|
+
const latestChangeset = await briefcaseManagerModule.BriefcaseManager.getLatestChangeset({ iModelId: iModel.iModelId });
|
|
420
|
+
if (latestChangeset.id !== iModel.changeset.id)
|
|
421
|
+
throw new IModelError(DbResult.BE_SQLITE_ERROR, "Cannot repair SchemaSync from a briefcase that is not at the tip of the iModel timeline");
|
|
422
|
+
};
|
|
423
|
+
// Avoid asking Hub for an exclusive lock it cannot grant to a briefcase that first needs to pull.
|
|
424
|
+
await assertBriefcaseIsAtTip();
|
|
425
|
+
const schemaLockWasHeld = iModel.holdsSchemaLock;
|
|
426
|
+
if (!schemaLockWasHeld)
|
|
427
|
+
await iModel.acquireSchemaLock();
|
|
428
|
+
try {
|
|
429
|
+
// Close the race between the preflight query and acquiring the exclusive lock.
|
|
430
|
+
await assertBriefcaseIsAtTip();
|
|
431
|
+
const repairScope = nativeRepairScope[arg.scope ?? "schemaMetadata"];
|
|
432
|
+
await withLockedAccess(iModel, { openMode: OpenMode.Readonly, operationName: "repair schema sync" }, async (syncAccess) => {
|
|
433
|
+
const nativeDb = iModel[_nativeDb];
|
|
434
|
+
nativeDb.schemaSyncRepair(syncAccess.getUri(), repairScope);
|
|
435
|
+
});
|
|
436
|
+
Logger.logInfo("SchemaSync", `Repaired SchemaSyncDb from changeset ${iModel.changeset.id || "0"}`);
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
if (!schemaLockWasHeld)
|
|
440
|
+
await iModel.locks.abandonAllLocks();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
SchemaSync.repairForIModel = repairForIModel;
|
|
444
|
+
/** Turn schema sync on for an iModel: create the container if needed, record it, and seed it.
|
|
445
|
+
*
|
|
446
|
+
* The single call callers should use. [[createContainerForIModel]] and [[initializeForIModel]] have
|
|
447
|
+
* to happen in this order, and an iModel left between the two is one that names a container nothing
|
|
448
|
+
* has seeded.
|
|
449
|
+
* @returns the container props recorded on the iModel.
|
|
450
|
+
* @note Takes the exclusive schema lock and pushes, same protocol as [[BriefcaseDb.upgradeSchemas]] -
|
|
451
|
+
* every operation that changes how a file is governed uses it. If this operation fails after acquiring the schema lock,
|
|
452
|
+
* it leaves the lock held.
|
|
453
|
+
*/
|
|
454
|
+
async function enableForIModel(arg) {
|
|
455
|
+
if (undefined === arg.containerProps && undefined === arg.iModel.iTwinId)
|
|
456
|
+
throw new IModelError(DbResult.BE_SQLITE_ERROR, "Cannot create a SchemaSync container for an iModel that has no iTwin");
|
|
457
|
+
const briefcase = await prepareToInitializeForIModel(arg.iModel, arg.overrideContainer ?? false);
|
|
458
|
+
const containerProps = arg.containerProps
|
|
459
|
+
?? await createContainerForIModel({ iModel: arg.iModel, label: arg.label, description: arg.description });
|
|
460
|
+
await initializeAfterPreflight({ iModel: arg.iModel, containerProps, overrideContainer: arg.overrideContainer }, briefcase);
|
|
461
|
+
return containerProps;
|
|
462
|
+
}
|
|
463
|
+
SchemaSync.enableForIModel = enableForIModel;
|
|
129
464
|
/** Provides access to a cloud-based `SchemaSyncDb` to hold ECSchemas. */
|
|
130
465
|
class CloudAccess extends CloudSqlite.DbAccess {
|
|
131
466
|
constructor(props) {
|
|
@@ -142,6 +477,12 @@ export var SchemaSync;
|
|
|
142
477
|
static async initializeDb(props) {
|
|
143
478
|
return super._initializeDb({ props, dbType: SchemaSyncDb, dbName: defaultDbName });
|
|
144
479
|
}
|
|
480
|
+
/** Create and initialize a new `schema-sync` BlobContainer to hold a `SchemaSyncDb`. */
|
|
481
|
+
static async createNewContainer(args) {
|
|
482
|
+
const props = await this.createBlobContainer({ scope: args.scope, metadata: { ...args.metadata, containerType: SchemaSync.containerType } });
|
|
483
|
+
await this.initializeDb(props);
|
|
484
|
+
return props;
|
|
485
|
+
}
|
|
145
486
|
}
|
|
146
487
|
SchemaSync.CloudAccess = CloudAccess;
|
|
147
488
|
})(SchemaSync || (SchemaSync = {}));
|