typeorm
Version:
Data-Mapper ORM for TypeScript and ES2021+. Supports MySQL/MariaDB, PostgreSQL, MS SQL Server, Oracle, SAP HANA, SQLite, MongoDB databases.
451 lines (449 loc) • 18.5 kB
JavaScript
import { Query } from "../driver/Query";
import { SqlInMemory } from "../driver/SqlInMemory";
import { TypeORMError } from "../error/TypeORMError";
import { OrmUtils } from "../util/OrmUtils";
import { InstanceChecker } from "../util/InstanceChecker";
import { buildSqlTag } from "../util/SqlTagUtils";
export class BaseQueryRunner {
constructor() {
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Indicates if connection for this query runner is released.
* Once its released, query runner cannot run queries anymore.
*/
this.isReleased = false;
/**
* Indicates if transaction is in progress.
*/
this.isTransactionActive = false;
/**
* Stores temporarily user data.
* Useful for sharing data with subscribers.
*/
this.data = {};
/**
* All synchronized tables in the database.
*/
this.loadedTables = [];
/**
* All synchronized views in the database.
*/
this.loadedViews = [];
/**
* Indicates if special query runner mode in which sql queries won't be executed is enabled.
*/
this.sqlMemoryMode = false;
/**
* Sql-s stored if "sql in memory" mode is enabled.
*/
this.sqlInMemory = new SqlInMemory();
/**
* current depth of transaction.
* for transactionDepth > 0 will use SAVEPOINT to start and commit/rollback transaction blocks
*/
this.transactionDepth = 0;
this.cachedTablePaths = {};
}
/**
* Tagged template function that executes raw SQL query and returns raw database results.
* Template expressions are automatically transformed into database parameters.
* Raw query execution is supported only by relational databases (MongoDB is not supported).
* Note: Don't call this as a regular function, it is meant to be used with backticks to tag a template literal.
* Example: queryRunner.sql`SELECT * FROM table_name WHERE id = ${id}`
*/
async sql(strings, ...values) {
const { query, parameters } = buildSqlTag({
driver: this.connection.driver,
strings: strings,
expressions: values,
});
return await this.query(query, parameters);
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Called before migrations are run.
*/
async beforeMigration() {
// Do nothing
}
/**
* Called after migrations are run.
*/
async afterMigration() {
// Do nothing
}
/**
* Loads given table's data from the database.
*/
async getTable(tablePath) {
this.loadedTables = await this.loadTables([tablePath]);
return this.loadedTables.length > 0 ? this.loadedTables[0] : undefined;
}
/**
* Loads all tables (with given names) from the database.
*/
async getTables(tableNames) {
if (!tableNames) {
// Don't cache in this case.
// This is the new case & isn't used anywhere else anyway.
return await this.loadTables(tableNames);
}
this.loadedTables = await this.loadTables(tableNames);
return this.loadedTables;
}
/**
* Loads given view's data from the database.
*/
async getView(viewPath) {
this.loadedViews = await this.loadViews([viewPath]);
return this.loadedViews.length > 0 ? this.loadedViews[0] : undefined;
}
/**
* Loads given view's data from the database.
*/
async getViews(viewPaths) {
this.loadedViews = await this.loadViews(viewPaths);
return this.loadedViews;
}
/**
* Enables special query runner mode in which sql queries won't be executed,
* instead they will be memorized into a special variable inside query runner.
* You can get memorized sql using getMemorySql() method.
*/
enableSqlMemory() {
this.sqlInMemory = new SqlInMemory();
this.sqlMemoryMode = true;
}
/**
* Disables special query runner mode in which sql queries won't be executed
* started by calling enableSqlMemory() method.
*
* Previously memorized sql will be flushed.
*/
disableSqlMemory() {
this.sqlInMemory = new SqlInMemory();
this.sqlMemoryMode = false;
}
/**
* Flushes all memorized sqls.
*/
clearSqlMemory() {
this.sqlInMemory = new SqlInMemory();
}
/**
* Gets sql stored in the memory. Parameters in the sql are already replaced.
*/
getMemorySql() {
return this.sqlInMemory;
}
/**
* Executes up sql queries.
*/
async executeMemoryUpSql() {
for (const { query, parameters } of this.sqlInMemory.upQueries) {
await this.query(query, parameters);
}
}
/**
* Executes down sql queries.
*/
async executeMemoryDownSql() {
for (const { query, parameters, } of this.sqlInMemory.downQueries.reverse()) {
await this.query(query, parameters);
}
}
getReplicationMode() {
return this.mode;
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Gets view from previously loaded views, otherwise loads it from database.
*/
async getCachedView(viewName) {
const view = this.loadedViews.find((view) => view.name === viewName);
if (view)
return view;
const foundViews = await this.loadViews([viewName]);
if (foundViews.length > 0) {
this.loadedViews.push(foundViews[0]);
return foundViews[0];
}
else {
throw new TypeORMError(`View "${viewName}" does not exist.`);
}
}
/**
* Gets table from previously loaded tables, otherwise loads it from database.
*/
async getCachedTable(tableName) {
if (tableName in this.cachedTablePaths) {
const tablePath = this.cachedTablePaths[tableName];
const table = this.loadedTables.find((table) => this.getTablePath(table) === tablePath);
if (table) {
return table;
}
}
const foundTables = await this.loadTables([tableName]);
if (foundTables.length > 0) {
const foundTablePath = this.getTablePath(foundTables[0]);
const cachedTable = this.loadedTables.find((table) => this.getTablePath(table) === foundTablePath);
if (!cachedTable) {
this.cachedTablePaths[tableName] = this.getTablePath(foundTables[0]);
this.loadedTables.push(foundTables[0]);
return foundTables[0];
}
else {
return cachedTable;
}
}
else {
throw new TypeORMError(`Table "${tableName}" does not exist.`);
}
}
/**
* Replaces loaded table with given changed table.
*/
replaceCachedTable(table, changedTable) {
const oldTablePath = this.getTablePath(table);
const foundTable = this.loadedTables.find((loadedTable) => this.getTablePath(loadedTable) === oldTablePath);
// Clean up the lookup cache..
for (const [key, cachedPath] of Object.entries(this.cachedTablePaths)) {
if (cachedPath === oldTablePath) {
this.cachedTablePaths[key] = this.getTablePath(changedTable);
}
}
if (foundTable) {
foundTable.database = changedTable.database;
foundTable.schema = changedTable.schema;
foundTable.name = changedTable.name;
foundTable.columns = changedTable.columns;
foundTable.indices = changedTable.indices;
foundTable.foreignKeys = changedTable.foreignKeys;
foundTable.uniques = changedTable.uniques;
foundTable.checks = changedTable.checks;
foundTable.justCreated = changedTable.justCreated;
foundTable.engine = changedTable.engine;
foundTable.comment = changedTable.comment;
}
}
getTablePath(target) {
const parsed = this.connection.driver.parseTableName(target);
return this.connection.driver.buildTableName(parsed.tableName, parsed.schema, parsed.database);
}
getTypeormMetadataTableName() {
const options = this.connection.driver.options;
return this.connection.driver.buildTableName(this.connection.metadataTableName, options.schema, options.database);
}
/**
* Generates SQL query to select record from typeorm metadata table.
*/
selectTypeormMetadataSql({ database, schema, table, type, name, }) {
const qb = this.connection.createQueryBuilder();
const selectQb = qb
.select()
.from(this.getTypeormMetadataTableName(), "t")
.where(`${qb.escape("type")} = :type`, { type })
.andWhere(`${qb.escape("name")} = :name`, { name });
if (database) {
selectQb.andWhere(`${qb.escape("database")} = :database`, {
database,
});
}
if (schema) {
selectQb.andWhere(`${qb.escape("schema")} = :schema`, { schema });
}
if (table) {
selectQb.andWhere(`${qb.escape("table")} = :table`, { table });
}
const [query, parameters] = selectQb.getQueryAndParameters();
return new Query(query, parameters);
}
/**
* Generates SQL query to insert a record into typeorm metadata table.
*/
insertTypeormMetadataSql({ database, schema, table, type, name, value, }) {
const [query, parameters] = this.connection
.createQueryBuilder()
.insert()
.into(this.getTypeormMetadataTableName())
.values({
database: database,
schema: schema,
table: table,
type: type,
name: name,
value: value,
})
.getQueryAndParameters();
return new Query(query, parameters);
}
/**
* Generates SQL query to delete a record from typeorm metadata table.
*/
deleteTypeormMetadataSql({ database, schema, table, type, name, }) {
const qb = this.connection.createQueryBuilder();
const deleteQb = qb
.delete()
.from(this.getTypeormMetadataTableName())
.where(`${qb.escape("type")} = :type`, { type })
.andWhere(`${qb.escape("name")} = :name`, { name });
if (database) {
deleteQb.andWhere(`${qb.escape("database")} = :database`, {
database,
});
}
if (schema) {
deleteQb.andWhere(`${qb.escape("schema")} = :schema`, { schema });
}
if (table) {
deleteQb.andWhere(`${qb.escape("table")} = :table`, { table });
}
const [query, parameters] = deleteQb.getQueryAndParameters();
return new Query(query, parameters);
}
/**
* Checks if at least one of column properties was changed.
* Does not checks column type, length and autoincrement, because these properties changes separately.
*/
isColumnChanged(oldColumn, newColumn, checkDefault, checkComment, checkEnum = true) {
// this logs need to debug issues in column change detection. Do not delete it!
// console.log("charset ---------------");
// console.log(oldColumn.charset !== newColumn.charset);
// console.log(oldColumn.charset, newColumn.charset);
// console.log("collation ---------------");
// console.log(oldColumn.collation !== newColumn.collation);
// console.log(oldColumn.collation, newColumn.collation);
// console.log("precision ---------------");
// console.log(oldColumn.precision !== newColumn.precision);
// console.log(oldColumn.precision, newColumn.precision);
// console.log("scale ---------------");
// console.log(oldColumn.scale !== newColumn.scale);
// console.log(oldColumn.scale, newColumn.scale);
// console.log("default ---------------");
// console.log((checkDefault && oldColumn.default !== newColumn.default));
// console.log(oldColumn.default, newColumn.default);
// console.log("isNullable ---------------");
// console.log(oldColumn.isNullable !== newColumn.isNullable);
// console.log(oldColumn.isNullable, newColumn.isNullable);
// console.log("comment ---------------");
// console.log((checkComment && oldColumn.comment !== newColumn.comment));
// console.log(oldColumn.comment, newColumn.comment);
// console.log("enum ---------------");
// console.log(!OrmUtils.isArraysEqual(oldColumn.enum || [], newColumn.enum || []));
// console.log(oldColumn.enum, newColumn.enum);
return (oldColumn.charset !== newColumn.charset ||
oldColumn.collation !== newColumn.collation ||
oldColumn.precision !== newColumn.precision ||
oldColumn.scale !== newColumn.scale ||
oldColumn.width !== newColumn.width || // MySQL only
oldColumn.zerofill !== newColumn.zerofill || // MySQL only
oldColumn.unsigned !== newColumn.unsigned || // MySQL only
oldColumn.asExpression !== newColumn.asExpression ||
(checkDefault && oldColumn.default !== newColumn.default) ||
oldColumn.onUpdate !== newColumn.onUpdate || // MySQL only
oldColumn.isNullable !== newColumn.isNullable ||
(checkComment && oldColumn.comment !== newColumn.comment) ||
(checkEnum && this.isEnumChanged(oldColumn, newColumn)));
}
isEnumChanged(oldColumn, newColumn) {
return !OrmUtils.isArraysEqual(oldColumn.enum || [], newColumn.enum || []);
}
/**
* Checks if column length is by default.
*/
isDefaultColumnLength(table, column, length) {
// if table have metadata, we check if length is specified in column metadata
if (this.connection.hasMetadata(table.name)) {
const metadata = this.connection.getMetadata(table.name);
const columnMetadata = metadata.findColumnWithDatabaseName(column.name);
if (columnMetadata) {
const columnMetadataLength = this.connection.driver.getColumnLength(columnMetadata);
if (columnMetadataLength)
return false;
}
}
if (this.connection.driver.dataTypeDefaults &&
this.connection.driver.dataTypeDefaults[column.type] &&
this.connection.driver.dataTypeDefaults[column.type].length) {
return (this.connection.driver.dataTypeDefaults[column.type].length.toString() === length.toString());
}
return false;
}
/**
* Checks if column precision is by default.
*/
isDefaultColumnPrecision(table, column, precision) {
// if table have metadata, we check if length is specified in column metadata
if (this.connection.hasMetadata(table.name)) {
const metadata = this.connection.getMetadata(table.name);
const columnMetadata = metadata.findColumnWithDatabaseName(column.name);
if (columnMetadata &&
columnMetadata.precision !== null &&
columnMetadata.precision !== undefined)
return false;
}
if (this.connection.driver.dataTypeDefaults &&
this.connection.driver.dataTypeDefaults[column.type] &&
this.connection.driver.dataTypeDefaults[column.type].precision !==
null &&
this.connection.driver.dataTypeDefaults[column.type].precision !==
undefined)
return (this.connection.driver.dataTypeDefaults[column.type]
.precision === precision);
return false;
}
/**
* Checks if column scale is by default.
*/
isDefaultColumnScale(table, column, scale) {
// if table have metadata, we check if length is specified in column metadata
if (this.connection.hasMetadata(table.name)) {
const metadata = this.connection.getMetadata(table.name);
const columnMetadata = metadata.findColumnWithDatabaseName(column.name);
if (columnMetadata &&
columnMetadata.scale !== null &&
columnMetadata.scale !== undefined)
return false;
}
if (this.connection.driver.dataTypeDefaults &&
this.connection.driver.dataTypeDefaults[column.type] &&
this.connection.driver.dataTypeDefaults[column.type].scale !==
null &&
this.connection.driver.dataTypeDefaults[column.type].scale !==
undefined)
return (this.connection.driver.dataTypeDefaults[column.type].scale ===
scale);
return false;
}
/**
* Executes sql used special for schema build.
*/
async executeQueries(upQueries, downQueries) {
if (InstanceChecker.isQuery(upQueries))
upQueries = [upQueries];
if (InstanceChecker.isQuery(downQueries))
downQueries = [downQueries];
this.sqlInMemory.upQueries.push(...upQueries);
this.sqlInMemory.downQueries.push(...downQueries);
// if sql-in-memory mode is enabled then simply store sql in memory and return
if (this.sqlMemoryMode === true)
return Promise.resolve();
for (const { query, parameters } of upQueries) {
await this.query(query, parameters);
}
}
/**
* Generated an index name for a table and index
*/
generateIndexName(table, index) {
// new index may be passed without name. In this case we generate index name manually.
return this.connection.namingStrategy.indexName(table, index.columnNames, index.where);
}
}
//# sourceMappingURL=BaseQueryRunner.js.map