UNPKG

@itwin/core-backend

Version:
414 lines • 19.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ECDb = exports.ECDbOpenMode = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module ECDb */ const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const BackendLoggerCategory_1 = require("./BackendLoggerCategory"); const ConcurrentQuery_1 = require("./ConcurrentQuery"); const ECSqlStatement_1 = require("./ECSqlStatement"); const NativePlatform_1 = require("./internal/NativePlatform"); const SqliteStatement_1 = require("./SqliteStatement"); const Symbols_1 = require("./internal/Symbols"); const loggerCategory = BackendLoggerCategory_1.BackendLoggerCategory.ECDb; /** Modes for how to open [ECDb]($backend) files. * @public */ var ECDbOpenMode; (function (ECDbOpenMode) { ECDbOpenMode[ECDbOpenMode["Readonly"] = 0] = "Readonly"; ECDbOpenMode[ECDbOpenMode["ReadWrite"] = 1] = "ReadWrite"; /** Opens the file read-write and upgrades the file if necessary to the latest file format version. */ ECDbOpenMode[ECDbOpenMode["FileUpgrade"] = 2] = "FileUpgrade"; })(ECDbOpenMode || (exports.ECDbOpenMode = ECDbOpenMode = {})); /** An ECDb file * @public */ class ECDb { _nativeDb; // eslint-disable-next-line @typescript-eslint/no-deprecated _statementCache = new SqliteStatement_1.StatementCache(); _sqliteStatementCache = new SqliteStatement_1.StatementCache(); /** only for tests * @internal */ resetSqliteCache(size) { this._sqliteStatementCache.clear(); this._sqliteStatementCache = new SqliteStatement_1.StatementCache(size); } constructor() { this._nativeDb = new NativePlatform_1.IModelNative.platform.ECDb(); } /** Call this function when finished with this ECDb object. This releases the native resources held by the * ECDb object. */ [Symbol.dispose]() { if (!this._nativeDb) return; this.closeDb(); this._nativeDb.dispose(); this._nativeDb = undefined; } /** * Attach an iModel file to this connection and load and register its schemas. * @note There are some reserve tablespace names that cannot be used. They are 'main', 'schema_sync_db', 'ecchange' & 'temp' * @param fileName IModel file name * @param alias identifier for the attached file. This identifer is used to access schema from the attached file. e.g. if alias is 'abc' then schema can be accessed using 'abc.MySchema.MyClass' */ attachDb(fileName, alias) { if (alias.toLowerCase() === "main" || alias.toLowerCase() === "schema_sync_db" || alias.toLowerCase() === "ecchange" || alias.toLowerCase() === "temp") { throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Reserved tablespace name cannot be used"); } this[Symbols_1._nativeDb].attachDb(fileName, alias); } /** * Detach the attached file from this connection. The attached file is closed and its schemas are unregistered. * @note There are some reserve tablespace names that cannot be used. They are 'main', 'schema_sync_db', 'ecchange' & 'temp' * @param alias identifer that was used in the call to [[attachDb]] */ detachDb(alias) { if (alias.toLowerCase() === "main" || alias.toLowerCase() === "schema_sync_db" || alias.toLowerCase() === "ecchange" || alias.toLowerCase() === "temp") { throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Reserved tablespace name cannot be used"); } this.clearStatementCache(); this[Symbols_1._nativeDb].detachDb(alias); } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */ dispose() { this[Symbol.dispose](); } /** Create an ECDb * @param pathName The path to the ECDb file to create. * @throws [IModelError]($common) if the operation failed. */ createDb(pathName) { const status = this[Symbols_1._nativeDb].createDb(pathName); if (status !== core_bentley_1.DbResult.BE_SQLITE_OK) throw new core_common_1.IModelError(status, "Failed to created ECDb"); } /** Open the ECDb. * @param pathName The path to the ECDb file to open * @param openMode Open mode * @throws [IModelError]($common) if the operation failed. */ openDb(pathName, openMode = ECDbOpenMode.Readonly) { const nativeOpenMode = openMode === ECDbOpenMode.Readonly ? core_bentley_1.OpenMode.Readonly : core_bentley_1.OpenMode.ReadWrite; const tryUpgrade = openMode === ECDbOpenMode.FileUpgrade; const status = this[Symbols_1._nativeDb].openDb(pathName, nativeOpenMode, tryUpgrade); if (status !== core_bentley_1.DbResult.BE_SQLITE_OK) throw new core_common_1.IModelError(status, "Failed to open ECDb"); } /** Returns true if the ECDb is open */ get isOpen() { return this[Symbols_1._nativeDb].isOpen(); } /** Close the Db after saving any uncommitted changes. * @throws [IModelError]($common) if the database is not open. */ closeDb() { this._statementCache.clear(); this._sqliteStatementCache.clear(); this[Symbols_1._nativeDb].closeDb(); } /** @internal use to test statement caching */ clearStatementCache() { this._statementCache.clear(); } /** @internal use to test statement caching */ getCachedStatementCount() { return this._statementCache.size; } /** Commit the outermost transaction, writing changes to the file. Then, restart the transaction. * @param changesetName The name of the operation that generated these changes. * @throws [IModelError]($common) if the database is not open or if the operation failed. */ saveChanges(changesetName) { const status = this[Symbols_1._nativeDb].saveChanges(changesetName); if (status !== core_bentley_1.DbResult.BE_SQLITE_OK) throw new core_common_1.IModelError(status, "Failed to save changes"); } /** Abandon (cancel) the outermost transaction, discarding all changes since last save. Then, restart the transaction. * @throws [IModelError]($common) if the database is not open or if the operation failed. */ abandonChanges() { const status = this[Symbols_1._nativeDb].abandonChanges(); if (status !== core_bentley_1.DbResult.BE_SQLITE_OK) throw new core_common_1.IModelError(status, "Failed to abandon changes"); } /** Import a schema. * * If the import was successful, the database is automatically saved to disk. * @param pathName Path to ECSchema XML file to import. * @throws [IModelError]($common) if the database is not open or if the operation failed. */ importSchema(pathName) { const status = this[Symbols_1._nativeDb].importSchema(pathName); if (status !== core_bentley_1.DbResult.BE_SQLITE_OK) { core_bentley_1.Logger.logError(loggerCategory, `Failed to import schema from '${pathName}'.`); throw new core_common_1.IModelError(status, `Failed to import schema from '${pathName}'.`); } } /** * Returns the full schema for the input name. * @param name The name of the schema e.g. 'ECDbMeta' * @returns The SchemaProps for the requested schema * @throws if the schema can not be found or loaded. */ getSchemaProps(name) { return this[Symbols_1._nativeDb].getSchemaProps(name); } /** * Use a prepared ECSQL statement, potentially from the statement cache. If the requested statement doesn't exist * in the statement cache, a new statement is prepared. After the callback completes, the statement is reset and saved * in the statement cache so it can be reused in the future. Use this method for ECSQL statements that will be * reused often and are expensive to prepare. The statement cache holds the most recently used statements, discarding * the oldest statements as it fills. For statements you don't intend to reuse, instead use [[withStatement]]. * @param sql The SQLite SQL statement to execute * @param callback the callback to invoke on the prepared statement * @param logErrors Determines if error will be logged if statement fail to prepare * @returns the value returned by `callback`. * @see [[withWriteStatement]] * @beta */ withCachedWriteStatement(ecsql, callback, logErrors = true) { // eslint-disable-next-line @typescript-eslint/no-deprecated const stmt = this._statementCache.findAndRemove(ecsql) ?? this.prepareStatement(ecsql, logErrors); const release = () => this._statementCache.addOrDispose(stmt); try { const val = callback(new ECSqlStatement_1.ECSqlWriteStatement(stmt)); if (val instanceof Promise) { val.then(release, release); } else { release(); } return val; } catch (err) { release(); throw err; } } /** * Prepared and execute a callback on an ECSQL statement. After the callback completes the statement is disposed. * Use this method for ECSQL statements are either not expected to be reused, or are not expensive to prepare. * For statements that will be reused often, instead use [[withPreparedStatement]]. * @param sql The SQLite SQL statement to execute * @param callback the callback to invoke on the prepared statement * @param logErrors Determines if error will be logged if statement fail to prepare * @returns the value returned by `callback`. * @see [[withCachedWriteStatement]] * @beta */ withWriteStatement(ecsql, callback, logErrors = true) { const stmt = this.prepareWriteStatement(ecsql, logErrors); const release = () => { }; try { const val = callback(stmt); if (val instanceof Promise) { val.then(release, release); } else { release(); } return val; } catch (err) { release(); throw err; } } /** Prepare an ECSQL statement. * @param ecsql The ECSQL statement to prepare * @param logErrors Determines if error will be logged if statement fail to prepare * @throws [IModelError]($common) if there is a problem preparing the statement. * @beta */ prepareWriteStatement(ecsql, logErrors = true) { // eslint-disable-next-line @typescript-eslint/no-deprecated return new ECSqlStatement_1.ECSqlWriteStatement(this.prepareStatement(ecsql, logErrors)); } /** * Use a prepared ECSQL statement, potentially from the statement cache. If the requested statement doesn't exist * in the statement cache, a new statement is prepared. After the callback completes, the statement is reset and saved * in the statement cache so it can be reused in the future. Use this method for ECSQL statements that will be * reused often and are expensive to prepare. The statement cache holds the most recently used statements, discarding * the oldest statements as it fills. For statements you don't intend to reuse, instead use [[withStatement]]. * @param sql The SQLite SQL statement to execute * @param callback the callback to invoke on the prepared statement * @param logErrors Determines if error will be logged if statement fail to prepare * @returns the value returned by `callback`. * @see [[withStatement]] * @public * @deprecated in 4.11 - will not be removed until after 2026-06-13. Use [[createQueryReader]] for SELECT statements and [[withCachedWriteStatement]] for INSERT/UPDATE/DELETE instead. */ // eslint-disable-next-line @typescript-eslint/no-deprecated withPreparedStatement(ecsql, callback, logErrors = true) { // eslint-disable-next-line @typescript-eslint/no-deprecated const stmt = this._statementCache.findAndRemove(ecsql) ?? this.prepareStatement(ecsql, logErrors); const release = () => this._statementCache.addOrDispose(stmt); try { const val = callback(stmt); if (val instanceof Promise) { val.then(release, release); } else { release(); } return val; } catch (err) { release(); throw err; } } /** * Prepared and execute a callback on an ECSQL statement. After the callback completes the statement is disposed. * Use this method for ECSQL statements are either not expected to be reused, or are not expensive to prepare. * For statements that will be reused often, instead use [[withPreparedStatement]]. * @param sql The SQLite SQL statement to execute * @param callback the callback to invoke on the prepared statement * @param logErrors Determines if error will be logged if statement fail to prepare * @returns the value returned by `callback`. * @see [[withPreparedStatement]] * @public * @deprecated in 4.11 - will not be removed until after 2026-06-13. Use [[createQueryReader]] for SELECT statements and [[withWriteStatement]] for INSERT/UPDATE/DELETE instead. */ // eslint-disable-next-line @typescript-eslint/no-deprecated withStatement(ecsql, callback, logErrors = true) { // eslint-disable-next-line @typescript-eslint/no-deprecated const stmt = this.prepareStatement(ecsql, logErrors); const release = () => stmt[Symbol.dispose](); try { const val = callback(stmt); if (val instanceof Promise) { val.then(release, release); } else { release(); } return val; } catch (err) { release(); throw err; } } /** Prepare an ECSQL statement. * @param ecsql The ECSQL statement to prepare * @param logErrors Determines if error will be logged if statement fail to prepare * @throws [IModelError]($common) if there is a problem preparing the statement. * @deprecated in 4.11 - will not be removed until after 2026-06-13. Use [[prepareWriteStatement]] when preparing an INSERT/UPDATE/DELETE statement or [[createQueryReader]] to execute a SELECT statement. */ // eslint-disable-next-line @typescript-eslint/no-deprecated prepareStatement(ecsql, logErrors = true) { // eslint-disable-next-line @typescript-eslint/no-deprecated const stmt = new ECSqlStatement_1.ECSqlStatement(); stmt.prepare(this[Symbols_1._nativeDb], ecsql, logErrors); return stmt; } /** * Use a prepared SQL statement, potentially from the statement cache. If the requested statement doesn't exist * in the statement cache, a new statement is prepared. After the callback completes, the statement is reset and saved * in the statement cache so it can be reused in the future. Use this method for SQL statements that will be * reused often and are expensive to prepare. The statement cache holds the most recently used statements, discarding * the oldest statements as it fills. For statements you don't intend to reuse, instead use [[withSqliteStatement]]. * @param sql The SQLite SQL statement to execute * @param callback the callback to invoke on the prepared statement * @param logErrors Determines if error will be logged if statement fail to prepare * @returns the value returned by `callback`. * @see [[withPreparedStatement]] * @public */ withPreparedSqliteStatement(sql, callback, logErrors = true) { const stmt = this._sqliteStatementCache.findAndRemove(sql) ?? this.prepareSqliteStatement(sql, logErrors); const release = () => this._sqliteStatementCache.addOrDispose(stmt); try { const val = callback(stmt); if (val instanceof Promise) { val.then(release, release); } else { release(); } return val; } catch (err) { release(); throw err; } } /** * Prepared and execute a callback on a SQL statement. After the callback completes the statement is disposed. * Use this method for SQL statements are either not expected to be reused, or are not expensive to prepare. * For statements that will be reused often, instead use [[withPreparedSqliteStatement]]. * @param sql The SQLite SQL statement to execute * @param callback the callback to invoke on the prepared statement * @param logErrors Determines if error will be logged if statement fail to prepare * @returns the value returned by `callback`. * @public */ withSqliteStatement(sql, callback, logErrors = true) { const stmt = this.prepareSqliteStatement(sql, logErrors); const release = () => stmt[Symbol.dispose](); try { const val = callback(stmt); if (val instanceof Promise) { val.then(release, release); } else { release(); } return val; } catch (err) { release(); throw err; } } /** Prepare an SQL statement. * @param sql The SQLite SQL statement to prepare * @param logErrors Determines if error will be logged if statement fail to prepare * @throws [IModelError]($common) if there is a problem preparing the statement. * @internal */ prepareSqliteStatement(sql, logErrors = true) { const stmt = new SqliteStatement_1.SqliteStatement(sql); stmt.prepare(this[Symbols_1._nativeDb], logErrors); return stmt; } /** @internal */ get [Symbols_1._nativeDb]() { (0, core_bentley_1.assert)(undefined !== this._nativeDb); return this._nativeDb; } /** Allow to execute query and read results along with meta data. The result are streamed. * * See also: * - [ECSQL Overview]($docs/learning/backend/ExecutingECSQL) * - [Code Examples]($docs/learning/backend/ECSQLCodeExamples) * - [ECSQL Row Format]($docs/learning/ECSQLRowFormat) * * @param params The values to bind to the parameters (if the ECSQL has any). * @param config Allow to specify certain flags which control how query is executed. * @returns Returns an [ECSqlReader]($common) which helps iterate over the result set and also give access to metadata. * @public * */ createQueryReader(ecsql, params, config) { if (!this._nativeDb || !this._nativeDb.isOpen()) { throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "db not open"); } const executor = { execute: async (request) => { return ConcurrentQuery_1.ConcurrentQuery.executeQueryRequest(this[Symbols_1._nativeDb], request); }, }; return new core_common_1.ECSqlReader(executor, ecsql, params, config); } } exports.ECDb = ECDb; //# sourceMappingURL=ECDb.js.map