@itwin/core-backend 4.3.0-dev.9 → 4.4.0-dev.1
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 +49 -1
- package/lib/cjs/BriefcaseManager.js +3 -3
- package/lib/cjs/BriefcaseManager.js.map +1 -1
- package/lib/cjs/ChangesetECAdaptor.d.ts +196 -0
- package/lib/cjs/ChangesetECAdaptor.d.ts.map +1 -0
- package/lib/cjs/ChangesetECAdaptor.js +746 -0
- package/lib/cjs/ChangesetECAdaptor.js.map +1 -0
- package/lib/cjs/CheckpointManager.js +1 -1
- package/lib/cjs/CheckpointManager.js.map +1 -1
- package/lib/cjs/ECDb.d.ts +8 -1
- package/lib/cjs/ECDb.d.ts.map +1 -1
- package/lib/cjs/ECDb.js +9 -0
- package/lib/cjs/ECDb.js.map +1 -1
- package/lib/cjs/ElementAspect.d.ts.map +1 -1
- package/lib/cjs/ElementAspect.js +10 -3
- package/lib/cjs/ElementAspect.js.map +1 -1
- package/lib/cjs/GeometrySummary.js +5 -5
- package/lib/cjs/GeometrySummary.js.map +1 -1
- package/lib/cjs/IModelDb.d.ts +1 -1
- package/lib/cjs/IModelDb.d.ts.map +1 -1
- package/lib/cjs/IModelDb.js +13 -9
- package/lib/cjs/IModelDb.js.map +1 -1
- package/lib/cjs/IModelHost.d.ts +1 -1
- package/lib/cjs/IModelHost.d.ts.map +1 -1
- package/lib/cjs/IModelHost.js +2 -2
- package/lib/cjs/IModelHost.js.map +1 -1
- package/lib/cjs/LocalHub.js +1 -1
- package/lib/cjs/LocalHub.js.map +1 -1
- package/lib/cjs/PromiseMemoizer.d.ts.map +1 -1
- package/lib/cjs/PromiseMemoizer.js.map +1 -1
- package/lib/cjs/SqliteChangesetReader.d.ts +185 -0
- package/lib/cjs/SqliteChangesetReader.d.ts.map +1 -0
- package/lib/cjs/SqliteChangesetReader.js +217 -0
- package/lib/cjs/SqliteChangesetReader.js.map +1 -0
- package/lib/cjs/core-backend.d.ts +2 -0
- package/lib/cjs/core-backend.d.ts.map +1 -1
- package/lib/cjs/core-backend.js +2 -0
- package/lib/cjs/core-backend.js.map +1 -1
- package/package.json +21 -16
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChangesetECAdaptor = exports.PartialECChangeUnifier = void 0;
|
|
4
|
+
/*---------------------------------------------------------------------------------------------
|
|
5
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
6
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
7
|
+
*--------------------------------------------------------------------------------------------*/
|
|
8
|
+
/** @packageDocumentation
|
|
9
|
+
* @module ECDb
|
|
10
|
+
*/
|
|
11
|
+
const core_bentley_1 = require("@itwin/core-bentley");
|
|
12
|
+
class ECDbMap {
|
|
13
|
+
constructor(db) {
|
|
14
|
+
this.db = db;
|
|
15
|
+
this._cachedClassMaps = new Map();
|
|
16
|
+
this._cacheTables = new Map();
|
|
17
|
+
}
|
|
18
|
+
getAllDerivedClasses(classFullName) {
|
|
19
|
+
const sql = `
|
|
20
|
+
SELECT format('0x%x', ch.ClassId)
|
|
21
|
+
FROM [ec_cache_ClassHierarchy] [ch]
|
|
22
|
+
JOIN [ec_Class] [cs] ON [cs].[Id] = [ch].[BaseClassId]
|
|
23
|
+
JOIN [ec_Schema] [sc] ON [sc].[Id] = [cs].[SchemaId]
|
|
24
|
+
WHERE (([sc].[Alias] = :schemaNameOrAlias
|
|
25
|
+
OR [sc].[Name] = :schemaNameOrAlias)
|
|
26
|
+
AND ([cs].[Name] = :className))
|
|
27
|
+
`;
|
|
28
|
+
return this.db.withPreparedSqliteStatement(sql, (stmt) => {
|
|
29
|
+
const parts = classFullName.indexOf(".") !== -1 ? classFullName.split(".") : classFullName.split(":");
|
|
30
|
+
stmt.bindString(":schemaNameOrAlias", parts[0]);
|
|
31
|
+
stmt.bindString(":className", parts[1]);
|
|
32
|
+
const classIds = [];
|
|
33
|
+
while (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
34
|
+
classIds.push(stmt.getValueString(0));
|
|
35
|
+
return classIds;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
getTable(tableName) {
|
|
39
|
+
if (this._cacheTables.has(tableName))
|
|
40
|
+
return this._cacheTables.get(tableName);
|
|
41
|
+
const sql = `
|
|
42
|
+
SELECT
|
|
43
|
+
JSON_OBJECT (
|
|
44
|
+
'id', FORMAT ('0x%x', [t].[id]),
|
|
45
|
+
'name', [t].[Name],
|
|
46
|
+
'type', (
|
|
47
|
+
CASE
|
|
48
|
+
[t].[type]
|
|
49
|
+
WHEN 0 THEN 'Primary'
|
|
50
|
+
WHEN 1 THEN 'Joined'
|
|
51
|
+
WHEN 2 THEN 'Existing'
|
|
52
|
+
WHEN 3 THEN 'Overflow'
|
|
53
|
+
WHEN 4 THEN 'Virtual'
|
|
54
|
+
END
|
|
55
|
+
),
|
|
56
|
+
'exclusiveRootClassId', FORMAT ('0x%x', [t].[ExclusiveRootClassId]),
|
|
57
|
+
'isClassIdVirtual', (
|
|
58
|
+
SELECT
|
|
59
|
+
[c].[IsVirtual]
|
|
60
|
+
FROM
|
|
61
|
+
[ec_Column] [c]
|
|
62
|
+
WHERE
|
|
63
|
+
[c].[Name] = 'ECClassId' AND [c].[TableId] = [t].[Id]
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
FROM [ec_Table] [t]
|
|
67
|
+
WHERE
|
|
68
|
+
[t].[Name] = ?;
|
|
69
|
+
`;
|
|
70
|
+
return this.db.withPreparedSqliteStatement(sql, (stmt) => {
|
|
71
|
+
stmt.bindString(1, tableName);
|
|
72
|
+
if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW) {
|
|
73
|
+
const table = JSON.parse(stmt.getValueString(0), (key, value) => {
|
|
74
|
+
if (value === null)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (key === "isClassIdVirtual")
|
|
77
|
+
return value === 0 ? false : true;
|
|
78
|
+
return value;
|
|
79
|
+
});
|
|
80
|
+
this._cacheTables.set(tableName, table);
|
|
81
|
+
return table;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
getClassMap(classId) {
|
|
87
|
+
if (this._cachedClassMaps.has(classId))
|
|
88
|
+
return this._cachedClassMaps.get(classId);
|
|
89
|
+
const sql = `
|
|
90
|
+
SELECT
|
|
91
|
+
JSON_OBJECT(
|
|
92
|
+
'id', format('0x%x', cs.id),
|
|
93
|
+
'name', format('%s.%s', ss.Name, cs.Name),
|
|
94
|
+
'mapStrategy',
|
|
95
|
+
(
|
|
96
|
+
CASE cm.MapStrategy
|
|
97
|
+
WHEN 0 THEN 'NotMapped'
|
|
98
|
+
WHEN 1 THEN 'OwnTable'
|
|
99
|
+
WHEN 2 THEN 'TablePerHierarchy'
|
|
100
|
+
WHEN 3 THEN 'ExistingTable'
|
|
101
|
+
WHEN 10 THEN 'ForeignKeyInTargetTable'
|
|
102
|
+
WHEN 11 THEN 'ForeignKeyInSourceTable'
|
|
103
|
+
END
|
|
104
|
+
),
|
|
105
|
+
'type',
|
|
106
|
+
(
|
|
107
|
+
CASE cs.Type
|
|
108
|
+
WHEN 0 THEN 'Entity'
|
|
109
|
+
WHEN 1 THEN 'Relationship'
|
|
110
|
+
WHEN 2 THEN 'Struct'
|
|
111
|
+
WHEN 3 THEN 'CustomAttribute'
|
|
112
|
+
END
|
|
113
|
+
),
|
|
114
|
+
'modifier',
|
|
115
|
+
(
|
|
116
|
+
CASE cs.Modifier
|
|
117
|
+
WHEN 0 THEN 'None'
|
|
118
|
+
WHEN 1 THEN 'Abstract'
|
|
119
|
+
WHEN 2 THEN 'Sealed'
|
|
120
|
+
END
|
|
121
|
+
),
|
|
122
|
+
'properties',
|
|
123
|
+
(
|
|
124
|
+
SELECT
|
|
125
|
+
JSON_GROUP_ARRAY(JSON(propJson))
|
|
126
|
+
FROM
|
|
127
|
+
(
|
|
128
|
+
SELECT
|
|
129
|
+
JSON_OBJECT(
|
|
130
|
+
'id', format('0x%x', pt.id),
|
|
131
|
+
'name', pt.Name,
|
|
132
|
+
'kind',
|
|
133
|
+
(
|
|
134
|
+
CASE pt.Kind
|
|
135
|
+
WHEN 0 THEN 'Primitive'
|
|
136
|
+
WHEN 1 THEN 'Struct'
|
|
137
|
+
WHEN 2 THEN 'PrimitiveArray'
|
|
138
|
+
WHEN 3 THEN 'StructArray'
|
|
139
|
+
WHEN 4 THEN 'Navigation'
|
|
140
|
+
END
|
|
141
|
+
),
|
|
142
|
+
'primitiveType',
|
|
143
|
+
(
|
|
144
|
+
CASE pt.PrimitiveType
|
|
145
|
+
WHEN 0x101 THEN 'Binary'
|
|
146
|
+
WHEN 0x201 THEN 'Boolean'
|
|
147
|
+
WHEN 0x301 THEN 'DateTime'
|
|
148
|
+
WHEN 0x401 THEN 'Double'
|
|
149
|
+
WHEN 0x501 THEN 'Integer'
|
|
150
|
+
WHEN 0x601 THEN 'Long'
|
|
151
|
+
WHEN 0x701 THEN 'Point2d'
|
|
152
|
+
WHEN 0x801 THEN 'Point3d'
|
|
153
|
+
WHEN 0x901 THEN 'String'
|
|
154
|
+
WHEN 0xa01 THEN 'IGeometry'
|
|
155
|
+
END
|
|
156
|
+
),
|
|
157
|
+
'extendedTypeName', ExtendedTypeName,
|
|
158
|
+
'navigationRelationship',
|
|
159
|
+
(
|
|
160
|
+
SELECT
|
|
161
|
+
JSON_OBJECT(
|
|
162
|
+
'classId', format('0x%x', nc.Id),
|
|
163
|
+
'className', format('%s.%s', ns.Name, nc.Name)
|
|
164
|
+
)
|
|
165
|
+
FROM ec_Class nc
|
|
166
|
+
JOIN ec_Schema ns ON ns.Id = nc.SchemaId
|
|
167
|
+
WHERE
|
|
168
|
+
nc.Id = pt.NavigationRelationshipClassId
|
|
169
|
+
),
|
|
170
|
+
'structClass',
|
|
171
|
+
(
|
|
172
|
+
SELECT
|
|
173
|
+
JSON_OBJECT(
|
|
174
|
+
'classId', format('0x%x', nc.Id),
|
|
175
|
+
'className', format('%s.%s', ns.Name, nc.Name)
|
|
176
|
+
)
|
|
177
|
+
FROM ec_Class nc
|
|
178
|
+
JOIN ec_Schema ns ON ns.Id = nc.SchemaId
|
|
179
|
+
WHERE
|
|
180
|
+
nc.Id = pt.StructClassId
|
|
181
|
+
),
|
|
182
|
+
'dateTimeInfo', (
|
|
183
|
+
SELECT
|
|
184
|
+
JSON_OBJECT (
|
|
185
|
+
'dateTimeKind', (
|
|
186
|
+
CASE
|
|
187
|
+
WHEN [ca].[Instance] LIKE '%<DateTimeKind>Utc</DateTimeKind>%' COLLATE [NoCase] THEN 'Utc'
|
|
188
|
+
WHEN [ca].[Instance] LIKE '%<DateTimeKind>Local</DateTimeKind>%' COLLATE [NoCase] THEN 'Local'
|
|
189
|
+
ELSE 'Unspecified'
|
|
190
|
+
END
|
|
191
|
+
),
|
|
192
|
+
'dateTimeComponent', (
|
|
193
|
+
CASE
|
|
194
|
+
WHEN [ca].[Instance] LIKE '%<DateTimeComponent>DateTime</DateTimeComponent>%' COLLATE [NoCase] THEN 'DateTime'
|
|
195
|
+
WHEN [ca].[Instance] LIKE '%<DateTimeComponent>Date</DateTimeComponent>%' COLLATE [NoCase] THEN 'Date'
|
|
196
|
+
WHEN [ca].[Instance] LIKE '%<DateTimeComponent>TimeOfDay</DateTimeComponent>%' COLLATE [NoCase] THEN 'TimeOfDay'
|
|
197
|
+
ELSE 'DateTime'
|
|
198
|
+
END
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
FROM
|
|
202
|
+
[ec_CustomAttribute] [ca]
|
|
203
|
+
JOIN [ec_Class] [cl] ON [cl].[Id] = [ca].[ClassId]
|
|
204
|
+
JOIN [ec_Schema] [sc] ON [sc].[Id] = [cl].[SchemaId]
|
|
205
|
+
WHERE
|
|
206
|
+
[ca].[ContainerType] = 992
|
|
207
|
+
AND [cl].[Name] = 'DateTimeInfo'
|
|
208
|
+
AND [sc].[Name] = 'CoreCustomAttributes'
|
|
209
|
+
AND [ca].[ContainerId] = [pt].[Id]
|
|
210
|
+
),
|
|
211
|
+
'columns',
|
|
212
|
+
(
|
|
213
|
+
SELECT
|
|
214
|
+
JSON_GROUP_ARRAY(JSON(columnJson))
|
|
215
|
+
FROM
|
|
216
|
+
(
|
|
217
|
+
SELECT
|
|
218
|
+
JSON_OBJECT(
|
|
219
|
+
'table', tb.Name,
|
|
220
|
+
'column', cc.Name,
|
|
221
|
+
'type',
|
|
222
|
+
(
|
|
223
|
+
CASE cc.Type
|
|
224
|
+
WHEN 0 THEN 'Any'
|
|
225
|
+
WHEN 1 THEN 'Boolean'
|
|
226
|
+
WHEN 2 THEN 'Blob'
|
|
227
|
+
WHEN 3 THEN 'Timestamp'
|
|
228
|
+
WHEN 4 THEN 'Real'
|
|
229
|
+
WHEN 5 THEN 'Integer'
|
|
230
|
+
WHEN 6 THEN 'Text'
|
|
231
|
+
END
|
|
232
|
+
),
|
|
233
|
+
'columnKind',
|
|
234
|
+
(
|
|
235
|
+
CASE cc.ColumnKind
|
|
236
|
+
WHEN 0 THEN 'Default'
|
|
237
|
+
WHEN 1 THEN 'Id'
|
|
238
|
+
WHEN 2 THEN 'ClassId'
|
|
239
|
+
WHEN 4 THEN 'SharedData'
|
|
240
|
+
END
|
|
241
|
+
),
|
|
242
|
+
'accessString', pp0.AccessString,
|
|
243
|
+
'isVirtual', cc.IsVirtual OR tb.Type = 4
|
|
244
|
+
) columnJson
|
|
245
|
+
FROM [ec_PropertyMap] [pm0]
|
|
246
|
+
JOIN [ec_Column] [cc] ON [cc].[Id] = [pm0].[ColumnId]
|
|
247
|
+
JOIN [ec_Table] [tb] ON [tb].[Id] = [cc].[TableId]
|
|
248
|
+
JOIN [ec_PropertyPath] [pp0] ON [pp0].[Id] = [pm0].[PropertyPathId]
|
|
249
|
+
WHERE
|
|
250
|
+
[pp0].[RootPropertyId] = pt.Id AND pm0.ClassId = cs.Id
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
) propJson
|
|
254
|
+
FROM [ec_PropertyMap] [pm]
|
|
255
|
+
JOIN [ec_PropertyPath] [pp] ON [pp].[Id] = [pm].[PropertyPathId]
|
|
256
|
+
JOIN [ec_Property] [pt] ON [pt].[Id] = [pp].[RootPropertyId]
|
|
257
|
+
WHERE
|
|
258
|
+
pm.ClassId = cs.Id
|
|
259
|
+
GROUP BY
|
|
260
|
+
pt.Id
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
) classDef
|
|
264
|
+
FROM [ec_Class] [cs]
|
|
265
|
+
JOIN [ec_ClassMap] [cm] ON [cm].[ClassId] = [cs].[Id]
|
|
266
|
+
JOIN [ec_Schema] [ss] ON [ss].[Id] = [cs].[SchemaId]
|
|
267
|
+
WHERE
|
|
268
|
+
[cs].[Id] = ?
|
|
269
|
+
`;
|
|
270
|
+
return this.db.withPreparedSqliteStatement(sql, (stmt) => {
|
|
271
|
+
stmt.bindId(1, classId);
|
|
272
|
+
if (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW) {
|
|
273
|
+
const classMap = JSON.parse(stmt.getValueString(0), (key, value) => {
|
|
274
|
+
if (value === null) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
if (key === "isVirtual") {
|
|
278
|
+
return value === 0 ? false : true;
|
|
279
|
+
}
|
|
280
|
+
return value;
|
|
281
|
+
});
|
|
282
|
+
this._cachedClassMaps.set(classId, classMap);
|
|
283
|
+
return classMap;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Helper function to convert between JS DateTime & SQLite JulianDay values.
|
|
291
|
+
* @beta
|
|
292
|
+
* */
|
|
293
|
+
var DateTime;
|
|
294
|
+
(function (DateTime) {
|
|
295
|
+
/**
|
|
296
|
+
* Convert JS date to JulianDay value.
|
|
297
|
+
* @param dt JS Date object.
|
|
298
|
+
* @param convertToUtc convert the input value to UTC.
|
|
299
|
+
* @returns julian day value
|
|
300
|
+
*/
|
|
301
|
+
function toJulianDay(dt, convertToUtc = true) {
|
|
302
|
+
const utcOffset = convertToUtc ? dt.getTimezoneOffset() / 1440 : 0;
|
|
303
|
+
return (dt.valueOf() / 86400000) - utcOffset + 2440587.5;
|
|
304
|
+
}
|
|
305
|
+
DateTime.toJulianDay = toJulianDay;
|
|
306
|
+
/**
|
|
307
|
+
* Convert Julian day to JS Date object
|
|
308
|
+
* @param jd JulianDay value for date/time
|
|
309
|
+
* @param isLocalTime if julian day is local time or UTC
|
|
310
|
+
* @returns JS Date object.
|
|
311
|
+
*/
|
|
312
|
+
function fromJulianDay(jd, isLocalTime) {
|
|
313
|
+
const utcOffset = isLocalTime ? 0 : new Date().getTimezoneOffset() / 1440;
|
|
314
|
+
return new Date((jd - 2440587.5 + utcOffset) * 86400000);
|
|
315
|
+
}
|
|
316
|
+
DateTime.fromJulianDay = fromJulianDay;
|
|
317
|
+
})(DateTime || (DateTime = {}));
|
|
318
|
+
/**
|
|
319
|
+
* Combine partial changed instance into single instance.
|
|
320
|
+
* Partial changes is per table and a single instance can
|
|
321
|
+
* span multiple tables.
|
|
322
|
+
* @beta
|
|
323
|
+
*/
|
|
324
|
+
class PartialECChangeUnifier {
|
|
325
|
+
constructor() {
|
|
326
|
+
this._cache = new Map();
|
|
327
|
+
this._readonly = false;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Combine partial instance with instance with same key if already exists.
|
|
331
|
+
* @param rhs partial instance
|
|
332
|
+
*/
|
|
333
|
+
combine(rhs) {
|
|
334
|
+
if (!rhs.$meta) {
|
|
335
|
+
throw new Error("PartialECChange being combine must have '$meta' property");
|
|
336
|
+
}
|
|
337
|
+
const key = PartialECChangeUnifier.buildKey(rhs);
|
|
338
|
+
const lhs = this._cache.get(key);
|
|
339
|
+
if (lhs) {
|
|
340
|
+
const { $meta: _, ...restOfRhs } = rhs;
|
|
341
|
+
Object.assign(lhs, restOfRhs);
|
|
342
|
+
if (lhs.$meta && rhs.$meta) {
|
|
343
|
+
lhs.$meta.tables = [...rhs.$meta?.tables, ...lhs.$meta?.tables];
|
|
344
|
+
lhs.$meta.changeIndexes = [...rhs.$meta?.changeIndexes, ...lhs.$meta?.changeIndexes];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
this._cache.set(key, rhs);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Build key from EC change.
|
|
353
|
+
* @param change EC change
|
|
354
|
+
* @returns key created from EC change.
|
|
355
|
+
*/
|
|
356
|
+
static buildKey(change) {
|
|
357
|
+
return `${change.ECClassId}-${change.ECInstanceId}-${change.$meta?.stage}`.toLowerCase();
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Append partial changes which will be combine using there instance key.
|
|
361
|
+
* @note $meta property must be present on partial change as information
|
|
362
|
+
* in it is used to combine partial instances.
|
|
363
|
+
* @param adaptor changeset adaptor is use to read the partial EC change.
|
|
364
|
+
* @beta
|
|
365
|
+
*/
|
|
366
|
+
appendFrom(adaptor) {
|
|
367
|
+
if (adaptor.disableMetaData) {
|
|
368
|
+
throw new Error("change adaptor property 'disableMetaData' must be set to 'false'");
|
|
369
|
+
}
|
|
370
|
+
if (this._readonly) {
|
|
371
|
+
throw new Error("this instance is marked as readonly.");
|
|
372
|
+
}
|
|
373
|
+
if (adaptor.op === "Updated" && adaptor.inserted && adaptor.deleted) {
|
|
374
|
+
this.combine(adaptor.inserted);
|
|
375
|
+
this.combine(adaptor.deleted);
|
|
376
|
+
}
|
|
377
|
+
else if (adaptor.op === "Inserted" && adaptor.inserted) {
|
|
378
|
+
this.combine(adaptor.inserted);
|
|
379
|
+
}
|
|
380
|
+
else if (adaptor.op === "Deleted" && adaptor.deleted) {
|
|
381
|
+
this.combine(adaptor.deleted);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Delete $meta from all the instances.
|
|
386
|
+
*/
|
|
387
|
+
stripMetaData() {
|
|
388
|
+
for (const inst of this._cache.values()) {
|
|
389
|
+
if ("$meta" in inst) {
|
|
390
|
+
delete inst.$meta;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
this._readonly = true;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Returns complete EC change instances.
|
|
397
|
+
* @beta
|
|
398
|
+
*/
|
|
399
|
+
get instances() { return this._cache.values(); }
|
|
400
|
+
}
|
|
401
|
+
exports.PartialECChangeUnifier = PartialECChangeUnifier;
|
|
402
|
+
/**
|
|
403
|
+
* Transform sqlite change to ec change. EC change is partial change as
|
|
404
|
+
* it is per table while a single instance can span multiple table.
|
|
405
|
+
* @note PrimitiveArray and StructArray are not supported types.
|
|
406
|
+
* @beta
|
|
407
|
+
*
|
|
408
|
+
*/
|
|
409
|
+
class ChangesetECAdaptor {
|
|
410
|
+
/**
|
|
411
|
+
* Setup filter that will result in change enumeration restricted to
|
|
412
|
+
* list of tables added by acceptTable().
|
|
413
|
+
* @param table Name of the table
|
|
414
|
+
* @returns Fluent reference to ChangesetAdaptor.
|
|
415
|
+
*/
|
|
416
|
+
acceptTable(table) {
|
|
417
|
+
if (!this._tableFilter.has(table))
|
|
418
|
+
this._tableFilter.add(table);
|
|
419
|
+
return this;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Setup filter that will result in change enumeration restricted to
|
|
423
|
+
* list of op added by acceptOp().
|
|
424
|
+
* @param op
|
|
425
|
+
* @returns Fluent reference to ChangesetAdaptor.
|
|
426
|
+
*/
|
|
427
|
+
acceptOp(op) {
|
|
428
|
+
if (!this._opFilter.has(op))
|
|
429
|
+
this._opFilter.add(op);
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Setup filter that will result in change enumeration restricted to
|
|
434
|
+
* list of class and its derived classes added by acceptClass().
|
|
435
|
+
* @param classFullName
|
|
436
|
+
* @returns
|
|
437
|
+
*/
|
|
438
|
+
acceptClass(classFullName) {
|
|
439
|
+
if (!this._classFilter.has(classFullName))
|
|
440
|
+
this._classFilter.add(classFullName);
|
|
441
|
+
this._allowedClasses.clear();
|
|
442
|
+
return this;
|
|
443
|
+
}
|
|
444
|
+
buildClassFilter() {
|
|
445
|
+
if (this._allowedClasses.size !== 0 || this._classFilter.size === 0)
|
|
446
|
+
return;
|
|
447
|
+
this._classFilter.forEach((className) => {
|
|
448
|
+
this._mapCache.getAllDerivedClasses(className).forEach((classId) => {
|
|
449
|
+
this._allowedClasses.add(classId);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Construct adaptor with a initialized reader.
|
|
455
|
+
* @note the changeset reader must have disableSchemaCheck
|
|
456
|
+
* set to false and db must also be set.
|
|
457
|
+
* @param reader wrap changeset reader.
|
|
458
|
+
*/
|
|
459
|
+
constructor(reader, disableMetaData = false) {
|
|
460
|
+
this.reader = reader;
|
|
461
|
+
this.disableMetaData = disableMetaData;
|
|
462
|
+
this._tableFilter = new Set();
|
|
463
|
+
this._opFilter = new Set();
|
|
464
|
+
this._classFilter = new Set();
|
|
465
|
+
this._allowedClasses = new Set();
|
|
466
|
+
/**
|
|
467
|
+
* set debug flags
|
|
468
|
+
*/
|
|
469
|
+
this.debugFlags = {
|
|
470
|
+
replaceBlobWithEllipsis: false,
|
|
471
|
+
replaceGeomWithEllipsis: false,
|
|
472
|
+
replaceGuidWithEllipsis: false, // replace geom with ... for debugging
|
|
473
|
+
};
|
|
474
|
+
if (!reader.db)
|
|
475
|
+
throw new Error("SqliteChangesetReader, 'db' param must be set to a valid IModelDb or ECDb.");
|
|
476
|
+
if (!reader.disableSchemaCheck)
|
|
477
|
+
throw new Error("SqliteChangesetReader, 'disableSchemaCheck' param must be set to false.");
|
|
478
|
+
this._mapCache = new ECDbMap(reader.db);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* dispose current instance and it will also dispose the changeset reader.
|
|
482
|
+
*/
|
|
483
|
+
dispose() {
|
|
484
|
+
this.close();
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* close current instance and it will also close the changeset reader.
|
|
488
|
+
*/
|
|
489
|
+
close() {
|
|
490
|
+
this.reader.close();
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Convert binary GUID into string GUID.
|
|
494
|
+
* @param binaryGUID binary version of guid.
|
|
495
|
+
* @returns GUID string.
|
|
496
|
+
*/
|
|
497
|
+
static convertBinaryToGuid(binaryGUID) {
|
|
498
|
+
// Check if the array has 16 elements
|
|
499
|
+
if (binaryGUID.length !== 16) {
|
|
500
|
+
throw new Error("Invalid array length for Guid");
|
|
501
|
+
}
|
|
502
|
+
// Convert each element to a two-digit hexadecimal string
|
|
503
|
+
const hex = Array.from(binaryGUID, (byte) => byte.toString(16).padStart(2, "0"));
|
|
504
|
+
// Join the hexadecimal strings and insert hyphens
|
|
505
|
+
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Set value use access string in a JS object.
|
|
509
|
+
* @param targetObj object that will be updated.
|
|
510
|
+
* @param accessString access string token separated by '.'.
|
|
511
|
+
*/
|
|
512
|
+
static setValue(targetObj, accessString, value) {
|
|
513
|
+
let cursor = targetObj;
|
|
514
|
+
const propPath = accessString.split(".");
|
|
515
|
+
propPath.forEach((propertyName) => {
|
|
516
|
+
if (propertyName === "__proto__")
|
|
517
|
+
throw new Error("access string cannot container __proto__");
|
|
518
|
+
});
|
|
519
|
+
const leafProp = propPath.splice(-1).shift();
|
|
520
|
+
if (!leafProp)
|
|
521
|
+
throw new Error("not access string was specified.");
|
|
522
|
+
for (const elem of propPath) {
|
|
523
|
+
if (typeof cursor[elem] === "undefined")
|
|
524
|
+
cursor[elem] = {};
|
|
525
|
+
cursor = cursor[elem];
|
|
526
|
+
}
|
|
527
|
+
cursor[leafProp] = value;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Check if sqlite change table is a EC data table
|
|
531
|
+
* @param tableName name of the table.
|
|
532
|
+
* @returns true if table has EC data.
|
|
533
|
+
*/
|
|
534
|
+
isECTable(tableName) {
|
|
535
|
+
return typeof this._mapCache.getTable(tableName) !== "undefined";
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Attempt find ECClassId from ECInstanceId for a change of type 'updated'.
|
|
539
|
+
* @param tableName name of the table to find ECClassId from given ECInstanceId
|
|
540
|
+
* @param instanceId instance id for which we need ECClassId for.
|
|
541
|
+
* @returns if successful returns ECClassId else return undefined.
|
|
542
|
+
*/
|
|
543
|
+
getClassIdFromDb(tableName, instanceId) {
|
|
544
|
+
try {
|
|
545
|
+
return this.reader.db?.withPreparedSqliteStatement(`SELECT [ECClassId] FROM [${tableName}] WHERE [rowId]=?`, (stmt) => {
|
|
546
|
+
stmt.bindId(1, instanceId);
|
|
547
|
+
return stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW ? stmt.getValueId(0) : undefined;
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
return undefined;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/** helper method around reader.op */
|
|
555
|
+
get op() { return this.reader.op; }
|
|
556
|
+
/** Return true if current change is of type "Inserted" */
|
|
557
|
+
get isInserted() { return this.op === "Inserted"; }
|
|
558
|
+
/** Return true if current change is of type "Deleted" */
|
|
559
|
+
get isDeleted() { return this.op === "Deleted"; }
|
|
560
|
+
/** Return true if current change is of type "Updated" */
|
|
561
|
+
get isUpdated() { return this.op === "Updated"; }
|
|
562
|
+
/**
|
|
563
|
+
* Advance reader to next change or a change that meets the filter set in the current adaptor
|
|
564
|
+
* @returns return false if no more changes to read.
|
|
565
|
+
*/
|
|
566
|
+
step() {
|
|
567
|
+
this.inserted = undefined;
|
|
568
|
+
this.deleted = undefined;
|
|
569
|
+
this.buildClassFilter();
|
|
570
|
+
while (this.reader.step()) {
|
|
571
|
+
if (!this.isECTable(this.reader.tableName))
|
|
572
|
+
continue;
|
|
573
|
+
if (this._tableFilter.size > 0) {
|
|
574
|
+
if (!this._tableFilter.has(this.reader.tableName))
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (this._opFilter.size > 0) {
|
|
578
|
+
if (!this._opFilter.has(this.reader.op))
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (this.reader.hasRow) {
|
|
582
|
+
const table = this._mapCache.getTable(this.reader.tableName);
|
|
583
|
+
if (!table || table.type === "Virtual") {
|
|
584
|
+
throw new Error(`table in changeset not found or is virtual ${this.reader.tableName}`);
|
|
585
|
+
}
|
|
586
|
+
const change = {
|
|
587
|
+
inserted: this.reader.getChangeValuesObject("New", { includePrimaryKeyInUpdateNew: true }),
|
|
588
|
+
deleted: this.reader.getChangeValuesObject("Old", { includePrimaryKeyInUpdateNew: true }),
|
|
589
|
+
};
|
|
590
|
+
if (!change.inserted && !change.deleted) {
|
|
591
|
+
throw new Error(`unable to get change from changeset reader`);
|
|
592
|
+
}
|
|
593
|
+
let ecClassId = this.reader.op === "Inserted" ? change.inserted?.ECClassId : change.deleted?.ECClassId;
|
|
594
|
+
const classIdPresetInChange = !ecClassId;
|
|
595
|
+
let classMap;
|
|
596
|
+
let fallbackClassId;
|
|
597
|
+
if (table.isClassIdVirtual) {
|
|
598
|
+
classMap = this._mapCache.getClassMap(table.exclusiveRootClassId);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
if (!ecClassId) {
|
|
602
|
+
// attempt to find ECClassId against row from the db.
|
|
603
|
+
const primaryKeys = this.reader.primaryKeyValues;
|
|
604
|
+
if (primaryKeys.length === 1) {
|
|
605
|
+
ecClassId = this.getClassIdFromDb(this.reader.tableName, this.reader.primaryKeyValues[0]);
|
|
606
|
+
}
|
|
607
|
+
// this is update and does not include ECClassId and it was also not found from db
|
|
608
|
+
// so its unrecoverable error. Call can skip updates to ignore this error.
|
|
609
|
+
if (!ecClassId)
|
|
610
|
+
throw new Error(`change arg must contain 'ECClassId' property.`);
|
|
611
|
+
}
|
|
612
|
+
classMap = this._mapCache.getClassMap(ecClassId);
|
|
613
|
+
if (!classMap) {
|
|
614
|
+
// fallback to root map for table.
|
|
615
|
+
classMap = this._mapCache.getClassMap(table.exclusiveRootClassId);
|
|
616
|
+
if (classMap)
|
|
617
|
+
fallbackClassId = table.exclusiveRootClassId;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (!classMap)
|
|
621
|
+
throw new Error(`unable to load class map`);
|
|
622
|
+
if (!classIdPresetInChange && !ecClassId)
|
|
623
|
+
ecClassId = classMap.id;
|
|
624
|
+
if (this._allowedClasses.size !== 0) {
|
|
625
|
+
if (!this._allowedClasses.has(classMap.id))
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const $meta = {
|
|
629
|
+
tables: [this.reader.tableName],
|
|
630
|
+
op: this.reader.op,
|
|
631
|
+
className: classMap.name,
|
|
632
|
+
fallbackClassId,
|
|
633
|
+
changeIndexes: [this.reader.changeIndex],
|
|
634
|
+
};
|
|
635
|
+
if (this.reader.op === "Inserted" && change.inserted) {
|
|
636
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
637
|
+
this.inserted = { ECClassId: ecClassId, ECInstanceId: "" };
|
|
638
|
+
if (!this.disableMetaData)
|
|
639
|
+
this.inserted.$meta = { ...$meta, stage: "New" };
|
|
640
|
+
this.transform(classMap, change.inserted, table, this.inserted);
|
|
641
|
+
}
|
|
642
|
+
else if (this.reader.op === "Deleted" && change.deleted) {
|
|
643
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
644
|
+
this.deleted = { ECClassId: ecClassId, ECInstanceId: "" };
|
|
645
|
+
if (!this.disableMetaData)
|
|
646
|
+
this.deleted.$meta = { ...$meta, stage: "Old" };
|
|
647
|
+
this.transform(classMap, change.deleted, table, this.deleted);
|
|
648
|
+
}
|
|
649
|
+
else if (change.inserted && change.deleted) {
|
|
650
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
651
|
+
this.inserted = { ECClassId: ecClassId, ECInstanceId: "" };
|
|
652
|
+
if (!this.disableMetaData)
|
|
653
|
+
this.inserted.$meta = { ...$meta, stage: "New" };
|
|
654
|
+
this.transform(classMap, change.inserted, table, this.inserted);
|
|
655
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
656
|
+
this.deleted = { ECClassId: ecClassId, ECInstanceId: "" };
|
|
657
|
+
if (!this.disableMetaData)
|
|
658
|
+
this.deleted.$meta = { ...$meta, stage: "Old" };
|
|
659
|
+
this.transform(classMap, change.deleted, table, this.deleted);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
throw new Error("unable to read EC changes");
|
|
663
|
+
}
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return this.reader.hasRow;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Transform nav change column into navigation EC property
|
|
671
|
+
* @param prop navigation property definition.
|
|
672
|
+
* @param change sqlite change.
|
|
673
|
+
* @param out ec instance that will be updated with navigation property.
|
|
674
|
+
*/
|
|
675
|
+
transformNavigationProperty(prop, change, out) {
|
|
676
|
+
const idCol = prop.columns.filter(($) => $.accessString.endsWith(".Id")).at(0);
|
|
677
|
+
if (!idCol) {
|
|
678
|
+
throw new Error("invalid map for nav property");
|
|
679
|
+
}
|
|
680
|
+
const idValue = change[idCol.column];
|
|
681
|
+
if (typeof idValue === "undefined")
|
|
682
|
+
return;
|
|
683
|
+
ChangesetECAdaptor.setValue(out, idCol.accessString, idValue);
|
|
684
|
+
const relClassIdCol = prop.columns.filter(($) => $.accessString.endsWith(".RelECClassId")).at(0);
|
|
685
|
+
if (!relClassIdCol) {
|
|
686
|
+
throw new Error("invalid map for nav property");
|
|
687
|
+
}
|
|
688
|
+
const relClassIdValue = relClassIdCol.isVirtual ? prop.navigationRelationship?.classId : change[relClassIdCol.column];
|
|
689
|
+
if (typeof relClassIdValue === "undefined")
|
|
690
|
+
return;
|
|
691
|
+
ChangesetECAdaptor.setValue(out, relClassIdCol.accessString, relClassIdValue);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Transform sqlite change into EC change.
|
|
695
|
+
* @param classMap classMap use to deserialize sqlite change into EC change.
|
|
696
|
+
* @param change sqlite change from changeset.
|
|
697
|
+
* @param table table definition of sqlite change provided.
|
|
698
|
+
* @param out EC changeset that will be updated with properties.
|
|
699
|
+
*/
|
|
700
|
+
transform(classMap, change, table, out) {
|
|
701
|
+
// transform change row to instance
|
|
702
|
+
for (const prop of classMap.properties) {
|
|
703
|
+
if (prop.kind === "PrimitiveArray" || prop.kind === "StructArray") {
|
|
704
|
+
// Arrays not supported
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (prop.columns.filter((_) => _.isVirtual).length === prop.columns.length) {
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
if (prop.kind === "Navigation") {
|
|
711
|
+
this.transformNavigationProperty(prop, change, out);
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
for (const col of prop.columns) {
|
|
715
|
+
if (col.table !== table.name)
|
|
716
|
+
continue;
|
|
717
|
+
const columnValue = change[col.column];
|
|
718
|
+
if (typeof columnValue === "undefined")
|
|
719
|
+
continue;
|
|
720
|
+
if (columnValue !== null) {
|
|
721
|
+
if (prop.primitiveType === "DateTime") {
|
|
722
|
+
const dt = DateTime.fromJulianDay(columnValue, prop.dateTimeInfo?.dateTimeKind === "Local");
|
|
723
|
+
ChangesetECAdaptor.setValue(out, col.accessString, dt.toISOString());
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (prop.extendedTypeName === "BeGuid") {
|
|
727
|
+
ChangesetECAdaptor.setValue(out, col.accessString, this.debugFlags.replaceGuidWithEllipsis ? "..." : ChangesetECAdaptor.convertBinaryToGuid(columnValue));
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (prop.extendedTypeName === "GeometryStream") {
|
|
731
|
+
ChangesetECAdaptor.setValue(out, col.accessString, this.debugFlags.replaceGeomWithEllipsis ? "..." : columnValue);
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (prop.primitiveType === "Binary") {
|
|
735
|
+
ChangesetECAdaptor.setValue(out, col.accessString, this.debugFlags.replaceBlobWithEllipsis ? "..." : columnValue);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
ChangesetECAdaptor.setValue(out, col.accessString, columnValue);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
exports.ChangesetECAdaptor = ChangesetECAdaptor;
|
|
746
|
+
//# sourceMappingURL=ChangesetECAdaptor.js.map
|