@itwin/core-backend 5.14.0-dev.2 → 5.14.0-dev.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/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/CloudSqlite.d.ts.map +1 -1
- package/lib/cjs/CloudSqlite.js +9 -2
- package/lib/cjs/CloudSqlite.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/IModelDb.d.ts +42 -7
- package/lib/cjs/IModelDb.d.ts.map +1 -1
- package/lib/cjs/IModelDb.js +175 -74
- package/lib/cjs/IModelDb.js.map +1 -1
- package/lib/cjs/SchemaSync.d.ts +126 -18
- package/lib/cjs/SchemaSync.d.ts.map +1 -1
- package/lib/cjs/SchemaSync.js +303 -81
- package/lib/cjs/SchemaSync.js.map +1 -1
- package/lib/cjs/TxnManager.d.ts.map +1 -1
- package/lib/cjs/TxnManager.js +2 -5
- package/lib/cjs/TxnManager.js.map +1 -1
- package/lib/cjs/internal/SchemaSyncReservations.d.ts.map +1 -1
- package/lib/cjs/internal/SchemaSyncReservations.js +13 -3
- package/lib/cjs/internal/SchemaSyncReservations.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/CloudSqlite.d.ts.map +1 -1
- package/lib/esm/CloudSqlite.js +10 -3
- package/lib/esm/CloudSqlite.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/IModelDb.d.ts +42 -7
- package/lib/esm/IModelDb.d.ts.map +1 -1
- package/lib/esm/IModelDb.js +175 -74
- package/lib/esm/IModelDb.js.map +1 -1
- package/lib/esm/SchemaSync.d.ts +126 -18
- package/lib/esm/SchemaSync.d.ts.map +1 -1
- package/lib/esm/SchemaSync.js +304 -82
- package/lib/esm/SchemaSync.js.map +1 -1
- package/lib/esm/TxnManager.d.ts.map +1 -1
- package/lib/esm/TxnManager.js +2 -5
- package/lib/esm/TxnManager.js.map +1 -1
- package/lib/esm/internal/SchemaSyncReservations.d.ts.map +1 -1
- package/lib/esm/internal/SchemaSyncReservations.js +13 -3
- package/lib/esm/internal/SchemaSyncReservations.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/standalone/SchemaSyncDb.test.js +5 -25
- package/lib/esm/test/standalone/SchemaSyncDb.test.js.map +1 -1
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.js +1 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.js.map +1 -1
- package/package.json +13 -13
package/lib/cjs/SchemaSync.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CloudSqlite } from "./CloudSqlite";
|
|
2
2
|
import { SQLiteDb, VersionedSqliteDb } from "./SQLiteDb";
|
|
3
|
-
import { IModelDb } from "./IModelDb";
|
|
3
|
+
import { BriefcaseDb, IModelDb } from "./IModelDb";
|
|
4
4
|
import { GuidString, Id64String, OpenMode } from "@itwin/core-bentley";
|
|
5
5
|
import { Code, LocalFileName } from "@itwin/core-common";
|
|
6
|
+
import type { BlobContainer } from "./BlobContainerService";
|
|
6
7
|
/** @internal */
|
|
7
8
|
export declare namespace SchemaSync {
|
|
8
9
|
/** Identifies an element to be reserved in a `SchemaSyncDb`. @internal */
|
|
@@ -21,13 +22,13 @@ export declare namespace SchemaSync {
|
|
|
21
22
|
findReservedElement(federationGuid: GuidString): ReservedElement | undefined;
|
|
22
23
|
}
|
|
23
24
|
interface WriteMethods {
|
|
24
|
-
/** Reserve the specified elements in the `SchemaSyncDb`.
|
|
25
|
+
/** Reserve the specified elements in the `SchemaSyncDb`. Throws if any requested reservation conflicts with an existing reservation. */
|
|
25
26
|
reserveElements(identities: ProposedElementReservation[]): Promise<void>;
|
|
26
27
|
}
|
|
27
28
|
/** A CloudSqlite database for synchronizing schema changes across briefcases. */
|
|
28
29
|
class SchemaSyncDb extends VersionedSqliteDb implements ReadMethods, WriteMethods {
|
|
29
30
|
private _supportsReservations?;
|
|
30
|
-
readonly myVersion = "
|
|
31
|
+
readonly myVersion = "5.0.0";
|
|
31
32
|
protected createDDL(): void;
|
|
32
33
|
openDb(dbName: string, openMode: OpenMode | SQLiteDb.OpenParams, container?: CloudSqlite.CloudContainer): void;
|
|
33
34
|
private ensureReservedElementsTable;
|
|
@@ -38,28 +39,133 @@ export declare namespace SchemaSync {
|
|
|
38
39
|
private getNextReservedElementLocalId;
|
|
39
40
|
private setNextReservedElementLocalId;
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
const
|
|
42
|
+
/** The `containerType` recorded on a BlobContainer that holds an iModel's `SchemaSyncDb`. */
|
|
43
|
+
const containerType = "schema-sync";
|
|
44
|
+
function setTestCache(iModel: IModelDb, cacheName?: string): void;
|
|
45
|
+
/** Either an open iModel or the name of a closed briefcase file. Every local read below accepts both, so
|
|
46
|
+
* a caller that has only a file name does not have to open a `BriefcaseDb` to ask a question about it.
|
|
47
|
+
*/
|
|
48
|
+
type IModelOrFileName = IModelDb | {
|
|
43
49
|
readonly fileName: LocalFileName;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
};
|
|
51
|
+
/** Whether this iModel's ECSchemas are governed by a `SchemaSyncDb`.
|
|
52
|
+
*
|
|
53
|
+
* This is the single question every schema operation branches on. It reads one `be_Prop` row written when
|
|
54
|
+
* schema sync was enabled and carried to every other briefcase by that changeset - no cloud access, and it
|
|
55
|
+
* works on a readonly briefcase, a checkpoint, or a closed file.
|
|
56
|
+
* @note This says nothing about whether the container is reachable, or even named. Use
|
|
57
|
+
* [[queryContainerProps]] for that. The two are separate on purpose: a file that says it is governed by a
|
|
58
|
+
* sync db must never fall back to importing schemas on its own, whatever state the container is in.
|
|
59
|
+
*/
|
|
60
|
+
function isEnabled(arg: IModelOrFileName): boolean;
|
|
61
|
+
/** The container holding this iModel's `SchemaSyncDb`, or `undefined` if schema sync was never enabled.
|
|
62
|
+
*
|
|
63
|
+
* Local read of the same `be_Prop` file property that [[initializeForIModel]] wrote. Requesting an access
|
|
64
|
+
* token for the container is a separate, asynchronous step.
|
|
65
|
+
*/
|
|
66
|
+
function queryContainerProps(arg: IModelOrFileName): CloudSqlite.ContainerProps | undefined;
|
|
67
|
+
function getCloudAccess(arg: IModelOrFileName): Promise<CloudAccess>;
|
|
68
|
+
/** Release an access obtained through [[getCloudAccess]]. */
|
|
69
|
+
function releaseCloudAccess(access: CloudAccess): void;
|
|
70
|
+
/** Arguments for [[withLockedAccess]]. */
|
|
71
|
+
interface WithLockedAccessArgs {
|
|
48
72
|
operationName: string;
|
|
49
73
|
openMode?: OpenMode;
|
|
50
74
|
user?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
}
|
|
76
|
+
function withLockedAccess(iModel: IModelOrFileName, args: WithLockedAccessArgs, operation: (access: CloudAccess) => Promise<void>): Promise<void>;
|
|
77
|
+
/** Build the tables and indexes the briefcase's `ec_` rows describe. A merged schema changeset carries
|
|
78
|
+
* those rows but no DDL, so the physical columns are missing until this runs. Needs no cloud access.
|
|
79
|
+
*/
|
|
80
|
+
function updateDbSchema(iModel: IModelDb): void;
|
|
81
|
+
/** Whether a failed [[IModelDb.importSchemas]] can be retried through the upgrade path.
|
|
82
|
+
*
|
|
83
|
+
* The update tier refuses two kinds of change: one that has to move data between columns, and one
|
|
84
|
+
* that destroys instances or property values. They arrive as different statuses because they are
|
|
85
|
+
* different changes, but they mean one thing to a caller - this needs
|
|
86
|
+
* [[BriefcaseDb.upgradeSchemas]], which takes the exclusive schema lock.
|
|
87
|
+
*
|
|
88
|
+
* The retry is the app's decision, not the platform's: taking that lock disturbs everyone else, so
|
|
89
|
+
* only the app knows whether to do it now, schedule it, or tell the user.
|
|
90
|
+
* ```ts
|
|
91
|
+
* try {
|
|
92
|
+
* await db.importSchemas(files);
|
|
93
|
+
* } catch (e) {
|
|
94
|
+
* if (!SchemaSync.requiresUpgrade(e)) throw e;
|
|
95
|
+
* await db.upgradeSchemas(files, { description: "..." });
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
function requiresUpgrade(error: unknown): boolean;
|
|
101
|
+
/** Arguments for [[createContainerForIModel]]. */
|
|
102
|
+
interface CreateContainerForIModelArgs {
|
|
103
|
+
iModel: IModelDb;
|
|
104
|
+
label?: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
}
|
|
107
|
+
/** Create a cloud container to hold this iModel's `SchemaSyncDb`, and initialize it as empty.
|
|
108
|
+
*
|
|
109
|
+
* The container is scoped to the iModel, so the service deletes it when the iModel is deleted. Pass the
|
|
110
|
+
* returned props to [[initializeForIModel]] - the two calls together are what enables schema sync on an
|
|
111
|
+
* iModel that was created without it.
|
|
112
|
+
* @note The current user must be authorized to create containers for the iTwin.
|
|
113
|
+
*/
|
|
114
|
+
function createContainerForIModel(arg: CreateContainerForIModelArgs): Promise<CloudSqlite.ContainerProps>;
|
|
115
|
+
/** Arguments for [[initializeForIModel]]. */
|
|
116
|
+
interface InitializeForIModelArgs {
|
|
59
117
|
iModel: IModelDb;
|
|
60
118
|
containerProps: CloudSqlite.ContainerProps;
|
|
119
|
+
/** Replace the container already recorded on this iModel instead of failing. */
|
|
61
120
|
overrideContainer?: boolean;
|
|
62
|
-
}
|
|
121
|
+
}
|
|
122
|
+
/** Enable schema sync for an iModel, seeding the container from this briefcase.
|
|
123
|
+
* @note Takes the exclusive schema lock, refuses local changes, and pulls the briefcase to the tip before writing the container.
|
|
124
|
+
* If this operation fails after acquiring the schema lock, it leaves the lock held.
|
|
125
|
+
*/
|
|
126
|
+
function initializeForIModel(arg: InitializeForIModelArgs): Promise<void>;
|
|
127
|
+
/** The part of a SchemaSyncDb to restore from an authoritative briefcase. @alpha */
|
|
128
|
+
type RepairScope = "schemaMetadata" | "schemaMetadataAndProfile";
|
|
129
|
+
/** Arguments for [[repairForIModel]]. @alpha */
|
|
130
|
+
interface RepairForIModelArgs {
|
|
131
|
+
/** A clean SchemaSync-enabled briefcase at the tip of the iModel timeline. */
|
|
132
|
+
iModel: BriefcaseDb;
|
|
133
|
+
/** Include the schema-owned EC and BeSQLite profile table definitions. */
|
|
134
|
+
scope?: RepairScope;
|
|
135
|
+
}
|
|
136
|
+
/** Restore the schema-owned portion of the SchemaSyncDb from a briefcase at the tip of the timeline.
|
|
137
|
+
*
|
|
138
|
+
* This operation takes the exclusive schema lock and refuses to pull or modify the briefcase. Element
|
|
139
|
+
* reservations and all other target-only state in the SchemaSyncDb are preserved.
|
|
140
|
+
* @alpha
|
|
141
|
+
*/
|
|
142
|
+
function repairForIModel(arg: RepairForIModelArgs): Promise<void>;
|
|
143
|
+
/** Arguments for [[enableForIModel]]. */
|
|
144
|
+
interface EnableForIModelArgs {
|
|
145
|
+
iModel: IModelDb;
|
|
146
|
+
/** An existing container to use. When omitted one is created for this iModel. */
|
|
147
|
+
containerProps?: CloudSqlite.ContainerProps;
|
|
148
|
+
/** Replace the container already recorded on this iModel instead of failing. */
|
|
149
|
+
overrideContainer?: boolean;
|
|
150
|
+
label?: string;
|
|
151
|
+
description?: string;
|
|
152
|
+
}
|
|
153
|
+
/** Turn schema sync on for an iModel: create the container if needed, record it, and seed it.
|
|
154
|
+
*
|
|
155
|
+
* The single call callers should use. [[createContainerForIModel]] and [[initializeForIModel]] have
|
|
156
|
+
* to happen in this order, and an iModel left between the two is one that names a container nothing
|
|
157
|
+
* has seeded.
|
|
158
|
+
* @returns the container props recorded on the iModel.
|
|
159
|
+
* @note Takes the exclusive schema lock and pushes, same protocol as [[BriefcaseDb.upgradeSchemas]] -
|
|
160
|
+
* every operation that changes how a file is governed uses it. If this operation fails after acquiring the schema lock,
|
|
161
|
+
* it leaves the lock held.
|
|
162
|
+
*/
|
|
163
|
+
function enableForIModel(arg: EnableForIModelArgs): Promise<CloudSqlite.ContainerProps>;
|
|
164
|
+
/** Arguments for [[CloudAccess.createNewContainer]]. */
|
|
165
|
+
interface CreateNewContainerProps {
|
|
166
|
+
scope: BlobContainer.Scope;
|
|
167
|
+
metadata: Omit<BlobContainer.Metadata, "containerType">;
|
|
168
|
+
}
|
|
63
169
|
/** Provides access to a cloud-based `SchemaSyncDb` to hold ECSchemas. */
|
|
64
170
|
class CloudAccess extends CloudSqlite.DbAccess<SchemaSyncDb, ReadMethods, WriteMethods> {
|
|
65
171
|
constructor(props: CloudSqlite.ContainerAccessProps);
|
|
@@ -70,6 +176,8 @@ export declare namespace SchemaSync {
|
|
|
70
176
|
* @note this deletes any existing content in the container.
|
|
71
177
|
*/
|
|
72
178
|
static initializeDb(props: CloudSqlite.ContainerProps): Promise<void>;
|
|
179
|
+
/** Create and initialize a new `schema-sync` BlobContainer to hold a `SchemaSyncDb`. */
|
|
180
|
+
static createNewContainer(args: CreateNewContainerProps): Promise<CloudSqlite.ContainerProps>;
|
|
73
181
|
}
|
|
74
182
|
}
|
|
75
183
|
//# sourceMappingURL=SchemaSync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SchemaSync.d.ts","sourceRoot":"","sources":["../../src/SchemaSync.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,
|
|
1
|
+
{"version":3,"file":"SchemaSync.d.ts","sourceRoot":"","sources":["../../src/SchemaSync.ts"],"names":[],"mappings":"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"}
|
package/lib/cjs/SchemaSync.js
CHANGED
|
@@ -19,14 +19,27 @@ const Symbols_1 = require("./internal/Symbols");
|
|
|
19
19
|
/** @internal */
|
|
20
20
|
var SchemaSync;
|
|
21
21
|
(function (SchemaSync) {
|
|
22
|
-
|
|
22
|
+
/** How long a briefcase waits for another briefcase's schema import to finish before giving up.
|
|
23
|
+
*
|
|
24
|
+
* `nRetries` alone does not bound the wait: when the count runs out `CloudSqlite.withLockedDb` asks
|
|
25
|
+
* `onFailure` what to do, and anything other than "stop" restarts the whole cycle. Without an
|
|
26
|
+
* `onFailure` the caller waits forever with no way to give up, so this supplies one.
|
|
27
|
+
*/
|
|
28
|
+
const lockParams = {
|
|
29
|
+
retryDelayMs: 6000,
|
|
30
|
+
nRetries: 10,
|
|
31
|
+
onFailure: async (lockedBy, expires) => {
|
|
32
|
+
core_bentley_1.Logger.logInfo("SchemaSync", `schema sync container is held by ${lockedBy} until ${expires}; giving up`);
|
|
33
|
+
return "stop";
|
|
34
|
+
},
|
|
35
|
+
};
|
|
23
36
|
const reservedElementsTableName = "reserved_elements";
|
|
24
37
|
const maxLocalIdExclusive = 0x10000000000; // 2^40
|
|
25
38
|
const idSequenceProp = { namespace: "schemasync", name: "nextReservedElementLocalId" };
|
|
26
39
|
/** A CloudSqlite database for synchronizing schema changes across briefcases. */
|
|
27
40
|
class SchemaSyncDb extends SQLiteDb_1.VersionedSqliteDb {
|
|
28
41
|
_supportsReservations;
|
|
29
|
-
myVersion = "
|
|
42
|
+
myVersion = "5.0.0";
|
|
30
43
|
createDDL() {
|
|
31
44
|
this.ensureReservedElementsTable();
|
|
32
45
|
}
|
|
@@ -138,116 +151,319 @@ var SchemaSync;
|
|
|
138
151
|
SchemaSync.SchemaSyncDb = SchemaSyncDb;
|
|
139
152
|
const syncProperty = { namespace: "itwinjs", name: "SchemaSync" };
|
|
140
153
|
const defaultDbName = "SchemaSyncDb";
|
|
154
|
+
/** The `containerType` recorded on a BlobContainer that holds an iModel's `SchemaSyncDb`. */
|
|
155
|
+
SchemaSync.containerType = "schema-sync";
|
|
141
156
|
const testSyncCachePropKey = "test.schema_sync.cache_name";
|
|
142
157
|
// for tests only
|
|
143
|
-
|
|
158
|
+
function setTestCache(iModel, cacheName) {
|
|
144
159
|
if (cacheName)
|
|
145
160
|
iModel[Symbols_1._nativeDb].saveLocalValue(testSyncCachePropKey, cacheName);
|
|
146
161
|
else
|
|
147
162
|
iModel[Symbols_1._nativeDb].deleteLocalValue(testSyncCachePropKey);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
else {
|
|
157
|
-
nativeDb = new NativePlatform_1.IModelNative.platform.DgnDb();
|
|
158
|
-
nativeDb.openIModel(arg.fileName, core_bentley_1.OpenMode.Readonly);
|
|
159
|
-
}
|
|
160
|
-
const testSyncCache = nativeDb.queryLocalValue(testSyncCachePropKey);
|
|
161
|
-
const propsString = nativeDb.queryFileProperty(syncProperty, true);
|
|
162
|
-
if (!propsString)
|
|
163
|
-
throw new Error("iModel does not have a SchemaSyncDb");
|
|
163
|
+
}
|
|
164
|
+
SchemaSync.setTestCache = setTestCache;
|
|
165
|
+
/** Read from an open iModel, or from a closed file opened readonly for the duration of `operation`. */
|
|
166
|
+
function readFromIModel(arg, operation) {
|
|
167
|
+
if (arg instanceof IModelDb_1.IModelDb)
|
|
168
|
+
return operation(arg[Symbols_1._nativeDb]);
|
|
169
|
+
const nativeDb = new NativePlatform_1.IModelNative.platform.DgnDb();
|
|
170
|
+
nativeDb.openIModel(arg.fileName, core_bentley_1.OpenMode.Readonly);
|
|
164
171
|
try {
|
|
165
|
-
|
|
166
|
-
const sharedAccessKey = propsString + (testSyncCache ?? "");
|
|
167
|
-
const cached = sharedAccessByIModel.get(sharedAccessKey);
|
|
168
|
-
if (cached)
|
|
169
|
-
return cached;
|
|
170
|
-
const props = JSON.parse(propsString);
|
|
171
|
-
const accessToken = await CloudSqlite_1.CloudSqlite.requestToken(props);
|
|
172
|
-
const access = new CloudAccess({ ...props, accessToken });
|
|
173
|
-
Object.assign(access.lockParams, lockParams);
|
|
174
|
-
if (testSyncCache)
|
|
175
|
-
access.setCache(CloudSqlite_1.CloudSqlite.CloudCaches.getCache({ cacheName: testSyncCache }));
|
|
176
|
-
sharedAccessByIModel.set(sharedAccessKey, access);
|
|
177
|
-
return access;
|
|
172
|
+
return operation(nativeDb);
|
|
178
173
|
}
|
|
179
174
|
finally {
|
|
180
|
-
|
|
181
|
-
|
|
175
|
+
nativeDb.closeFile();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Whether this iModel's ECSchemas are governed by a `SchemaSyncDb`.
|
|
179
|
+
*
|
|
180
|
+
* This is the single question every schema operation branches on. It reads one `be_Prop` row written when
|
|
181
|
+
* schema sync was enabled and carried to every other briefcase by that changeset - no cloud access, and it
|
|
182
|
+
* works on a readonly briefcase, a checkpoint, or a closed file.
|
|
183
|
+
* @note This says nothing about whether the container is reachable, or even named. Use
|
|
184
|
+
* [[queryContainerProps]] for that. The two are separate on purpose: a file that says it is governed by a
|
|
185
|
+
* sync db must never fall back to importing schemas on its own, whatever state the container is in.
|
|
186
|
+
*/
|
|
187
|
+
function isEnabled(arg) {
|
|
188
|
+
return readFromIModel(arg, (nativeDb) => nativeDb.schemaSyncEnabled());
|
|
189
|
+
}
|
|
190
|
+
SchemaSync.isEnabled = isEnabled;
|
|
191
|
+
/** One pass over the values schema sync keeps in the file: which container holds the sync db, plus the
|
|
192
|
+
* test-only cache override. Read together so a closed file is opened once.
|
|
193
|
+
*/
|
|
194
|
+
function readLocalSyncProps(arg) {
|
|
195
|
+
return readFromIModel(arg, (nativeDb) => {
|
|
196
|
+
const propsString = nativeDb.queryFileProperty(syncProperty, true);
|
|
197
|
+
let containerProps;
|
|
198
|
+
if (propsString !== undefined) {
|
|
199
|
+
try {
|
|
200
|
+
containerProps = JSON.parse(propsString);
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
// Deliberately not swallowed into `undefined`: that would read as "schema sync was never
|
|
204
|
+
// enabled" and let this briefcase import schemas on its own.
|
|
205
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_CORRUPT, `iModel names a SchemaSyncDb container but the property cannot be read: ${core_bentley_1.BentleyError.getErrorMessage(e)}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
containerProps,
|
|
210
|
+
testCacheName: nativeDb.queryLocalValue(testSyncCachePropKey),
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/** The container holding this iModel's `SchemaSyncDb`, or `undefined` if schema sync was never enabled.
|
|
215
|
+
*
|
|
216
|
+
* Local read of the same `be_Prop` file property that [[initializeForIModel]] wrote. Requesting an access
|
|
217
|
+
* token for the container is a separate, asynchronous step.
|
|
218
|
+
*/
|
|
219
|
+
function queryContainerProps(arg) {
|
|
220
|
+
return readLocalSyncProps(arg).containerProps;
|
|
221
|
+
}
|
|
222
|
+
SchemaSync.queryContainerProps = queryContainerProps;
|
|
223
|
+
const sharedAccessByContainer = new Map();
|
|
224
|
+
const sharedAccessByInstance = new WeakMap();
|
|
225
|
+
async function getCloudAccess(arg) {
|
|
226
|
+
const { containerProps, testCacheName } = readLocalSyncProps(arg);
|
|
227
|
+
if (undefined === containerProps)
|
|
228
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_NOTFOUND, "iModel does not have a SchemaSyncDb");
|
|
229
|
+
const sharedAccessKey = JSON.stringify(containerProps) + (testCacheName ?? "");
|
|
230
|
+
let shared = sharedAccessByContainer.get(sharedAccessKey);
|
|
231
|
+
if (undefined === shared) {
|
|
232
|
+
const pending = (async () => {
|
|
233
|
+
const accessToken = await CloudSqlite_1.CloudSqlite.requestToken(containerProps);
|
|
234
|
+
const access = new CloudAccess({ ...containerProps, accessToken });
|
|
235
|
+
Object.assign(access.lockParams, lockParams);
|
|
236
|
+
if (testCacheName)
|
|
237
|
+
access.setCache(CloudSqlite_1.CloudSqlite.CloudCaches.getCache({ cacheName: testCacheName }));
|
|
238
|
+
const created = { access, key: sharedAccessKey, referenceCount: 0 };
|
|
239
|
+
sharedAccessByInstance.set(access, created);
|
|
240
|
+
return created;
|
|
241
|
+
})();
|
|
242
|
+
sharedAccessByContainer.set(sharedAccessKey, pending);
|
|
243
|
+
try {
|
|
244
|
+
shared = await pending;
|
|
245
|
+
if (sharedAccessByContainer.get(sharedAccessKey) === pending)
|
|
246
|
+
sharedAccessByContainer.set(sharedAccessKey, shared);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (sharedAccessByContainer.get(sharedAccessKey) === pending)
|
|
250
|
+
sharedAccessByContainer.delete(sharedAccessKey);
|
|
251
|
+
throw error;
|
|
182
252
|
}
|
|
183
253
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const access = await SchemaSync.getCloudAccess(iModel);
|
|
187
|
-
try {
|
|
188
|
-
await access.withLockedDb(args, async () => operation(access));
|
|
254
|
+
else if (shared instanceof Promise) {
|
|
255
|
+
shared = await shared;
|
|
189
256
|
}
|
|
190
|
-
|
|
257
|
+
++shared.referenceCount;
|
|
258
|
+
return shared.access;
|
|
259
|
+
}
|
|
260
|
+
SchemaSync.getCloudAccess = getCloudAccess;
|
|
261
|
+
/** Release an access obtained through [[getCloudAccess]]. */
|
|
262
|
+
function releaseCloudAccess(access) {
|
|
263
|
+
const shared = sharedAccessByInstance.get(access);
|
|
264
|
+
if (undefined === shared) {
|
|
191
265
|
access.close();
|
|
266
|
+
return;
|
|
192
267
|
}
|
|
193
|
-
|
|
194
|
-
|
|
268
|
+
if (--shared.referenceCount > 0)
|
|
269
|
+
return;
|
|
270
|
+
sharedAccessByInstance.delete(access);
|
|
271
|
+
if (sharedAccessByContainer.get(shared.key) === shared)
|
|
272
|
+
sharedAccessByContainer.delete(shared.key);
|
|
273
|
+
access.close();
|
|
274
|
+
}
|
|
275
|
+
SchemaSync.releaseCloudAccess = releaseCloudAccess;
|
|
276
|
+
async function withLockedAccess(iModel, args, operation) {
|
|
195
277
|
const access = await SchemaSync.getCloudAccess(iModel);
|
|
196
|
-
access.synchronizeWithCloud();
|
|
197
|
-
access.openForRead();
|
|
198
278
|
try {
|
|
199
|
-
await operation(access);
|
|
279
|
+
await access.withLockedDb(args, async () => operation(access));
|
|
200
280
|
}
|
|
201
281
|
finally {
|
|
202
|
-
access
|
|
282
|
+
releaseCloudAccess(access);
|
|
203
283
|
}
|
|
204
|
-
}
|
|
205
|
-
SchemaSync.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (iModel
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
iModel[Symbols_1._nativeDb].schemaSyncPull(schemaSyncDbUri);
|
|
215
|
-
iModel[Symbols_1._implicitTxn].saveChanges("schema synchronized with cloud container");
|
|
216
|
-
});
|
|
284
|
+
}
|
|
285
|
+
SchemaSync.withLockedAccess = withLockedAccess;
|
|
286
|
+
/** Build the tables and indexes the briefcase's `ec_` rows describe. A merged schema changeset carries
|
|
287
|
+
* those rows but no DDL, so the physical columns are missing until this runs. Needs no cloud access.
|
|
288
|
+
*/
|
|
289
|
+
function updateDbSchema(iModel) {
|
|
290
|
+
if (isEnabled(iModel) && !iModel.isReadonly) {
|
|
291
|
+
iModel.clearCaches();
|
|
292
|
+
iModel[Symbols_1._nativeDb].schemaSyncUpdateDbSchema();
|
|
293
|
+
iModel[Symbols_1._implicitTxn].saveChanges("materialized db schema from ec_ tables");
|
|
217
294
|
}
|
|
218
|
-
}
|
|
219
|
-
SchemaSync.
|
|
220
|
-
|
|
295
|
+
}
|
|
296
|
+
SchemaSync.updateDbSchema = updateDbSchema;
|
|
297
|
+
/** Whether a failed [[IModelDb.importSchemas]] can be retried through the upgrade path.
|
|
298
|
+
*
|
|
299
|
+
* The update tier refuses two kinds of change: one that has to move data between columns, and one
|
|
300
|
+
* that destroys instances or property values. They arrive as different statuses because they are
|
|
301
|
+
* different changes, but they mean one thing to a caller - this needs
|
|
302
|
+
* [[BriefcaseDb.upgradeSchemas]], which takes the exclusive schema lock.
|
|
303
|
+
*
|
|
304
|
+
* The retry is the app's decision, not the platform's: taking that lock disturbs everyone else, so
|
|
305
|
+
* only the app knows whether to do it now, schedule it, or tell the user.
|
|
306
|
+
* ```ts
|
|
307
|
+
* try {
|
|
308
|
+
* await db.importSchemas(files);
|
|
309
|
+
* } catch (e) {
|
|
310
|
+
* if (!SchemaSync.requiresUpgrade(e)) throw e;
|
|
311
|
+
* await db.upgradeSchemas(files, { description: "..." });
|
|
312
|
+
* }
|
|
313
|
+
* ```
|
|
314
|
+
* @internal
|
|
315
|
+
*/
|
|
316
|
+
function requiresUpgrade(error) {
|
|
317
|
+
const errorNumber = error?.errorNumber;
|
|
318
|
+
return errorNumber === core_bentley_1.DbResult.BE_SQLITE_ERROR_DataTransformRequired
|
|
319
|
+
|| errorNumber === core_bentley_1.DbResult.BE_SQLITE_ERROR_DataDeletionRequired;
|
|
320
|
+
}
|
|
321
|
+
SchemaSync.requiresUpgrade = requiresUpgrade;
|
|
322
|
+
/** Create a cloud container to hold this iModel's `SchemaSyncDb`, and initialize it as empty.
|
|
323
|
+
*
|
|
324
|
+
* The container is scoped to the iModel, so the service deletes it when the iModel is deleted. Pass the
|
|
325
|
+
* returned props to [[initializeForIModel]] - the two calls together are what enables schema sync on an
|
|
326
|
+
* iModel that was created without it.
|
|
327
|
+
* @note The current user must be authorized to create containers for the iTwin.
|
|
328
|
+
*/
|
|
329
|
+
async function createContainerForIModel(arg) {
|
|
221
330
|
const iModel = arg.iModel;
|
|
331
|
+
const iTwinId = iModel.iTwinId;
|
|
332
|
+
if (undefined === iTwinId)
|
|
333
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot create a SchemaSync container for an iModel that has no iTwin");
|
|
334
|
+
return CloudAccess.createNewContainer({
|
|
335
|
+
scope: { iTwinId, iModelId: iModel.iModelId },
|
|
336
|
+
metadata: { label: arg.label ?? `SchemaSync for ${iModel.name}`, description: arg.description },
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
SchemaSync.createContainerForIModel = createContainerForIModel;
|
|
340
|
+
/** Refuse initialization before a caller provisions a container, then hold the exclusive schema lock and pull to the tip. */
|
|
341
|
+
async function prepareToInitializeForIModel(iModel, overrideContainer) {
|
|
222
342
|
const briefcase = iModel instanceof IModelDb_1.BriefcaseDb ? iModel : undefined;
|
|
343
|
+
if (briefcase && !briefcase.locks.isServerBased)
|
|
344
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot enable SchemaSync without server-based locking");
|
|
345
|
+
if (briefcase && (iModel[Symbols_1._nativeDb].hasUnsavedChanges() || briefcase.txns.hasLocalChanges))
|
|
346
|
+
throw new core_common_1.IModelError(core_bentley_1.ChangeSetStatus.HasLocalChanges, "Cannot enable SchemaSync while there are local changes");
|
|
347
|
+
const assertContainerCanBeInitialized = () => {
|
|
348
|
+
const localInfo = iModel[Symbols_1._nativeDb].schemaSyncGetLocalDbInfo();
|
|
349
|
+
if (localInfo && !overrideContainer)
|
|
350
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, `Local db already initialized to schema sync (container-id: ${localInfo.id})`);
|
|
351
|
+
};
|
|
352
|
+
assertContainerCanBeInitialized();
|
|
223
353
|
await iModel.acquireSchemaLock();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
354
|
+
await briefcase?.pullChanges();
|
|
355
|
+
assertContainerCanBeInitialized();
|
|
356
|
+
return briefcase;
|
|
357
|
+
}
|
|
358
|
+
async function initializeAfterPreflight(arg, briefcase) {
|
|
359
|
+
const props = { baseUri: arg.containerProps.baseUri, containerId: arg.containerProps.containerId, storageType: arg.containerProps.storageType }; // sanitize to only known properties
|
|
360
|
+
const iModel = arg.iModel;
|
|
361
|
+
const description = arg.overrideContainer
|
|
362
|
+
? `Overriding SchemaSync for iModel with container-id: ${props.containerId}`
|
|
363
|
+
: `Enable SchemaSync for iModel with container-id: ${props.containerId}`;
|
|
364
|
+
const txnBeforeInit = briefcase?.txns.getCurrentTxnId();
|
|
230
365
|
try {
|
|
231
366
|
iModel[Symbols_1._implicitTxn].saveFileProperty(syncProperty, JSON.stringify(props));
|
|
232
|
-
|
|
233
|
-
iModel
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
367
|
+
try {
|
|
368
|
+
await withLockedAccess(iModel, { operationName: "initialize schemaSync", openMode: core_bentley_1.OpenMode.Readonly }, async (syncAccess) => {
|
|
369
|
+
iModel[Symbols_1._nativeDb].schemaSyncInit(syncAccess.getUri(), props.containerId, arg.overrideContainer ?? false);
|
|
370
|
+
iModel[Symbols_1._implicitTxn].saveChanges(description);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
iModel[Symbols_1._implicitTxn].abandonChanges();
|
|
375
|
+
if (briefcase && txnBeforeInit !== undefined && briefcase.txns.getCurrentTxnId() !== txnBeforeInit) {
|
|
376
|
+
const status = briefcase[Symbols_1._nativeDb].cancelTo(txnBeforeInit, true);
|
|
377
|
+
if (status !== core_bentley_1.IModelStatus.Success)
|
|
378
|
+
core_bentley_1.Logger.logError("SchemaSync", `Failed to roll back schema sync initialization after the container upload failed: ${core_bentley_1.IModelStatus[status] ?? status}`);
|
|
379
|
+
}
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
// Upload the initialized container before publishing the property that tells every briefcase to use it.
|
|
383
|
+
// If the push fails, this briefcase keeps the local txn and exclusive schema lock so the push can be retried.
|
|
384
|
+
await briefcase?.pushChanges({ description });
|
|
239
385
|
}
|
|
240
386
|
finally {
|
|
241
387
|
iModel[Symbols_1._implicitTxn].abandonChanges();
|
|
242
388
|
}
|
|
243
|
-
if (briefcase) {
|
|
244
|
-
if (arg.overrideContainer)
|
|
245
|
-
await briefcase.pushChanges({ description: `Overriding SchemaSync for iModel with container-id: ${props.containerId}` });
|
|
246
|
-
else
|
|
247
|
-
await briefcase.pushChanges({ description: `Enable SchemaSync for iModel with container-id: ${props.containerId}` });
|
|
248
|
-
}
|
|
249
389
|
await iModel.initializeSharedElementReservations();
|
|
390
|
+
}
|
|
391
|
+
/** Enable schema sync for an iModel, seeding the container from this briefcase.
|
|
392
|
+
* @note Takes the exclusive schema lock, refuses local changes, and pulls the briefcase to the tip before writing the container.
|
|
393
|
+
* If this operation fails after acquiring the schema lock, it leaves the lock held.
|
|
394
|
+
*/
|
|
395
|
+
async function initializeForIModel(arg) {
|
|
396
|
+
const briefcase = await prepareToInitializeForIModel(arg.iModel, arg.overrideContainer ?? false);
|
|
397
|
+
await initializeAfterPreflight(arg, briefcase);
|
|
398
|
+
}
|
|
399
|
+
SchemaSync.initializeForIModel = initializeForIModel;
|
|
400
|
+
const nativeRepairScope = {
|
|
401
|
+
schemaMetadata: 0,
|
|
402
|
+
schemaMetadataAndProfile: 1,
|
|
250
403
|
};
|
|
404
|
+
/** Restore the schema-owned portion of the SchemaSyncDb from a briefcase at the tip of the timeline.
|
|
405
|
+
*
|
|
406
|
+
* This operation takes the exclusive schema lock and refuses to pull or modify the briefcase. Element
|
|
407
|
+
* reservations and all other target-only state in the SchemaSyncDb are preserved.
|
|
408
|
+
* @alpha
|
|
409
|
+
*/
|
|
410
|
+
async function repairForIModel(arg) {
|
|
411
|
+
const iModel = arg.iModel;
|
|
412
|
+
if (!isEnabled(iModel))
|
|
413
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot repair SchemaSync because it is not enabled for this iModel");
|
|
414
|
+
if (iModel[Symbols_1._nativeDb].hasUnsavedChanges() || iModel.txns.hasLocalChanges)
|
|
415
|
+
throw new core_common_1.IModelError(core_bentley_1.ChangeSetStatus.HasLocalChanges, "Cannot repair SchemaSync while there are local changes");
|
|
416
|
+
if (!iModel.locks.isServerBased)
|
|
417
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot repair SchemaSync without server-based locking");
|
|
418
|
+
if (arg.scope !== undefined && arg.scope !== "schemaMetadata" && arg.scope !== "schemaMetadataAndProfile")
|
|
419
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Unknown SchemaSync repair scope");
|
|
420
|
+
const briefcaseManagerModule = await Promise.resolve().then(() => require("./BriefcaseManager"));
|
|
421
|
+
const assertBriefcaseIsAtTip = async () => {
|
|
422
|
+
const latestChangeset = await briefcaseManagerModule.BriefcaseManager.getLatestChangeset({ iModelId: iModel.iModelId });
|
|
423
|
+
if (latestChangeset.id !== iModel.changeset.id)
|
|
424
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot repair SchemaSync from a briefcase that is not at the tip of the iModel timeline");
|
|
425
|
+
};
|
|
426
|
+
// Avoid asking Hub for an exclusive lock it cannot grant to a briefcase that first needs to pull.
|
|
427
|
+
await assertBriefcaseIsAtTip();
|
|
428
|
+
const schemaLockWasHeld = iModel.holdsSchemaLock;
|
|
429
|
+
if (!schemaLockWasHeld)
|
|
430
|
+
await iModel.acquireSchemaLock();
|
|
431
|
+
try {
|
|
432
|
+
// Close the race between the preflight query and acquiring the exclusive lock.
|
|
433
|
+
await assertBriefcaseIsAtTip();
|
|
434
|
+
const repairScope = nativeRepairScope[arg.scope ?? "schemaMetadata"];
|
|
435
|
+
await withLockedAccess(iModel, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "repair schema sync" }, async (syncAccess) => {
|
|
436
|
+
const nativeDb = iModel[Symbols_1._nativeDb];
|
|
437
|
+
nativeDb.schemaSyncRepair(syncAccess.getUri(), repairScope);
|
|
438
|
+
});
|
|
439
|
+
core_bentley_1.Logger.logInfo("SchemaSync", `Repaired SchemaSyncDb from changeset ${iModel.changeset.id || "0"}`);
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
if (!schemaLockWasHeld)
|
|
443
|
+
await iModel.locks.abandonAllLocks();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
SchemaSync.repairForIModel = repairForIModel;
|
|
447
|
+
/** Turn schema sync on for an iModel: create the container if needed, record it, and seed it.
|
|
448
|
+
*
|
|
449
|
+
* The single call callers should use. [[createContainerForIModel]] and [[initializeForIModel]] have
|
|
450
|
+
* to happen in this order, and an iModel left between the two is one that names a container nothing
|
|
451
|
+
* has seeded.
|
|
452
|
+
* @returns the container props recorded on the iModel.
|
|
453
|
+
* @note Takes the exclusive schema lock and pushes, same protocol as [[BriefcaseDb.upgradeSchemas]] -
|
|
454
|
+
* every operation that changes how a file is governed uses it. If this operation fails after acquiring the schema lock,
|
|
455
|
+
* it leaves the lock held.
|
|
456
|
+
*/
|
|
457
|
+
async function enableForIModel(arg) {
|
|
458
|
+
if (undefined === arg.containerProps && undefined === arg.iModel.iTwinId)
|
|
459
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot create a SchemaSync container for an iModel that has no iTwin");
|
|
460
|
+
const briefcase = await prepareToInitializeForIModel(arg.iModel, arg.overrideContainer ?? false);
|
|
461
|
+
const containerProps = arg.containerProps
|
|
462
|
+
?? await createContainerForIModel({ iModel: arg.iModel, label: arg.label, description: arg.description });
|
|
463
|
+
await initializeAfterPreflight({ iModel: arg.iModel, containerProps, overrideContainer: arg.overrideContainer }, briefcase);
|
|
464
|
+
return containerProps;
|
|
465
|
+
}
|
|
466
|
+
SchemaSync.enableForIModel = enableForIModel;
|
|
251
467
|
/** Provides access to a cloud-based `SchemaSyncDb` to hold ECSchemas. */
|
|
252
468
|
class CloudAccess extends CloudSqlite_1.CloudSqlite.DbAccess {
|
|
253
469
|
constructor(props) {
|
|
@@ -264,6 +480,12 @@ var SchemaSync;
|
|
|
264
480
|
static async initializeDb(props) {
|
|
265
481
|
return super._initializeDb({ props, dbType: SchemaSyncDb, dbName: defaultDbName });
|
|
266
482
|
}
|
|
483
|
+
/** Create and initialize a new `schema-sync` BlobContainer to hold a `SchemaSyncDb`. */
|
|
484
|
+
static async createNewContainer(args) {
|
|
485
|
+
const props = await this.createBlobContainer({ scope: args.scope, metadata: { ...args.metadata, containerType: SchemaSync.containerType } });
|
|
486
|
+
await this.initializeDb(props);
|
|
487
|
+
return props;
|
|
488
|
+
}
|
|
267
489
|
}
|
|
268
490
|
SchemaSync.CloudAccess = CloudAccess;
|
|
269
491
|
})(SchemaSync || (exports.SchemaSync = SchemaSync = {}));
|