@itwin/core-backend
Version:
iTwin.js backend components
337 lines • 12.6 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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