UNPKG

@itwin/core-backend

Version:
626 lines • 30.5 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module iModels */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TxnManager = exports.ChangeMergeManager = void 0; exports.setMaxEntitiesPerEvent = setMaxEntitiesPerEvent; const touch = require("touch"); const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const BackendLoggerCategory_1 = require("./BackendLoggerCategory"); const IpcHost_1 = require("./IpcHost"); const Symbols_1 = require("./internal/Symbols"); const ChangesetConflictArgs_1 = require("./internal/ChangesetConflictArgs"); /** Strictly for tests. @internal */ function setMaxEntitiesPerEvent(max) { const prevMax = ChangedEntitiesProc.maxPerEvent; ChangedEntitiesProc.maxPerEvent = max; return prevMax; } /** Maintains an ordered array of entity Ids and a parallel array containing the index of the corresponding entity's class Id. */ class ChangedEntitiesArray { entityIds = new core_bentley_1.OrderedId64Array(); _classIndices = []; _classIds; constructor(classIds) { this._classIds = classIds; } insert(entityId, classId) { const entityIndex = this.entityIds.insert(entityId); const classIndex = this._classIds.insert(classId); (0, core_bentley_1.assert)(classIndex >= 0); if (this.entityIds.length !== this._classIndices.length) { // New entity - insert corresponding class index entry. this._classIndices.splice(entityIndex, 0, classIndex); } else { // Existing entity - update corresponding class index. // (We do this because apparently connectors can (very rarely) change the class Id of an existing element). this._classIndices[entityIndex] = classIndex; } (0, core_bentley_1.assert)(this.entityIds.length === this._classIndices.length); } clear() { this.entityIds.clear(); this._classIndices.length = 0; } addToChangedEntities(entities, type) { if (this.entityIds.length > 0) entities[type] = core_bentley_1.CompressedId64Set.compressIds(this.entityIds); entities[`${type}Meta`] = this._classIndices; } iterable(classIds) { function* iterator(entityIds, classIndices) { const entity = { id: "", classId: "" }; for (let i = 0; i < entityIds.length; i++) { entity.id = entityIds[i]; entity.classId = classIds[classIndices[i]]; yield entity; } } return { [Symbol.iterator]: () => iterator(this.entityIds.array, this._classIndices), }; } } class ChangedEntitiesProc { _classIds = new core_bentley_1.IndexMap((lhs, rhs) => (0, core_bentley_1.compareStrings)(lhs, rhs)); _inserted = new ChangedEntitiesArray(this._classIds); _deleted = new ChangedEntitiesArray(this._classIds); _updated = new ChangedEntitiesArray(this._classIds); _currSize = 0; static maxPerEvent = 1000; static process(iModel, mgr) { if (mgr.isDisposed) { // The iModel is being closed. Do not prepare new sqlite statements. return; } this.processChanges(iModel, mgr.onElementsChanged, "notifyElementsChanged"); this.processChanges(iModel, mgr.onModelsChanged, "notifyModelsChanged"); } populateMetadata(db, classIds) { // Ensure metadata for all class Ids is loaded. Loading metadata for a derived class loads metadata for all of its superclasses. // eslint-disable-next-line @typescript-eslint/no-deprecated const classIdsToLoad = classIds.filter((x) => undefined === db.classMetaDataRegistry.findByClassId(x)); if (classIdsToLoad.length > 0) { const classIdsStr = classIdsToLoad.join(","); const sql = `SELECT ec_class.Name, ec_class.Id, ec_schema.Name FROM ec_class JOIN ec_schema WHERE ec_schema.Id = ec_class.SchemaId AND ec_class.Id IN (${classIdsStr})`; db.withPreparedSqliteStatement(sql, (stmt) => { while (stmt.step() === core_bentley_1.DbResult.BE_SQLITE_ROW) { const classFullName = `${stmt.getValueString(2)}:${stmt.getValueString(0)}`; // eslint-disable-next-line @typescript-eslint/no-deprecated db.tryGetMetaData(classFullName); } }); } // Define array indices for the metadata array entries correlating to the class Ids in the input list. const nameToIndex = new Map(); for (const classId of classIds) { // eslint-disable-next-line @typescript-eslint/no-deprecated const meta = db.classMetaDataRegistry.findByClassId(classId); nameToIndex.set(meta?.ecclass ?? "", nameToIndex.size); } const result = []; function addMetadata(name, index) { const bases = []; result[index] = { name, bases }; // eslint-disable-next-line @typescript-eslint/no-deprecated const meta = db.tryGetMetaData(name); if (!meta) { return; } for (const baseClassName of meta.baseClasses) { let baseClassIndex = nameToIndex.get(baseClassName); if (undefined === baseClassIndex) { baseClassIndex = nameToIndex.size; nameToIndex.set(baseClassName, baseClassIndex); addMetadata(baseClassName, baseClassIndex); } bases.push(baseClassIndex); } } for (const [name, index] of nameToIndex) { if (index >= classIds.length) { // Entries beyond this are base classes for the classes in `classIds` - don't reprocess them. break; } addMetadata(name, index); } return result; } sendEvent(iModel, evt, evtName) { if (this._currSize === 0) return; const classIds = this._classIds.toArray(); // Notify backend listeners. const txnEntities = { inserts: this._inserted.iterable(classIds), deletes: this._deleted.iterable(classIds), updates: this._updated.iterable(classIds), }; evt.raiseEvent(txnEntities); // Notify frontend listeners. const entities = { insertedMeta: [], updatedMeta: [], deletedMeta: [], meta: this.populateMetadata(iModel, classIds), }; this._inserted.addToChangedEntities(entities, "inserted"); this._deleted.addToChangedEntities(entities, "deleted"); this._updated.addToChangedEntities(entities, "updated"); IpcHost_1.IpcHost.notifyTxns(iModel, evtName, entities); // Reset state. this._inserted.clear(); this._deleted.clear(); this._updated.clear(); this._classIds.clear(); this._currSize = 0; } static processChanges(iModel, changedEvent, evtName) { try { const maxSize = this.maxPerEvent; const changes = new ChangedEntitiesProc(); const select = "notifyElementsChanged" === evtName ? "SELECT ElementId, ChangeType, ECClassId FROM temp.txn_Elements" : "SELECT ModelId, ChangeType, ECClassId FROM temp.txn_Models"; iModel.withPreparedSqliteStatement(select, (sql) => { const stmt = sql.stmt; while (sql.step() === core_bentley_1.DbResult.BE_SQLITE_ROW) { const id = stmt.getValueId(0); const classId = stmt.getValueId(2); switch (stmt.getValueInteger(1)) { case 0: changes._inserted.insert(id, classId); break; case 1: changes._updated.insert(id, classId); break; case 2: changes._deleted.insert(id, classId); break; } if (++changes._currSize >= maxSize) changes.sendEvent(iModel, changedEvent, evtName); } }); changes.sendEvent(iModel, changedEvent, evtName); } catch (err) { core_bentley_1.Logger.logError(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, core_bentley_1.BentleyError.getErrorMessage(err)); } } } /** * @internal * Manages conflict resolution during a merge operation. */ class ChangeMergeManager { _iModel; _conflictHandlers; constructor(_iModel) { this._iModel = _iModel; } resume() { this._iModel[Symbols_1._nativeDb].pullMergeResume(); } inProgress() { return this._iModel[Symbols_1._nativeDb].pullMergeInProgress(); } onConflict(args) { let curr = this._conflictHandlers; while (curr) { const resolution = curr.handler(args); if (resolution !== undefined) { core_bentley_1.Logger.logTrace(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, `Conflict handler ${curr.id} resolved conflict`); return resolution; } curr = curr.next; } return undefined; } addConflictHandler(args) { const idExists = (id) => { let curr = this._conflictHandlers; while (curr) { if (curr.id === id) return true; curr = curr.next; } return false; }; if (idExists(args.id)) throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, `Conflict handler with id ${args.id} already exists`); this._conflictHandlers = { ...args, next: this._conflictHandlers }; } removeConflictHandler(id) { if (!this._conflictHandlers) return; if (this._conflictHandlers?.id === id) { this._conflictHandlers = this._conflictHandlers.next; return; } let prev = this._conflictHandlers; let curr = this._conflictHandlers?.next; while (curr) { if (curr.id === id) { prev.next = curr.next; return; } prev = curr; curr = curr.next; } } } exports.ChangeMergeManager = ChangeMergeManager; /** Manages local changes to a [[BriefcaseDb]] or [[StandaloneDb]] via [Txns]($docs/learning/InteractiveEditing.md) * @public @preview */ class TxnManager { _iModel; /** @internal */ _isDisposed = false; /** @internal */ get isDisposed() { return this._isDisposed; } /** @internal */ changeMergeManager; /** @internal */ constructor(_iModel) { this._iModel = _iModel; this.changeMergeManager = new ChangeMergeManager(_iModel); _iModel.onBeforeClose.addOnce(() => { this._isDisposed = true; }); } /** Array of errors from dependency propagation */ validationErrors = []; get _nativeDb() { return this._iModel[Symbols_1._nativeDb]; } _getElementClass(elClassName) { return this._iModel.getJsClass(elClassName); } _getRelationshipClass(relClassName) { return this._iModel.getJsClass(relClassName); } /** If a -watch file exists for this iModel, update its timestamp so watching processes can be * notified that we've modified the briefcase. * @internal Used by IModelDb on push/pull. */ touchWatchFile() { // This is an async call. We don't have any reason to await it. // eslint-disable-next-line @typescript-eslint/no-floating-promises touch(this._iModel.watchFilePathName, { nocreate: true }); } /** @internal */ _onBeforeOutputsHandled(elClassName, elId) { this._getElementClass(elClassName).onBeforeOutputsHandled(elId, this._iModel); } /** @internal */ _onAllInputsHandled(elClassName, elId) { this._getElementClass(elClassName).onAllInputsHandled(elId, this._iModel); } /** @internal */ _onRootChanged(props) { this._getRelationshipClass(props.classFullName).onRootChanged(props, this._iModel); } /** @internal */ _onDeletedDependency(props) { this._getRelationshipClass(props.classFullName).onDeletedDependency(props, this._iModel); } /** @internal */ _onBeginValidate() { this.validationErrors.length = 0; } /** called from native code after validation of a Txn, either from saveChanges or apply changeset. * @internal */ _onEndValidate() { ChangedEntitiesProc.process(this._iModel, this); this.onEndValidation.raiseEvent(); // TODO: if (this.validationErrors.length !== 0) throw new IModelError(validation ...) } /** @internal */ _onGeometryChanged(modelProps) { this.onGeometryChanged.raiseEvent(modelProps); IpcHost_1.IpcHost.notifyEditingScope(this._iModel, "notifyGeometryChanged", modelProps); // send to frontend } /** @internal */ _onGeometryGuidsChanged(changes) { this.onModelGeometryChanged.raiseEvent(changes); IpcHost_1.IpcHost.notifyTxns(this._iModel, "notifyGeometryGuidsChanged", changes); } /** @internal */ _onCommit() { this.onCommit.raiseEvent(); IpcHost_1.IpcHost.notifyTxns(this._iModel, "notifyCommit"); } /** @internal */ _onCommitted() { this.touchWatchFile(); this.onCommitted.raiseEvent(); IpcHost_1.IpcHost.notifyTxns(this._iModel, "notifyCommitted", this.hasPendingTxns, Date.now()); } /** @internal */ _onReplayExternalTxns() { this.onReplayExternalTxns.raiseEvent(); IpcHost_1.IpcHost.notifyTxns(this._iModel, "notifyReplayExternalTxns"); } /** @internal */ _onReplayedExternalTxns() { this.onReplayedExternalTxns.raiseEvent(); IpcHost_1.IpcHost.notifyTxns(this._iModel, "notifyReplayedExternalTxns"); } /** @internal */ _onChangesApplied() { this._iModel.clearCaches(); ChangedEntitiesProc.process(this._iModel, this); this.onChangesApplied.raiseEvent(); IpcHost_1.IpcHost.notifyTxns(this._iModel, "notifyChangesApplied"); } /** @internal */ _onBeforeUndoRedo(isUndo) { this.onBeforeUndoRedo.raiseEvent(isUndo); IpcHost_1.IpcHost.notifyTxns(this._iModel, "notifyBeforeUndoRedo", isUndo); } /** @internal */ _onAfterUndoRedo(isUndo) { this.touchWatchFile(); this.onAfterUndoRedo.raiseEvent(isUndo); IpcHost_1.IpcHost.notifyTxns(this._iModel, "notifyAfterUndoRedo", isUndo); } _onRebaseTxnBegin(txn) { this.onRebaseTxnBegin.raiseEvent(txn); } _onRebaseTxnEnd(txn) { this.onRebaseTxnEnd.raiseEvent(txn); } _onRebaseLocalTxnConflict(internalArg) { const args = new ChangesetConflictArgs_1.RebaseChangesetConflictArgs(internalArg, this._iModel); const getChangeMetaData = () => { return { parent: this._iModel.changeset, txn: args.txn, table: args.tableName, op: args.opcode, cause: args.cause, indirect: args.indirect, primarykey: args.getPrimaryKeyValues(), fkConflictCount: args.cause === "ForeignKey" ? args.getForeignKeyConflicts() : undefined, }; }; // Default conflict resolution for which custom handler is never called. if (args.cause === "Data" && !args.indirect) { if (args.tableName === "be_Prop") { if (args.getValueText(0, "Old") === "ec_Db" && args.getValueText(1, "Old") === "localDbInfo") { return core_bentley_1.DbConflictResolution.Skip; } } if (args.tableName.startsWith("ec_")) { return core_bentley_1.DbConflictResolution.Skip; } } if (args.cause === "Conflict") { if (args.tableName.startsWith("ec_")) { return core_bentley_1.DbConflictResolution.Skip; } } try { const resolution = this.changeMergeManager.onConflict(args); if (resolution !== undefined) return resolution; } catch (err) { const msg = `Rebase failed. Custom conflict handler should not throw exception. Aborting txn. ${core_bentley_1.BentleyError.getErrorMessage(err)}`; core_bentley_1.Logger.logError(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, msg, getChangeMetaData()); args.setLastError(msg); return core_bentley_1.DbConflictResolution.Abort; } if (args.cause === "Data" && !args.indirect) { core_bentley_1.Logger.logInfo(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, "UPDATE/DELETE before value do not match with one in db or CASCADE action was triggered. Local change will replace existing.", getChangeMetaData()); return core_bentley_1.DbConflictResolution.Replace; } if (args.cause === "Conflict") { const msg = "PRIMARY KEY insert conflict. Aborting rebase."; core_bentley_1.Logger.logError(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, msg, getChangeMetaData()); args.setLastError(msg); return core_bentley_1.DbConflictResolution.Abort; } if (args.cause === "ForeignKey") { const msg = `Foreign key conflicts in ChangeSet. Aborting rebase.`; core_bentley_1.Logger.logInfo(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, msg, getChangeMetaData()); args.setLastError(msg); return core_bentley_1.DbConflictResolution.Abort; } if (args.cause === "NotFound") { core_bentley_1.Logger.logInfo(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, "PRIMARY KEY not found. Skipping local change.", getChangeMetaData()); return core_bentley_1.DbConflictResolution.Skip; } if (args.cause === "Constraint") { core_bentley_1.Logger.logInfo(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, "Constraint voilation detected. Generally caused by db constraints like UNIQUE index. Skipping local change.", getChangeMetaData()); return core_bentley_1.DbConflictResolution.Skip; } return core_bentley_1.DbConflictResolution.Replace; } /** Dependency handlers may call method this to report a validation error. * @param error The error. If error.fatal === true, the transaction will cancel rather than commit. */ reportError(error) { this.validationErrors.push(error); this._nativeDb.logTxnError(error.fatal); } /** Determine whether any fatal validation errors have occurred during dependency propagation. */ get hasFatalError() { return this._nativeDb.hasFatalTxnError(); } /** @internal */ onEndValidation = new core_bentley_1.BeEvent(); /** Called after validation completes from [[IModelDb.saveChanges]]. * The argument to the event holds the list of elements that were inserted, updated, and deleted. * @note If there are many changed elements in a single Txn, the notifications are sent in batches so this event *may be called multiple times* per Txn. */ onElementsChanged = new core_bentley_1.BeEvent(); /** Called after validation completes from [[IModelDb.saveChanges]]. * The argument to the event holds the list of models that were inserted, updated, and deleted. * @note If there are many changed models in a single Txn, the notifications are sent in batches so this event *may be called multiple times* per Txn. */ onModelsChanged = new core_bentley_1.BeEvent(); /** Event raised after the geometry within one or more [[GeometricModel]]s is modified by applying a changeset or validation of a transaction. * A model's geometry can change as a result of: * - Insertion or deletion of a geometric element within the model; or * - Modification of an existing element's geometric properties; or * - An explicit request to flag it as changed via [[IModelDb.Models.updateModel]]. */ onModelGeometryChanged = new core_bentley_1.BeEvent(); onGeometryChanged = new core_bentley_1.BeEvent(); /** Event raised before a commit operation is performed. Initiated by a call to [[IModelDb.saveChanges]], unless there are no changes to save. */ onCommit = new core_bentley_1.BeEvent(); /** Event raised after a commit operation has been performed. Initiated by a call to [[IModelDb.saveChanges]], even if there were no changes to save. */ onCommitted = new core_bentley_1.BeEvent(); /** Event raised after a ChangeSet has been applied to this briefcase */ onChangesApplied = new core_bentley_1.BeEvent(); /** Event raised before an undo/redo operation is performed. */ onBeforeUndoRedo = new core_bentley_1.BeEvent(); /** Event raised after an undo/redo operation has been performed. * @param _action The action that was performed. */ onAfterUndoRedo = new core_bentley_1.BeEvent(); /** Event raised for a read-only briefcase that was opened with the `watchForChanges` flag enabled when changes made by another connection are applied to the briefcase. * @see [[onReplayedExternalTxns]] for the event raised after all such changes have been applied. */ onReplayExternalTxns = new core_bentley_1.BeEvent(); /** Event raised for a read-only briefcase that was opened with the `watchForChanges` flag enabled when changes made by another connection are applied to the briefcase. * @see [[onReplayExternalTxns]] for the event raised before the changes are applied. */ onReplayedExternalTxns = new core_bentley_1.BeEvent(); /** @internal */ onRebaseTxnBegin = new core_bentley_1.BeEvent(); /** @internal */ onRebaseTxnEnd = new core_bentley_1.BeEvent(); /** * if handler is set and it does not return undefiend then default handler will not be called * @internal * */ appCustomConflictHandler; /** * Restart the current TxnManager session. This causes all Txns in the current session to no longer be undoable (as if the file was closed * and reopened.) * @note This can be quite disconcerting to the user expecting to be able to undo previously made changes. It should only be used * under extreme circumstances where damage to the file or session could happen if the currently committed are reversed. Use sparingly and with care. * Probably a good idea to alert the user it happened. */ restartSession() { this._nativeDb.restartTxnSession(); } /** Determine whether current txn is propagating indirect changes or not. */ get isIndirectChanges() { return this._nativeDb.isIndirectChanges(); } /** Determine if there are currently any reversible (undoable) changes from this editing session. */ get isUndoPossible() { return this._nativeDb.isUndoPossible(); } /** Determine if there are currently any reinstatable (redoable) changes */ get isRedoPossible() { return this._nativeDb.isRedoPossible(); } /** Get the description of the operation that would be reversed by calling reverseTxns(1). * This is useful for showing the operation that would be undone, for example in a menu. */ getUndoString() { return this._nativeDb.getUndoString(); } /** Get a description of the operation that would be reinstated by calling reinstateTxn. * This is useful for showing the operation that would be redone, in a pull-down menu for example. */ getRedoString() { return this._nativeDb.getRedoString(); } /** Begin a new multi-Txn operation. This can be used to cause a series of Txns that would normally * be considered separate actions for undo to be grouped into a single undoable operation. This means that when reverseTxns(1) is called, * the entire group of changes are undone together. Multi-Txn operations can be nested and until the outermost operation is closed * all changes constitute a single operation. * @note This method must always be paired with a call to endMultiTxnAction. */ beginMultiTxnOperation() { return this._nativeDb.beginMultiTxnOperation(); } /** End a multi-Txn operation */ endMultiTxnOperation() { return this._nativeDb.endMultiTxnOperation(); } /** Return the depth of the multi-Txn stack. Generally for diagnostic use only. */ getMultiTxnOperationDepth() { return this._nativeDb.getMultiTxnOperationDepth(); } /** Reverse (undo) the most recent operation(s) to this IModelDb. * @param numOperations the number of operations to reverse. If this is greater than 1, the entire set of operations will * be reinstated together when/if ReinstateTxn is called. * @note If there are any outstanding uncommitted changes, they are reversed. * @note The term "operation" is used rather than Txn, since multiple Txns can be grouped together via [[beginMultiTxnOperation]]. So, * even if numOperations is 1, multiple Txns may be reversed if they were grouped together when they were made. * @note If numOperations is too large only the operations are reversible are reversed. */ reverseTxns(numOperations) { return this._nativeDb.reverseTxns(numOperations); } /** Reverse the most recent operation. */ reverseSingleTxn() { return this.reverseTxns(1); } /** Reverse all changes back to the beginning of the session. */ reverseAll() { return this._nativeDb.reverseAll(); } /** Reverse all changes back to a previously saved TxnId. * @param txnId a TxnId obtained from a previous call to GetCurrentTxnId. * @returns Success if the transactions were reversed, error status otherwise. * @see [[getCurrentTxnId]] [[cancelTo]] */ reverseTo(txnId) { return this._nativeDb.reverseTo(txnId); } /** Reverse and then cancel (make non-reinstatable) all changes back to a previous TxnId. * @param txnId a TxnId obtained from a previous call to [[getCurrentTxnId]] * @returns Success if the transactions were reversed and cleared, error status otherwise. */ cancelTo(txnId) { return this._nativeDb.cancelTo(txnId); } /** Reinstate the most recently reversed transaction. Since at any time multiple transactions can be reversed, it * may take multiple calls to this method to reinstate all reversed operations. * @returns Success if a reversed transaction was reinstated, error status otherwise. * @note If there are any outstanding uncommitted changes, they are canceled before the Txn is reinstated. */ reinstateTxn() { return this._iModel.reinstateTxn(); } /** Get the Id of the first transaction, if any. */ queryFirstTxnId() { return this._nativeDb.queryFirstTxnId(); } /** Get the successor of the specified TxnId */ queryNextTxnId(txnId) { return this._nativeDb.queryNextTxnId(txnId); } /** Get the predecessor of the specified TxnId */ queryPreviousTxnId(txnId) { return this._nativeDb.queryPreviousTxnId(txnId); } /** Get the Id of the current (tip) transaction. */ getCurrentTxnId() { return this._nativeDb.getCurrentTxnId(); } /** Get the description that was supplied when the specified transaction was saved. */ getTxnDescription(txnId) { return this._nativeDb.getTxnDescription(txnId); } /** Test if a TxnId is valid */ isTxnIdValid(txnId) { return this._nativeDb.isTxnIdValid(txnId); } /** Query if there are any pending Txns in this IModelDb that are waiting to be pushed. * @see [[IModelDb.pushChanges]] */ get hasPendingTxns() { return this._nativeDb.hasPendingTxns(); } /** * Query if there are any changes in memory that have yet to be saved to the IModelDb. * @see [[IModelDb.saveChanges]] */ get hasUnsavedChanges() { return this._nativeDb.hasUnsavedChanges(); } /** * Query if there are changes in memory that have not been saved to the iModelDb or if there are Txns that are waiting to be pushed. * @see [[IModelDb.saveChanges]] * @see [[IModelDb.pushChanges]] */ get hasLocalChanges() { return this.hasUnsavedChanges || this.hasPendingTxns; } /** Destroy the record of all local changes that have yet to be saved and/or pushed. * This permanently eradicates your changes - use with caution! * Typically, callers will want to subsequently use [[LockControl.releaseAllLocks]]. * After calling this function, [[hasLocalChanges]], [[hasPendingTxns]], and [[hasUnsavedChanges]] will all be `false`. */ deleteAllTxns() { this._nativeDb.deleteAllTxns(); } /** Obtain a list of the EC instances that have been changed locally by the [[BriefcaseDb]] associated with this `TxnManager` and have not yet been pushed to the iModel. * @beta */ queryLocalChanges(args) { if (!args) { args = { includedClasses: [], includeUnsavedChanges: false }; } return this._nativeDb.getLocalChanges(args.includedClasses ?? [], args.includeUnsavedChanges ?? false); } /** Query the number of bytes of memory currently allocated by SQLite to keep track of * changes to the iModel, for debugging/diagnostic purposes, as reported by [sqlite3session_memory_used](https://www.sqlite.org/session/sqlite3session_memory_used.html). */ getChangeTrackingMemoryUsed() { return this._iModel[Symbols_1._nativeDb].getChangeTrackingMemoryUsed(); } } exports.TxnManager = TxnManager; //# sourceMappingURL=TxnManager.js.map