UNPKG

@itwin/core-backend

Version:
337 lines • 12.6 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module SQLiteDb */ import { DbChangeStage, DbOpcode, DbResult } from "@itwin/core-bentley"; import { ECDb } from "./ECDb"; import { IModelNative } from "./internal/NativePlatform"; import { _nativeDb } from "./internal/Symbols"; /** * Read raw sqlite changeset from disk and enumerate changes. * It also optionally let you format change with schema from * a db provided. * @beta */ export class SqliteChangesetReader { db; _nativeReader = new IModelNative.platform.ChangesetReader(); _schemaCache = new Map(); _disableSchemaCheck = false; _changeIndex = 0; constructor( /** db from where sql schema will be read */ db) { this.db = db; } /** * Open changeset file from disk * @param args fileName of changeset reader and other options. * @returns SqliteChangesetReader instance */ static openFile(args) { const reader = new SqliteChangesetReader(args.db); reader._disableSchemaCheck = args.disableSchemaCheck ?? false; reader._nativeReader.openFile(args.fileName, args.invert ?? false); return reader; } /** * Group changeset file into single changeset and open that changeset. * @param args - The arguments for opening the changeset group. Requires an open db. * @returns The SqliteChangesetReader instance. */ static openGroup(args) { if (args.changesetFiles.length === 0) { throw new Error("changesetFiles must contain at least one file."); } const reader = new SqliteChangesetReader(args.db); reader._disableSchemaCheck = args.disableSchemaCheck ?? false; reader._nativeReader.openGroup(args.changesetFiles, args.db[_nativeDb], args.invert ?? false); return reader; } /** * Open txn change in iModel. * @param args iModel and other options. * @returns SqliteChangesetReader instance */ static openTxn(args) { if (args.db instanceof ECDb) { throw new Error("ECDb does not support openTxn"); } const reader = new SqliteChangesetReader(args.db); reader._disableSchemaCheck = args.disableSchemaCheck ?? false; reader._nativeReader.openTxn(args.db[_nativeDb], args.txnId, args.invert ?? false); return reader; } /** * Writes the changeset to a file. * @note can be use with openGroup() or openLocalChanges() to persist changeset. * @param args - The arguments for writing to the file. * @param args.fileName - The name of the file to write to. * @param args.containsSchemaChanges - Indicates whether the changeset contains schema changes. * @param args.overwriteFile - Indicates whether to override the file if it already exists. Default is false. */ writeToFile(args) { this._nativeReader.writeToFile(args.fileName, args.containsSchemaChanges, args.overwriteFile ?? false); } /** * Open local changes in iModel. * @param args iModel and other options. * @param args.db must be of type IModelDb * @returns SqliteChangesetReader instance */ static openLocalChanges(args) { const reader = new SqliteChangesetReader(args.db); reader._disableSchemaCheck = args.disableSchemaCheck ?? false; reader._nativeReader.openLocalChanges(args.db[_nativeDb], args.includeInMemoryChanges ?? false, args.invert ?? false); return reader; } /** check if schema check is disabled or not */ get disableSchemaCheck() { return this._disableSchemaCheck; } /** Move to next change in changeset * @returns true if there is current change false if reader is end of changeset. * @beta */ step() { if (this._nativeReader.step()) { this._changeIndex++; return true; } return false; } /** Check if reader current on a row * @beta */ get hasRow() { return this._nativeReader.hasRow(); } /** Check if its current change is indirect * @beta */ get isIndirect() { return this._nativeReader.isIndirectChange(); } /** Get count of columns in current change * @beta */ get columnCount() { return this._nativeReader.getColumnCount(); } /** Get operation that caused the change * @beta */ get op() { if (this._nativeReader.getOpCode() === DbOpcode.Insert) return "Inserted"; if (this._nativeReader.getOpCode() === DbOpcode.Delete) return "Deleted"; return "Updated"; } /** Get primary key value array * @beta */ get primaryKeyValues() { return this._nativeReader.getPrimaryKeys(); } /** Get primary key columns. * @note To this to work db arg must be set when opening changeset file. * @beta */ getPrimaryKeyColumnNames() { const cols = this.getColumnNames(this.tableName); if (!this._disableSchemaCheck && cols.length !== this.columnCount) throw new Error(`changeset table ${this.tableName} columns count does not match db declared table. ${this.columnCount} <> ${cols.length}`); return this._nativeReader.getPrimaryKeyColumnIndexes().map((i) => cols[i]); } /** Get current change table. * @beta */ get tableName() { return this._nativeReader.getTableName(); } /** * Get changed binary value for a column * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns value for changed column * @beta */ getChangeValueType(columnIndex, stage) { return this._nativeReader.getColumnValueType(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get changed binary value for a column * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns value for changed column * @beta */ getChangeValueBinary(columnIndex, stage) { return this._nativeReader.getColumnValueBinary(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get changed double value for a column * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns value for changed column * @beta */ getChangeValueDouble(columnIndex, stage) { return this._nativeReader.getColumnValueDouble(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get changed Id value for a column * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns value for changed column * @beta */ getChangeValueId(columnIndex, stage) { return this._nativeReader.getColumnValueId(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get changed integer value for a column * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns value for changed column * @beta */ getChangeValueInteger(columnIndex, stage) { return this._nativeReader.getColumnValueInteger(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get changed text value for a column * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns value for changed column * @beta */ getChangeValueText(columnIndex, stage) { return this._nativeReader.getColumnValueText(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Check if change value is null * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns true if value is null * @beta */ isColumnValueNull(columnIndex, stage) { return this._nativeReader.isColumnValueNull(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get change value type * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns change value type * @beta */ getColumnValueType(columnIndex, stage) { return this._nativeReader.getColumnValueType(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get changed value for a column * @param columnIndex index of column in current change * @param stage old or new value for change. * @returns value for changed column * @beta */ getChangeValue(columnIndex, stage) { return this._nativeReader.getColumnValue(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get all changed value in current change as array * @param stage old or new values for current change. * @returns array of values. * @beta */ getChangeValuesArray(stage) { return this._nativeReader.getRow(stage === "New" ? DbChangeStage.New : DbChangeStage.Old); } /** * Get change as object and format its content. * @param stage old or new value for current change. * @param args change format options * @returns return object or undefined * @beta */ getChangeValuesObject(stage, args = {}) { const cols = this.getColumnNames(this.tableName); const row = this.getChangeValuesArray(stage); if (!row) return undefined; process.env; const minLen = Math.min(cols.length, row.length); if (!this._disableSchemaCheck && cols.length !== this.columnCount) throw new Error(`changeset table ${this.tableName} columns count does not match db declared table. ${this.columnCount} <> ${cols.length}`); const out = {}; if (args.includeTableName) { out.$table = this.tableName; } if (args.includeOpCode) { out.$op = this.op; } if (args.includeStage) { out.$stage = stage; } if (args.includePrimaryKeyInUpdateNew && this.op === "Updated" && stage === "New") { const pkNames = this.getPrimaryKeyColumnNames(); const pkValues = this.primaryKeyValues; pkNames.forEach((v, i) => { out[v] = pkValues[i]; }); } const isNullOrUndefined = (val) => typeof val === "undefined"; for (let i = 0; i < minLen; ++i) { const columnValue = row[i]; const columnName = cols[i]; if (!args.includeNullColumns && isNullOrUndefined(columnValue)) continue; out[columnName] = columnValue; } return out; } /** * Get list of column for a table. This function also caches the result. * @note To this to work db arg must be set when opening changeset file. * @param tableName name of the table for which columns are requested. * @returns columns of table. * @beta */ getColumnNames(tableName) { const columns = this._schemaCache.get(tableName); if (columns) return columns; return this.db.withPreparedSqliteStatement("SELECT [name] FROM PRAGMA_TABLE_INFO(?) ORDER BY [cid]", (stmt) => { stmt.bindString(1, tableName); const tblCols = []; while (stmt.step() === DbResult.BE_SQLITE_ROW) { tblCols.push(stmt.getValueString(0)); } this._schemaCache.set(tableName, tblCols); return tblCols; }); } /** index of current change * @beta */ get changeIndex() { return this._changeIndex; } /** * Close changeset * @beta */ close() { this._changeIndex = 0; this._nativeReader.close(); } /** * Dispose this object * @beta */ [Symbol.dispose]() { this.close(); } } //# sourceMappingURL=SqliteChangesetReader.js.map