typeorm
Version:
Data-Mapper ORM for TypeScript and ES2023+. Supports MySQL/MariaDB, PostgreSQL, MS SQL Server, Oracle, SAP HANA, SQLite, MongoDB databases.
2,721 lines • 135 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqlServerQueryRunner = void 0;
const error_1 = require("../../error");
const NamedPlaceholdersNotSupportedError_1 = require("../../error/NamedPlaceholdersNotSupportedError");
const QueryFailedError_1 = require("../../error/QueryFailedError");
const QueryRunnerAlreadyReleasedError_1 = require("../../error/QueryRunnerAlreadyReleasedError");
const TransactionNotStartedError_1 = require("../../error/TransactionNotStartedError");
const BaseQueryRunner_1 = require("../../query-runner/BaseQueryRunner");
const QueryLock_1 = require("../../query-runner/QueryLock");
const QueryResult_1 = require("../../query-runner/QueryResult");
const Table_1 = require("../../schema-builder/table/Table");
const TableCheck_1 = require("../../schema-builder/table/TableCheck");
const TableColumn_1 = require("../../schema-builder/table/TableColumn");
const TableForeignKey_1 = require("../../schema-builder/table/TableForeignKey");
const TableIndex_1 = require("../../schema-builder/table/TableIndex");
const TableUnique_1 = require("../../schema-builder/table/TableUnique");
const View_1 = require("../../schema-builder/view/View");
const Broadcaster_1 = require("../../subscriber/Broadcaster");
const BroadcasterResult_1 = require("../../subscriber/BroadcasterResult");
const InstanceChecker_1 = require("../../util/InstanceChecker");
const OrmUtils_1 = require("../../util/OrmUtils");
const Query_1 = require("../Query");
const validate_isolation_level_1 = require("../validate-isolation-level");
const MetadataTableType_1 = require("../types/MetadataTableType");
/**
* Runs queries on a single SQL Server database connection.
*/
class SqlServerQueryRunner extends BaseQueryRunner_1.BaseQueryRunner {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(driver, mode) {
super();
// -------------------------------------------------------------------------
// Private Properties
// -------------------------------------------------------------------------
this.lock = new QueryLock_1.QueryLock();
this.driver = driver;
this.dataSource = driver.dataSource;
this.broadcaster = new Broadcaster_1.Broadcaster(this);
this.mode = mode;
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Creates/uses database connection from the connection pool to perform further operations.
* Returns obtained database connection.
*/
connect() {
return Promise.resolve();
}
/**
* Releases used database connection.
* You cannot use query runner methods once its released.
*/
release() {
this.isReleased = true;
return Promise.resolve();
}
/**
* Starts transaction.
*
* @param isolationLevel
*/
async startTransaction(isolationLevel) {
isolationLevel ??= this.dataSource.options.isolationLevel;
(0, validate_isolation_level_1.validateIsolationLevel)(this.driver.supportedIsolationLevels, isolationLevel);
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError_1.QueryRunnerAlreadyReleasedError();
this.isTransactionActive = true;
try {
await this.broadcaster.broadcast("BeforeTransactionStart");
}
catch (err) {
this.isTransactionActive = false;
throw err;
}
await new Promise(async (ok, fail) => {
const transactionCallback = (err) => {
if (err) {
this.isTransactionActive = false;
return fail(err);
}
ok();
};
if (this.transactionDepth === 0) {
const pool = await (this.mode === "slave"
? this.driver.obtainSlaveConnection()
: this.driver.obtainMasterConnection());
this.databaseConnection = pool.transaction();
this.dataSource.logger.logQuery("BEGIN TRANSACTION");
if (isolationLevel) {
this.databaseConnection.begin(this.driver.convertIsolationLevel(isolationLevel), transactionCallback);
this.dataSource.logger.logQuery("SET TRANSACTION ISOLATION LEVEL " + isolationLevel);
}
else {
this.databaseConnection.begin(transactionCallback);
}
}
else {
await this.query(`SAVE TRANSACTION typeorm_${this.transactionDepth}`);
ok();
}
this.transactionDepth += 1;
});
await this.broadcaster.broadcast("AfterTransactionStart");
}
/**
* Commits transaction.
* Error will be thrown if transaction was not started.
*/
async commitTransaction() {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError_1.QueryRunnerAlreadyReleasedError();
if (!this.isTransactionActive)
throw new TransactionNotStartedError_1.TransactionNotStartedError();
await this.broadcaster.broadcast("BeforeTransactionCommit");
if (this.transactionDepth === 1) {
return new Promise((ok, fail) => {
this.databaseConnection.commit(async (err) => {
if (err)
return fail(err);
this.isTransactionActive = false;
this.databaseConnection = null;
await this.broadcaster.broadcast("AfterTransactionCommit");
ok();
this.dataSource.logger.logQuery("COMMIT");
this.transactionDepth -= 1;
});
});
}
this.transactionDepth -= 1;
}
/**
* Rollbacks transaction.
* Error will be thrown if transaction was not started.
*/
async rollbackTransaction() {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError_1.QueryRunnerAlreadyReleasedError();
if (!this.isTransactionActive)
throw new TransactionNotStartedError_1.TransactionNotStartedError();
await this.broadcaster.broadcast("BeforeTransactionRollback");
if (this.transactionDepth > 1) {
await this.query(`ROLLBACK TRANSACTION typeorm_${this.transactionDepth - 1}`);
this.transactionDepth -= 1;
}
else {
return new Promise((ok, fail) => {
this.databaseConnection.rollback(async (err) => {
if (err)
return fail(err);
this.isTransactionActive = false;
this.databaseConnection = null;
await this.broadcaster.broadcast("AfterTransactionRollback");
ok();
this.dataSource.logger.logQuery("ROLLBACK");
this.transactionDepth -= 1;
});
});
}
}
/**
* Executes a given SQL query.
*
* @param query
* @param parameters
* @param useStructuredResult
*/
async query(query, parameters, useStructuredResult = false) {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError_1.QueryRunnerAlreadyReleasedError();
if (parameters && !Array.isArray(parameters))
throw new NamedPlaceholdersNotSupportedError_1.NamedPlaceholdersNotSupportedError();
const release = await this.lock.acquire();
this.driver.dataSource.logger.logQuery(query, parameters, this);
await this.broadcaster.broadcast("BeforeQuery", query, parameters);
const broadcasterResult = new BroadcasterResult_1.BroadcasterResult();
const maxQueryExecutionTime = this.driver.options.maxQueryExecutionTime;
let queryStartTime;
try {
const pool = await (this.mode === "slave"
? this.driver.obtainSlaveConnection()
: this.driver.obtainMasterConnection());
const request = new this.driver.mssql.Request(this.isTransactionActive ? this.databaseConnection : pool);
if (parameters?.length) {
parameters.forEach((parameter, index) => {
const parameterName = index.toString();
if (InstanceChecker_1.InstanceChecker.isMssqlParameter(parameter)) {
const mssqlParameter = this.mssqlParameterToNativeParameter(parameter);
if (mssqlParameter) {
request.input(parameterName, mssqlParameter, parameter.value);
}
else {
request.input(parameterName, parameter.value);
}
}
else {
request.input(parameterName, parameter);
}
});
}
queryStartTime = Date.now();
const raw = await request.query(query).catch((err) => {
throw new QueryFailedError_1.QueryFailedError(query, parameters, err);
});
// log slow queries if maxQueryExecution time is set
const queryExecutionTime = Date.now() - queryStartTime;
this.broadcaster.broadcastAfterQueryEvent(broadcasterResult, query, parameters, true, queryExecutionTime, raw, undefined);
if (maxQueryExecutionTime &&
queryExecutionTime > maxQueryExecutionTime) {
this.driver.dataSource.logger.logQuerySlow(queryExecutionTime, query, parameters, this);
}
const result = new QueryResult_1.QueryResult();
if (raw?.hasOwnProperty("recordset")) {
result.records = raw.recordset;
}
if (raw?.hasOwnProperty("rowsAffected")) {
result.affected = raw.rowsAffected[0];
}
const queryType = query.slice(0, query.indexOf(" "));
switch (queryType) {
case "DELETE":
// for DELETE query additionally return number of affected rows
result.raw = [raw.recordset, raw.rowsAffected[0]];
break;
default:
result.raw = raw.recordset;
}
return useStructuredResult ? result : result.raw;
}
catch (err) {
const queryExecutionTime = queryStartTime
? Date.now() - queryStartTime
: undefined;
if (maxQueryExecutionTime &&
queryExecutionTime !== undefined &&
queryExecutionTime > maxQueryExecutionTime) {
this.driver.dataSource.logger.logQuerySlow(queryExecutionTime, query, parameters, this);
}
this.driver.dataSource.logger.logQueryError(err, query, parameters, this);
this.broadcaster.broadcastAfterQueryEvent(broadcasterResult, query, parameters, false, undefined, undefined, err);
throw err;
}
finally {
await broadcasterResult.wait();
release();
}
}
/**
* Returns raw data stream.
*
* @param query
* @param parameters
* @param onEnd
* @param onError
*/
async stream(query, parameters, onEnd, onError) {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError_1.QueryRunnerAlreadyReleasedError();
const release = await this.lock.acquire();
this.driver.dataSource.logger.logQuery(query, parameters, this);
const pool = await (this.mode === "slave"
? this.driver.obtainSlaveConnection()
: this.driver.obtainMasterConnection());
const request = new this.driver.mssql.Request(this.isTransactionActive ? this.databaseConnection : pool);
if (parameters?.length) {
parameters.forEach((parameter, index) => {
const parameterName = index.toString();
if (InstanceChecker_1.InstanceChecker.isMssqlParameter(parameter)) {
request.input(parameterName, this.mssqlParameterToNativeParameter(parameter), parameter.value);
}
else {
request.input(parameterName, parameter);
}
});
}
request.query(query);
const streamRequest = request.toReadableStream();
streamRequest.on("error", (err) => {
release();
this.driver.dataSource.logger.logQueryError(err, query, parameters, this);
});
streamRequest.on("end", () => {
release();
});
if (onEnd) {
streamRequest.on("end", onEnd);
}
if (onError) {
streamRequest.on("error", onError);
}
return streamRequest;
}
/**
* Returns all available database names including system databases.
*/
async getDatabases() {
const results = await this.query(`EXEC sp_databases`);
return results.map((result) => result["DATABASE_NAME"]);
}
/**
* Returns all available schema names including system schemas.
* If database parameter specified, returns schemas of that database.
*
* @param database
*/
async getSchemas(database) {
const query = database
? `SELECT * FROM "${database}"."sys"."schemas"`
: `SELECT * FROM "sys"."schemas"`;
const results = await this.query(query);
return results.map((result) => result["name"]);
}
/**
* Checks if database with the given name exist.
*
* @param database
*/
async hasDatabase(database) {
const result = await this.query(`SELECT DB_ID(@0) as "db_id"`, [
database,
]);
const dbId = result[0]["db_id"];
return !!dbId;
}
/**
* Loads currently using database
*/
async getCurrentDatabase() {
const currentDBQuery = await this.query(`SELECT DB_NAME() AS "db_name"`);
return currentDBQuery[0]["db_name"];
}
/**
* Checks if schema with the given name exist.
*
* @param schema
*/
async hasSchema(schema) {
const result = await this.query(`SELECT SCHEMA_ID(@0) as "schema_id"`, [
schema,
]);
const schemaId = result[0]["schema_id"];
return !!schemaId;
}
/**
* Loads currently using database schema
*/
async getCurrentSchema() {
const currentSchemaQuery = await this.query(`SELECT SCHEMA_NAME() AS "schema_name"`);
return currentSchemaQuery[0]["schema_name"];
}
/**
* Checks if table with the given name exist in the database.
*
* @param tableOrName
*/
async hasTable(tableOrName) {
const parsedTableName = this.driver.parseTableName(tableOrName);
parsedTableName.database ??= await this.getCurrentDatabase();
parsedTableName.schema ??= await this.getCurrentSchema();
const sql = `SELECT * FROM ${this.driver.escape(parsedTableName.database)}."INFORMATION_SCHEMA"."TABLES" WHERE "TABLE_NAME" = @0 AND "TABLE_SCHEMA" = @1`;
const result = await this.query(sql, [
parsedTableName.tableName,
parsedTableName.schema,
]);
return result.length ? true : false;
}
/**
* Checks if column exist in the table.
*
* @param tableOrName
* @param columnName
*/
async hasColumn(tableOrName, columnName) {
const parsedTableName = this.driver.parseTableName(tableOrName);
parsedTableName.database ??= await this.getCurrentDatabase();
parsedTableName.schema ??= await this.getCurrentSchema();
const sql = `SELECT * FROM ${this.driver.escape(parsedTableName.database)}."INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = @0 AND "TABLE_SCHEMA" = @1 AND "COLUMN_NAME" = @2`;
const result = await this.query(sql, [
parsedTableName.tableName,
parsedTableName.schema,
columnName,
]);
return result.length ? true : false;
}
/**
* Creates a new database.
*
* @param database
* @param ifNotExists
*/
async createDatabase(database, ifNotExists) {
const escapedQuote = database.replaceAll("'", "''");
const up = ifNotExists
? `IF DB_ID('${escapedQuote}') IS NULL CREATE DATABASE ${this.driver.escape(database)}`
: `CREATE DATABASE ${this.driver.escape(database)}`;
const down = `DROP DATABASE ${this.driver.escape(database)}`;
await this.executeQueries(new Query_1.Query(up), new Query_1.Query(down));
}
/**
* Drops database.
*
* @param database
* @param ifExists
*/
async dropDatabase(database, ifExists) {
const escapedQuote = database.replaceAll("'", "''");
const up = ifExists
? `IF DB_ID('${escapedQuote}') IS NOT NULL DROP DATABASE ${this.driver.escape(database)}`
: `DROP DATABASE ${this.driver.escape(database)}`;
const down = `CREATE DATABASE ${this.driver.escape(database)}`;
await this.executeQueries(new Query_1.Query(up), new Query_1.Query(down));
}
/**
* Creates table schema.
* If database name also specified (e.g. 'dbName.schemaName') schema will be created in specified database.
*
* @param schemaPath
* @param ifNotExists
*/
async createSchema(schemaPath, ifNotExists) {
const upQueries = [];
const downQueries = [];
if (schemaPath.indexOf(".") === -1) {
const escapedQuote = schemaPath.replaceAll("'", "''");
const escapedExec = this.driver
.escape(schemaPath)
.replaceAll("'", "''");
const upQuery = ifNotExists
? `IF SCHEMA_ID('${escapedQuote}') IS NULL BEGIN EXEC ('CREATE SCHEMA ${escapedExec}') END`
: `CREATE SCHEMA ${this.driver.escape(schemaPath)}`;
upQueries.push(new Query_1.Query(upQuery));
downQueries.push(new Query_1.Query(`DROP SCHEMA ${this.driver.escape(schemaPath)}`));
}
else {
const dbName = schemaPath.split(".")[0];
const schema = schemaPath.split(".")[1];
const escapedSchemaQuote = schema.replaceAll("'", "''");
const escapedSchemaExec = this.driver
.escape(schema)
.replaceAll("'", "''");
const currentDB = await this.getCurrentDatabase();
upQueries.push(new Query_1.Query(`USE ${this.driver.escape(dbName)}`));
downQueries.push(new Query_1.Query(`USE ${this.driver.escape(currentDB)}`));
const upQuery = ifNotExists
? `IF SCHEMA_ID('${escapedSchemaQuote}') IS NULL BEGIN EXEC ('CREATE SCHEMA ${escapedSchemaExec}') END`
: `CREATE SCHEMA ${this.driver.escape(schema)}`;
upQueries.push(new Query_1.Query(upQuery));
downQueries.push(new Query_1.Query(`DROP SCHEMA ${this.driver.escape(schema)}`));
upQueries.push(new Query_1.Query(`USE ${this.driver.escape(currentDB)}`));
downQueries.push(new Query_1.Query(`USE ${this.driver.escape(dbName)}`));
}
await this.executeQueries(upQueries, downQueries);
}
/**
* Drops table schema.
* If database name also specified (e.g. 'dbName.schemaName') schema will be dropped in specified database.
*
* @param schemaPath
* @param ifExists
*/
async dropSchema(schemaPath, ifExists) {
const upQueries = [];
const downQueries = [];
if (schemaPath.indexOf(".") === -1) {
const escapedQuote = schemaPath.replaceAll("'", "''");
const escapedExec = this.driver
.escape(schemaPath)
.replaceAll("'", "''");
const upQuery = ifExists
? `IF SCHEMA_ID('${escapedQuote}') IS NOT NULL BEGIN EXEC ('DROP SCHEMA ${escapedExec}') END`
: `DROP SCHEMA ${this.driver.escape(schemaPath)}`;
upQueries.push(new Query_1.Query(upQuery));
downQueries.push(new Query_1.Query(`CREATE SCHEMA ${this.driver.escape(schemaPath)}`));
}
else {
const dbName = schemaPath.split(".")[0];
const schema = schemaPath.split(".")[1];
const escapedSchemaQuote = schema.replaceAll("'", "''");
const escapedSchemaExec = this.driver
.escape(schema)
.replaceAll("'", "''");
const currentDB = await this.getCurrentDatabase();
upQueries.push(new Query_1.Query(`USE ${this.driver.escape(dbName)}`));
downQueries.push(new Query_1.Query(`USE ${this.driver.escape(currentDB)}`));
const upQuery = ifExists
? `IF SCHEMA_ID('${escapedSchemaQuote}') IS NOT NULL BEGIN EXEC ('DROP SCHEMA ${escapedSchemaExec}') END`
: `DROP SCHEMA ${this.driver.escape(schema)}`;
upQueries.push(new Query_1.Query(upQuery));
downQueries.push(new Query_1.Query(`CREATE SCHEMA ${this.driver.escape(schema)}`));
upQueries.push(new Query_1.Query(`USE ${this.driver.escape(currentDB)}`));
downQueries.push(new Query_1.Query(`USE ${this.driver.escape(dbName)}`));
}
await this.executeQueries(upQueries, downQueries);
}
/**
* Creates a new table.
*
* @param table
* @param ifNotExists
* @param createForeignKeys
* @param createIndices
*/
async createTable(table, ifNotExists = false, createForeignKeys = true, createIndices = true) {
if (ifNotExists) {
const isTableExist = await this.hasTable(table);
if (isTableExist)
return Promise.resolve();
}
const upQueries = [];
const downQueries = [];
upQueries.push(this.createTableSql(table, createForeignKeys));
downQueries.push(this.dropTableSql(table));
// if createForeignKeys is true, we must drop created foreign keys in down query.
// createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
if (createForeignKeys)
table.foreignKeys.forEach((foreignKey) => downQueries.push(this.dropForeignKeySql(table, foreignKey)));
if (createIndices) {
table.indices.forEach((index) => {
// new index may be passed without name. In this case we generate index name manually.
index.name ??= this.dataSource.namingStrategy.indexName(table, index.columnNames, index.where);
upQueries.push(this.createIndexSql(table, index));
downQueries.push(this.dropIndexSql(table, index));
});
}
// if table have column with generated type, we must add the expression to the metadata table
const generatedColumns = table.columns.filter((column) => column.generatedType && column.asExpression);
if (generatedColumns.length > 0) {
const parsedTableName = this.driver.parseTableName(table);
parsedTableName.schema ??= await this.getCurrentSchema();
for (const column of generatedColumns) {
const insertQuery = this.insertTypeormMetadataSql({
database: parsedTableName.database,
schema: parsedTableName.schema,
table: parsedTableName.tableName,
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: column.name,
value: column.asExpression,
});
const deleteQuery = this.deleteTypeormMetadataSql({
database: parsedTableName.database,
schema: parsedTableName.schema,
table: parsedTableName.tableName,
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: column.name,
});
upQueries.push(insertQuery);
downQueries.push(deleteQuery);
}
}
await this.executeQueries(upQueries, downQueries);
}
/**
* Drops the table.
*
* @param tableOrName
* @param ifExists
* @param dropForeignKeys
* @param dropIndices
*/
async dropTable(tableOrName, ifExists, dropForeignKeys = true, dropIndices = true) {
if (ifExists) {
const isTableExist = await this.hasTable(tableOrName);
if (!isTableExist)
return Promise.resolve();
}
// if dropTable called with dropForeignKeys = true, we must create foreign keys in down query.
const createForeignKeys = dropForeignKeys;
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const upQueries = [];
const downQueries = [];
// It needs because if table does not exist and dropForeignKeys or dropIndices is true, we don't need
// to perform drop queries for foreign keys and indices.
if (dropIndices) {
table.indices.forEach((index) => {
upQueries.push(this.dropIndexSql(table, index));
downQueries.push(this.createIndexSql(table, index));
});
}
// if dropForeignKeys is true, we just drop the table, otherwise we also drop table foreign keys.
// createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
if (dropForeignKeys)
table.foreignKeys.forEach((foreignKey) => upQueries.push(this.dropForeignKeySql(table, foreignKey)));
upQueries.push(this.dropTableSql(table));
downQueries.push(this.createTableSql(table, createForeignKeys));
// if table had columns with generated type, we must remove the expression from the metadata table
const generatedColumns = table.columns.filter((column) => column.generatedType && column.asExpression);
if (generatedColumns.length > 0) {
const parsedTableName = this.driver.parseTableName(table);
parsedTableName.schema ??= await this.getCurrentSchema();
for (const column of generatedColumns) {
const deleteQuery = this.deleteTypeormMetadataSql({
database: parsedTableName.database,
schema: parsedTableName.schema,
table: parsedTableName.tableName,
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: column.name,
});
const insertQuery = this.insertTypeormMetadataSql({
database: parsedTableName.database,
schema: parsedTableName.schema,
table: parsedTableName.tableName,
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: column.name,
value: column.asExpression,
});
upQueries.push(deleteQuery);
downQueries.push(insertQuery);
}
}
await this.executeQueries(upQueries, downQueries);
}
/**
* Creates a new view.
*
* @param view
* @param syncWithMetadata
*/
async createView(view, syncWithMetadata = false) {
const upQueries = [];
const downQueries = [];
upQueries.push(this.createViewSql(view));
if (syncWithMetadata)
upQueries.push(await this.insertViewDefinitionSql(view));
downQueries.push(this.dropViewSql(view));
if (syncWithMetadata)
downQueries.push(await this.deleteViewDefinitionSql(view));
await this.executeQueries(upQueries, downQueries);
}
/**
* Drops the view.
*
* @param target
* @param ifExists
*/
async dropView(target, ifExists) {
const viewName = InstanceChecker_1.InstanceChecker.isView(target) ? target.name : target;
let view;
try {
view = await this.getCachedView(viewName);
}
catch {
if (ifExists)
return;
throw new error_1.TypeORMError(`View "${viewName}" does not exist.`);
}
await this.executeQueries([
await this.deleteViewDefinitionSql(view),
this.dropViewSql(view, ifExists),
], [
await this.insertViewDefinitionSql(view),
this.createViewSql(view),
]);
}
/**
* Renames a table.
*
* @param oldTableOrName
* @param newTableName
*/
async renameTable(oldTableOrName, newTableName) {
const upQueries = [];
const downQueries = [];
const oldTable = InstanceChecker_1.InstanceChecker.isTable(oldTableOrName)
? oldTableOrName
: await this.getCachedTable(oldTableOrName);
const newTable = oldTable.clone();
// we need database name and schema name to rename FK constraints
let dbName = undefined;
let schemaName = undefined;
let oldTableName = oldTable.name;
const splittedName = oldTable.name.split(".");
if (splittedName.length === 3) {
dbName = splittedName[0];
oldTableName = splittedName[2];
if (splittedName[1] !== "")
schemaName = splittedName[1];
}
else if (splittedName.length === 2) {
schemaName = splittedName[0];
oldTableName = splittedName[1];
}
newTable.name = this.driver.buildTableName(newTableName, schemaName, dbName);
// if we have tables with database which differs from database specified in config, we must change currently used database.
// This need because we can not rename objects from another database.
const currentDB = await this.getCurrentDatabase();
if (dbName && dbName !== currentDB) {
upQueries.push(new Query_1.Query(`USE "${dbName}"`));
downQueries.push(new Query_1.Query(`USE "${currentDB}"`));
}
// rename table
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(oldTable)}", "${newTableName}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(newTable)}", "${oldTableName}"`));
// rename primary key constraint
if (newTable.primaryColumns.length > 0 &&
!newTable.primaryColumns[0].primaryKeyConstraintName) {
const columnNames = newTable.primaryColumns.map((column) => column.name);
const oldPkName = this.dataSource.namingStrategy.primaryKeyName(oldTable, columnNames);
const newPkName = this.dataSource.namingStrategy.primaryKeyName(newTable, columnNames);
// rename primary constraint
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(newTable)}.${oldPkName}", "${newPkName}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(newTable)}.${newPkName}", "${oldPkName}"`));
}
// rename unique constraints
newTable.uniques.forEach((unique) => {
const oldUniqueName = this.dataSource.namingStrategy.uniqueConstraintName(oldTable, unique.columnNames);
// Skip renaming if Unique has user defined constraint name
if (unique.name !== oldUniqueName)
return;
// build new constraint name
const newUniqueName = this.dataSource.namingStrategy.uniqueConstraintName(newTable, unique.columnNames);
// build queries
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(newTable)}.${unique.name}", "${newUniqueName}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(newTable)}.${newUniqueName}", "${unique.name}"`));
// replace constraint name
unique.name = newUniqueName;
});
// rename index constraints
newTable.indices.forEach((index) => {
const oldIndexName = this.dataSource.namingStrategy.indexName(oldTable, index.columnNames, index.where);
// Skip renaming if Index has user defined constraint name
if (index.name !== oldIndexName)
return;
// build new constraint name
const newIndexName = this.dataSource.namingStrategy.indexName(newTable, index.columnNames, index.where);
// build queries
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(newTable)}.${index.name}", "${newIndexName}", "INDEX"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(newTable)}.${newIndexName}", "${index.name}", "INDEX"`));
// replace constraint name
index.name = newIndexName;
});
// rename foreign key constraints
newTable.foreignKeys.forEach((foreignKey) => {
const oldForeignKeyName = this.dataSource.namingStrategy.foreignKeyName(oldTable, foreignKey.columnNames, this.getTablePath(foreignKey), foreignKey.referencedColumnNames);
// Skip renaming if foreign key has user defined constraint name
if (foreignKey.name !== oldForeignKeyName)
return;
// build new constraint name
const newForeignKeyName = this.dataSource.namingStrategy.foreignKeyName(newTable, foreignKey.columnNames, this.getTablePath(foreignKey), foreignKey.referencedColumnNames);
// build queries
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.buildForeignKeyName(foreignKey.name, schemaName, dbName)}", "${newForeignKeyName}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.buildForeignKeyName(newForeignKeyName, schemaName, dbName)}", "${foreignKey.name}"`));
// replace constraint name
foreignKey.name = newForeignKeyName;
});
// change currently used database back to default db.
if (dbName && dbName !== currentDB) {
upQueries.push(new Query_1.Query(`USE "${currentDB}"`));
downQueries.push(new Query_1.Query(`USE "${dbName}"`));
}
await this.executeQueries(upQueries, downQueries);
// rename old table and replace it in cached tabled;
oldTable.name = newTable.name;
this.replaceCachedTable(oldTable, newTable);
}
/**
* Creates a new column from the column in the table.
*
* @param tableOrName
* @param column
*/
async addColumn(tableOrName, column) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const clonedTable = table.clone();
const upQueries = [];
const downQueries = [];
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD ${this.buildCreateColumnSql(table, column, false, true)}`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${column.name}"`));
// create or update primary key constraint
if (column.isPrimary) {
const primaryColumns = clonedTable.primaryColumns;
// if table already have primary key, me must drop it and recreate again
if (primaryColumns.length > 0) {
const pkName = primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, primaryColumns.map((column) => column.name));
const columnNames = primaryColumns
.map((column) => `"${column.name}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`));
}
primaryColumns.push(column);
const pkName = primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, primaryColumns.map((column) => column.name));
const columnNames = primaryColumns
.map((column) => `"${column.name}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`));
}
// create column index
const columnIndex = clonedTable.indices.find((index) => index.columnNames.length === 1 &&
index.columnNames[0] === column.name);
if (columnIndex) {
upQueries.push(this.createIndexSql(table, columnIndex));
downQueries.push(this.dropIndexSql(table, columnIndex));
}
// create unique constraint
if (column.isUnique) {
const uniqueConstraint = new TableUnique_1.TableUnique({
name: this.dataSource.namingStrategy.uniqueConstraintName(table, [column.name]),
columnNames: [column.name],
});
clonedTable.uniques.push(uniqueConstraint);
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${uniqueConstraint.name}" UNIQUE ("${column.name}")`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${uniqueConstraint.name}"`));
}
// remove default constraint
if (column.default !== null && column.default !== undefined) {
const defaultName = this.dataSource.namingStrategy.defaultConstraintName(table, column.name);
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${defaultName}"`));
}
if (column.generatedType && column.asExpression) {
const parsedTableName = this.driver.parseTableName(table);
parsedTableName.schema ??= await this.getCurrentSchema();
const insertQuery = this.insertTypeormMetadataSql({
database: parsedTableName.database,
schema: parsedTableName.schema,
table: parsedTableName.tableName,
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: column.name,
value: column.asExpression,
});
const deleteQuery = this.deleteTypeormMetadataSql({
database: parsedTableName.database,
schema: parsedTableName.schema,
table: parsedTableName.tableName,
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: column.name,
});
upQueries.push(insertQuery);
downQueries.push(deleteQuery);
}
await this.executeQueries(upQueries, downQueries);
clonedTable.addColumn(column);
this.replaceCachedTable(table, clonedTable);
}
/**
* Creates a new columns from the column in the table.
*
* @param tableOrName
* @param columns
*/
async addColumns(tableOrName, columns) {
for (const column of columns) {
await this.addColumn(tableOrName, column);
}
}
/**
* Renames column in the given table.
*
* @param tableOrName
* @param oldTableColumnOrName
* @param newTableColumnOrName
*/
async renameColumn(tableOrName, oldTableColumnOrName, newTableColumnOrName) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const oldColumn = InstanceChecker_1.InstanceChecker.isTableColumn(oldTableColumnOrName)
? oldTableColumnOrName
: table.columns.find((c) => c.name === oldTableColumnOrName);
if (!oldColumn)
throw new error_1.TypeORMError(`Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`);
let newColumn;
if (InstanceChecker_1.InstanceChecker.isTableColumn(newTableColumnOrName)) {
newColumn = newTableColumnOrName;
}
else {
newColumn = oldColumn.clone();
newColumn.name = newTableColumnOrName;
}
await this.changeColumn(table, oldColumn, newColumn);
}
/**
* Changes a column in the table.
*
* @param tableOrName
* @param oldTableColumnOrName
* @param newColumn
*/
async changeColumn(tableOrName, oldTableColumnOrName, newColumn) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
let clonedTable = table.clone();
const upQueries = [];
const downQueries = [];
const oldColumn = InstanceChecker_1.InstanceChecker.isTableColumn(oldTableColumnOrName)
? oldTableColumnOrName
: table.columns.find((column) => column.name === oldTableColumnOrName);
if (!oldColumn)
throw new error_1.TypeORMError(`Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`);
if ((newColumn.isGenerated !== oldColumn.isGenerated &&
newColumn.generationStrategy !== "uuid") ||
newColumn.type !== oldColumn.type ||
newColumn.length !== oldColumn.length ||
newColumn.asExpression !== oldColumn.asExpression ||
newColumn.generatedType !== oldColumn.generatedType) {
// SQL Server does not support changing of IDENTITY column, so we must drop column and recreate it again.
// Also, we recreate column if column type changed
await this.dropColumn(table, oldColumn);
await this.addColumn(table, newColumn);
// update cloned table
clonedTable = table.clone();
}
else {
if (newColumn.name !== oldColumn.name) {
// we need database name and schema name to rename FK constraints
let dbName = undefined;
let schemaName = undefined;
const splittedName = table.name.split(".");
if (splittedName.length === 3) {
dbName = splittedName[0];
if (splittedName[1] !== "")
schemaName = splittedName[1];
}
else if (splittedName.length === 2) {
schemaName = splittedName[0];
}
// if we have tables with database which differs from database specified in config, we must change currently used database.
// This need because we can not rename objects from another database.
const currentDB = await this.getCurrentDatabase();
if (dbName && dbName !== currentDB) {
upQueries.push(new Query_1.Query(`USE "${dbName}"`));
downQueries.push(new Query_1.Query(`USE "${currentDB}"`));
}
// rename the column
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(table)}.${oldColumn.name}", "${newColumn.name}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(table)}.${newColumn.name}", "${oldColumn.name}"`));
// rename column primary key constraint
if (oldColumn.isPrimary === true &&
!oldColumn.primaryKeyConstraintName) {
const primaryColumns = clonedTable.primaryColumns;
// build old primary constraint name
const columnNames = primaryColumns.map((column) => column.name);
const oldPkName = this.dataSource.namingStrategy.primaryKeyName(clonedTable, columnNames);
// replace old column name with new column name
columnNames.splice(columnNames.indexOf(oldColumn.name), 1);
columnNames.push(newColumn.name);
// build new primary constraint name
const newPkName = this.dataSource.namingStrategy.primaryKeyName(clonedTable, columnNames);
// rename primary constraint
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(clonedTable)}.${oldPkName}", "${newPkName}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(clonedTable)}.${newPkName}", "${oldPkName}"`));
}
// rename index constraints
clonedTable.findColumnIndices(oldColumn).forEach((index) => {
const oldIndexName = this.dataSource.namingStrategy.indexName(clonedTable, index.columnNames, index.where);
// Skip renaming if Index has user defined constraint name
if (index.name !== oldIndexName)
return;
// build new constraint name
index.columnNames.splice(index.columnNames.indexOf(oldColumn.name), 1);
index.columnNames.push(newColumn.name);
const newIndexName = this.dataSource.namingStrategy.indexName(clonedTable, index.columnNames, index.where);
// build queries
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(clonedTable)}.${index.name}", "${newIndexName}", "INDEX"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(clonedTable)}.${newIndexName}", "${index.name}", "INDEX"`));
// replace constraint name
index.name = newIndexName;
});
// rename foreign key constraints
clonedTable
.findColumnForeignKeys(oldColumn)
.forEach((foreignKey) => {
const foreignKeyName = this.dataSource.namingStrategy.foreignKeyName(clonedTable, foreignKey.columnNames, this.getTablePath(foreignKey), foreignKey.referencedColumnNames);
// Skip renaming if foreign key has user defined constraint name
if (foreignKey.name !== foreignKeyName)
return;
// build new constraint name
foreignKey.columnNames.splice(foreignKey.columnNames.indexOf(oldColumn.name), 1);
foreignKey.columnNames.push(newColumn.name);
const newForeignKeyName = this.dataSource.namingStrategy.foreignKeyName(clonedTable, foreignKey.columnNames, this.getTablePath(foreignKey), foreignKey.referencedColumnNames);
// build queries
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.buildForeignKeyName(foreignKey.name, schemaName, dbName)}", "${newForeignKeyName}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.buildForeignKeyName(newForeignKeyName, schemaName, dbName)}", "${foreignKey.name}"`));
// replace constraint name
foreignKey.name = newForeignKeyName;
});
// rename check constraints
clonedTable.findColumnChecks(oldColumn).forEach((check) => {
// build new constraint name
check.columnNames.splice(check.columnNames.indexOf(oldColumn.name), 1);
check.columnNames.push(newColumn.name);
const newCheckName = this.dataSource.namingStrategy.checkConstraintName(clonedTable, check.expression);
// build queries
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(clonedTable)}.${check.name}", "${newCheckName}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(clonedTable)}.${newCheckName}", "${check.name}"`));
// replace constraint name
check.name = newCheckName;
});
// rename unique constraints
clonedTable.findColumnUniques(oldColumn).forEach((unique) => {
const oldUniqueName = this.dataSource.namingStrategy.uniqueConstraintName(clonedTable, unique.columnNames);
// Skip renaming if Unique has user defined constraint name
if (unique.name !== oldUniqueName)
return;
// build new constraint name
unique.columnNames.splice(unique.columnNames.indexOf(oldColumn.name), 1);
unique.columnNames.push(newColumn.name);
const newUniqueName = this.dataSource.namingStrategy.uniqueConstraintName(clonedTable, unique.columnNames);
// build queries
upQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(clonedTable)}.${unique.name}", "${newUniqueName}"`));
downQueries.push(new Query_1.Query(`EXEC sp_rename "${this.getTablePath(clonedTable)}.${newUniqueName}", "${unique.name}"`));
// replace constraint name
unique.name = newUniqueName;
});
// rename default constraints
if (oldColumn.default !== null &&
oldColumn.default !== undefined) {
const oldDefaultName = this.dataSource.namingStrategy.defaultConstraintName(table, oldColumn.name);
const newDefaultName = this.dataSource.namingStrategy.defaultConstraintName(table, newColumn.name);
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${oldDefaultName}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${oldDefaultName}" DEFAULT ${oldColumn.default} FOR "${newColumn.name}"`));
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${newDefaultName}" DEFAULT ${oldColumn.default} FOR "${newColumn.name}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${newDefaultName}"`));
}
// change currently used database back to default db.
if (dbName && dbName !== currentDB) {
upQueries.push(new Query_1.Query(`USE "${currentDB}"`));
downQueries.push(new Query_1.Query(`USE "${dbName}"`));
}
// rename old column in the Table object
const oldTableColumn = clonedTable.columns.find((column) => column.name === oldColumn.name);
clonedTable.columns[clonedTable.columns.indexOf(oldTableColumn)].name = newColumn.name;
oldColumn.name = newColumn.name;
}
if (this.isColumnChanged(oldColumn, newColumn, false, false, false)) {
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN ${this.buildCreateColumnSql(table, newColumn, true, false, true)}`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN ${this.buildCreateColumnSql(table, oldColumn, true, false, true)}`));
}
if (this.isEnumChanged(oldColumn, newColumn)) {
const oldExpression = this.getEnumExpression(oldColumn);
const oldCheck = new TableCheck_1.TableCheck({
name: this.dataSource.namingStrategy.checkConstraintName(table, oldExpression, true),
expression: oldExpression,
});
const newExpression = this.getEnumExpression(newColumn);
const newCheck = new TableCheck_1.TableCheck({
name: this.dataSource.namingStrategy.checkConstraintName(table, newExpression, true),
expression: newExpression,
});
upQueries.push(this.dropCheckConstraintSql(table, oldCheck));
upQueries.push(this.createCheckConstraintSql(table, newCheck));
downQueries.push(this.dropCheckConstraintSql(table, newCheck));
downQueries.push(this.createCheckConstraintSql(table, oldCheck));
}
if (newColumn.isPrimary !== oldColumn.isPrimary) {
const primaryColumns = clonedTable.primaryColumns;
// if primary column state changed, we must always drop existed constraint.
if (primaryColumns.length > 0) {
const pkName = primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, primaryColumns.map((column) => column.name));
const columnNames = primaryColumns
.map((column) => `"${column.name}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`));
}
if (newColumn.isPrimary === true) {
primaryColumns.push(newColumn);
// update column in table
const column = clonedTable.columns.find((column) => column.name === newColumn.name);
column.isPrimary = true;
const pkName = primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, primaryColumns.map((column) => column.name));
const columnNames = primaryColumns
.map((column) => `"${column.name}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`));
}
else {
const primaryColumn = primaryColumns.find((c) => c.name === newColumn.name);
primaryColumns.splice(primaryColumns.indexOf(primaryColumn), 1);
// update column in table
const column = clonedTable.columns.find((column) => column.name === newColumn.name);
column.isPrimary = false;
// if we have another primary keys, we must recreate constraint.
if (primaryColumns.length > 0) {
const pkName = primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, primaryColumns.map((column) => column.name));
const columnNames = primaryColumns
.map((column) => `"${column.name}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`));
}
}
}
if (newColumn.isUnique !== oldColumn.isUnique) {
if (newColumn.isUnique === true) {
const uniqueConstraint = new TableUnique_1.TableUnique({
name: this.dataSource.namingStrategy.uniqueConstraintName(table, [newColumn.name]),
columnNames: [newColumn.name],
});
clonedTable.uniques.push(uniqueConstraint);
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${uniqueConstraint.name}" UNIQUE ("${newColumn.name}")`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${uniqueConstraint.name}"`));
}
else {
const uniqueConstraint = clonedTable.uniques.find((unique) => {
return (unique.columnNames.length === 1 &&
!!unique.columnNames.find((columnName) => columnName === newColumn.name));
});
clonedTable.uniques.splice(clonedTable.uniques.indexOf(uniqueConstraint), 1);
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${uniqueConstraint.name}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${uniqueConstraint.name}" UNIQUE ("${newColumn.name}")`));
}
}
if (newColumn.default !== oldColumn.default) {
// (note) if there is a previous default, we need to drop its constraint first
if (oldColumn.default !== null &&
oldColumn.default !== undefined) {
const defaultName = this.dataSource.namingStrategy.defaultConstraintName(table, oldColumn.name);
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${defaultName}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${defaultName}" DEFAULT ${oldColumn.default} FOR "${oldColumn.name}"`));
}
if (newColumn.default !== null &&
newColumn.default !== undefined) {
const defaultName = this.dataSource.namingStrategy.defaultConstraintName(table, newColumn.name);
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${defaultName}" DEFAULT ${newColumn.default} FOR "${newColumn.name}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${defaultName}"`));
}
}
await this.executeQueries(upQueries, downQueries);
this.replaceCachedTable(table, clonedTable);
}
}
/**
* Changes a column in the table.
*
* @param tableOrName
* @param changedColumns
*/
async changeColumns(tableOrName, changedColumns) {
for (const { oldColumn, newColumn } of changedColumns) {
await this.changeColumn(tableOrName, oldColumn, newColumn);
}
}
/**
* Drops column in the table.
*
* @param tableOrName
* @param columnOrName
* @param ifExists
*/
async dropColumn(tableOrName, columnOrName, ifExists) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const column = InstanceChecker_1.InstanceChecker.isTableColumn(columnOrName)
? columnOrName
: table.findColumnByName(columnOrName);
if (!column) {
if (ifExists)
return;
throw new error_1.TypeORMError(`Column "${columnOrName}" was not found in table "${table.name}"`);
}
const clonedTable = table.clone();
const upQueries = [];
const downQueries = [];
// drop primary key constraint
if (column.isPrimary) {
const pkName = column.primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, clonedTable.primaryColumns.map((column) => column.name));
const columnNames = clonedTable.primaryColumns
.map((primaryColumn) => `"${primaryColumn.name}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(clonedTable)} DROP CONSTRAINT "${pkName}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(clonedTable)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`));
// update column in table
const tableColumn = clonedTable.findColumnByName(column.name);
tableColumn.isPrimary = false;
// if primary key have multiple columns, we must recreate it without dropped column
if (clonedTable.primaryColumns.length > 0) {
const pkName = clonedTable.primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, clonedTable.primaryColumns.map((column) => column.name));
const columnNames = clonedTable.primaryColumns
.map((primaryColumn) => `"${primaryColumn.name}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(clonedTable)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(clonedTable)} DROP CONSTRAINT "${pkName}"`));
}
}
// drop column index
const columnIndex = clonedTable.indices.find((index) => index.columnNames.length === 1 &&
index.columnNames[0] === column.name);
if (columnIndex) {
clonedTable.indices.splice(clonedTable.indices.indexOf(columnIndex), 1);
upQueries.push(this.dropIndexSql(table, columnIndex));
downQueries.push(this.createIndexSql(table, columnIndex));
}
// drop column check
const columnCheck = clonedTable.checks.find((check) => !!check.columnNames &&
check.columnNames.length === 1 &&
check.columnNames[0] === column.name);
if (columnCheck) {
clonedTable.checks.splice(clonedTable.checks.indexOf(columnCheck), 1);
upQueries.push(this.dropCheckConstraintSql(table, columnCheck));
downQueries.push(this.createCheckConstraintSql(table, columnCheck));
}
// drop column unique
const columnUnique = clonedTable.uniques.find((unique) => unique.columnNames.length === 1 &&
unique.columnNames[0] === column.name);
if (columnUnique) {
clonedTable.uniques.splice(clonedTable.uniques.indexOf(columnUnique), 1);
upQueries.push(this.dropUniqueConstraintSql(table, columnUnique));
downQueries.push(this.createUniqueConstraintSql(table, columnUnique));
}
// drop default constraint
if (column.default !== null && column.default !== undefined) {
const defaultName = this.dataSource.namingStrategy.defaultConstraintName(table, column.name);
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${defaultName}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${defaultName}" DEFAULT ${column.default} FOR "${column.name}"`));
}
if (column.generatedType && column.asExpression) {
const parsedTableName = this.driver.parseTableName(table);
parsedTableName.schema ??= await this.getCurrentSchema();
const deleteQuery = this.deleteTypeormMetadataSql({
database: parsedTableName.database,
schema: parsedTableName.schema,
table: parsedTableName.tableName,
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: column.name,
});
const insertQuery = this.insertTypeormMetadataSql({
database: parsedTableName.database,
schema: parsedTableName.schema,
table: parsedTableName.tableName,
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: column.name,
value: column.asExpression,
});
upQueries.push(deleteQuery);
downQueries.push(insertQuery);
}
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${column.name}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD ${this.buildCreateColumnSql(table, column, false, false)}`));
await this.executeQueries(upQueries, downQueries);
clonedTable.removeColumn(column);
this.replaceCachedTable(table, clonedTable);
}
/**
* Drops the columns in the table.
*
* @param tableOrName
* @param columns
* @param ifExists
*/
async dropColumns(tableOrName, columns, ifExists) {
for (const column of [...columns]) {
await this.dropColumn(tableOrName, column, ifExists);
}
}
/**
* Creates a new primary key.
*
* @param tableOrName
* @param columnNames
* @param constraintName
*/
async createPrimaryKey(tableOrName, columnNames, constraintName) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const clonedTable = table.clone();
const up = this.createPrimaryKeySql(table, columnNames, constraintName);
// mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names.
clonedTable.columns.forEach((column) => {
if (columnNames.find((columnName) => columnName === column.name))
column.isPrimary = true;
});
const down = this.dropPrimaryKeySql(clonedTable);
await this.executeQueries(up, down);
this.replaceCachedTable(table, clonedTable);
}
/**
* Updates composite primary keys.
*
* @param tableOrName
* @param columns
*/
async updatePrimaryKeys(tableOrName, columns) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const clonedTable = table.clone();
const columnNames = columns.map((column) => column.name);
const upQueries = [];
const downQueries = [];
// if table already have primary columns, we must drop them.
const primaryColumns = clonedTable.primaryColumns;
if (primaryColumns.length > 0) {
const pkName = primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, primaryColumns.map((column) => column.name));
const columnNamesString = primaryColumns
.map((column) => `"${column.name}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`));
}
// update columns in table.
clonedTable.columns
.filter((column) => columnNames.indexOf(column.name) !== -1)
.forEach((column) => {
column.isPrimary = true;
});
const pkName = primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(clonedTable, columnNames);
const columnNamesString = columnNames
.map((columnName) => `"${columnName}"`)
.join(", ");
upQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`));
downQueries.push(new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`));
await this.executeQueries(upQueries, downQueries);
this.replaceCachedTable(table, clonedTable);
}
/**
* Drops a primary key.
*
* @param tableOrName
* @param constraintName
* @param ifExists
*/
async dropPrimaryKey(tableOrName, constraintName, ifExists) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
if (ifExists && table.primaryColumns.length === 0)
return;
const up = this.dropPrimaryKeySql(table);
const down = this.createPrimaryKeySql(table, table.primaryColumns.map((column) => column.name), constraintName);
await this.executeQueries(up, down);
table.primaryColumns.forEach((column) => {
column.isPrimary = false;
});
}
/**
* Creates a new unique constraint.
*
* @param tableOrName
* @param uniqueConstraint
*/
async createUniqueConstraint(tableOrName, uniqueConstraint) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
// new unique constraint may be passed without name. In this case we generate unique name manually.
uniqueConstraint.name ??=
this.dataSource.namingStrategy.uniqueConstraintName(table, uniqueConstraint.columnNames);
const up = this.createUniqueConstraintSql(table, uniqueConstraint);
const down = this.dropUniqueConstraintSql(table, uniqueConstraint);
await this.executeQueries(up, down);
table.addUniqueConstraint(uniqueConstraint);
}
/**
* Creates a new unique constraints.
*
* @param tableOrName
* @param uniqueConstraints
*/
async createUniqueConstraints(tableOrName, uniqueConstraints) {
const promises = uniqueConstraints.map((uniqueConstraint) => this.createUniqueConstraint(tableOrName, uniqueConstraint));
await Promise.all(promises);
}
/**
* Drops unique constraint.
*
* @param tableOrName
* @param uniqueOrName
* @param ifExists
*/
async dropUniqueConstraint(tableOrName, uniqueOrName, ifExists) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const uniqueConstraint = InstanceChecker_1.InstanceChecker.isTableUnique(uniqueOrName)
? uniqueOrName
: table.uniques.find((u) => u.name === uniqueOrName);
if (!uniqueConstraint) {
if (ifExists)
return;
throw new error_1.TypeORMError(`Supplied unique constraint was not found in table ${table.name}`);
}
const up = this.dropUniqueConstraintSql(table, uniqueConstraint);
const down = this.createUniqueConstraintSql(table, uniqueConstraint);
await this.executeQueries(up, down);
table.removeUniqueConstraint(uniqueConstraint);
}
/**
* Drops unique constraints.
*
* @param tableOrName
* @param uniqueConstraints
* @param ifExists
*/
async dropUniqueConstraints(tableOrName, uniqueConstraints, ifExists) {
const promises = uniqueConstraints.map((uniqueConstraint) => this.dropUniqueConstraint(tableOrName, uniqueConstraint, ifExists));
await Promise.all(promises);
}
/**
* Creates a new check constraint.
*
* @param tableOrName
* @param checkConstraint
*/
async createCheckConstraint(tableOrName, checkConstraint) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
// new unique constraint may be passed without name. In this case we generate unique name manually.
checkConstraint.name ??=
this.dataSource.namingStrategy.checkConstraintName(table, checkConstraint.expression);
const up = this.createCheckConstraintSql(table, checkConstraint);
const down = this.dropCheckConstraintSql(table, checkConstraint);
await this.executeQueries(up, down);
table.addCheckConstraint(checkConstraint);
}
/**
* Creates a new check constraints.
*
* @param tableOrName
* @param checkConstraints
*/
async createCheckConstraints(tableOrName, checkConstraints) {
const promises = checkConstraints.map((checkConstraint) => this.createCheckConstraint(tableOrName, checkConstraint));
await Promise.all(promises);
}
/**
* Drops check constraint.
*
* @param tableOrName
* @param checkOrName
* @param ifExists
*/
async dropCheckConstraint(tableOrName, checkOrName, ifExists) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const checkConstraint = InstanceChecker_1.InstanceChecker.isTableCheck(checkOrName)
? checkOrName
: table.checks.find((c) => c.name === checkOrName);
if (!checkConstraint) {
if (ifExists)
return;
throw new error_1.TypeORMError(`Supplied check constraint was not found in table ${table.name}`);
}
const up = this.dropCheckConstraintSql(table, checkConstraint);
const down = this.createCheckConstraintSql(table, checkConstraint);
await this.executeQueries(up, down);
table.removeCheckConstraint(checkConstraint);
}
/**
* Drops check constraints.
*
* @param tableOrName
* @param checkConstraints
* @param ifExists
*/
async dropCheckConstraints(tableOrName, checkConstraints, ifExists) {
const promises = checkConstraints.map((checkConstraint) => this.dropCheckConstraint(tableOrName, checkConstraint, ifExists));
await Promise.all(promises);
}
/**
* Creates a new exclusion constraint.
*
* @param tableOrName
* @param exclusionConstraint
*/
async createExclusionConstraint(tableOrName, exclusionConstraint) {
throw new error_1.TypeORMError(`SqlServer does not support exclusion constraints.`);
}
/**
* Creates a new exclusion constraints.
*
* @param tableOrName
* @param exclusionConstraints
*/
async createExclusionConstraints(tableOrName, exclusionConstraints) {
throw new error_1.TypeORMError(`SqlServer does not support exclusion constraints.`);
}
/**
* Drops exclusion constraint.
*
* @param tableOrName
* @param exclusionOrName
* @param ifExists
*/
async dropExclusionConstraint(tableOrName, exclusionOrName, ifExists) {
throw new error_1.TypeORMError(`SqlServer does not support exclusion constraints.`);
}
/**
* Drops exclusion constraints.
*
* @param tableOrName
* @param exclusionConstraints
* @param ifExists
*/
async dropExclusionConstraints(tableOrName, exclusionConstraints, ifExists) {
throw new error_1.TypeORMError(`SqlServer does not support exclusion constraints.`);
}
/**
* Creates a new foreign key.
*
* @param tableOrName
* @param foreignKey
*/
async createForeignKey(tableOrName, foreignKey) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const metadata = this.dataSource.hasMetadata(table.name)
? this.dataSource.getMetadata(table.name)
: undefined;
if (metadata?.treeParentRelation &&
metadata.treeParentRelation.isTreeParent &&
metadata.foreignKeys.find((foreignKey) => foreignKey.onDelete !== "NO ACTION"))
throw new error_1.TypeORMError("SqlServer does not support options in TreeParent.");
// new FK may be passed without name. In this case we generate FK name manually.
foreignKey.name ??= this.dataSource.namingStrategy.foreignKeyName(table, foreignKey.columnNames, this.getTablePath(foreignKey), foreignKey.referencedColumnNames);
const up = this.createForeignKeySql(table, foreignKey);
const down = this.dropForeignKeySql(table, foreignKey);
await this.executeQueries(up, down);
table.addForeignKey(foreignKey);
}
/**
* Creates a new foreign keys.
*
* @param tableOrName
* @param foreignKeys
*/
async createForeignKeys(tableOrName, foreignKeys) {
const promises = foreignKeys.map((foreignKey) => this.createForeignKey(tableOrName, foreignKey));
await Promise.all(promises);
}
/**
* Drops a foreign key from the table.
*
* @param tableOrName
* @param foreignKeyOrName
* @param ifExists
*/
async dropForeignKey(tableOrName, foreignKeyOrName, ifExists) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const foreignKey = InstanceChecker_1.InstanceChecker.isTableForeignKey(foreignKeyOrName)
? foreignKeyOrName
: table.foreignKeys.find((fk) => fk.name === foreignKeyOrName);
if (!foreignKey) {
if (ifExists)
return;
throw new error_1.TypeORMError(`Supplied foreign key was not found in table ${table.name}`);
}
foreignKey.name ??= this.dataSource.namingStrategy.foreignKeyName(table, foreignKey.columnNames, this.getTablePath(foreignKey), foreignKey.referencedColumnNames);
const up = this.dropForeignKeySql(table, foreignKey);
const down = this.createForeignKeySql(table, foreignKey);
await this.executeQueries(up, down);
table.removeForeignKey(foreignKey);
}
/**
* Drops a foreign keys from the table.
*
* @param tableOrName
* @param foreignKeys
* @param ifExists
*/
async dropForeignKeys(tableOrName, foreignKeys, ifExists) {
const promises = foreignKeys.map((foreignKey) => this.dropForeignKey(tableOrName, foreignKey, ifExists));
await Promise.all(promises);
}
/**
* Creates a new index.
*
* @param tableOrName
* @param index
*/
async createIndex(tableOrName, index) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
// new index may be passed without name. In this case we generate index name manually.
index.name ??= this.generateIndexName(table, index);
const up = this.createIndexSql(table, index);
const down = this.dropIndexSql(table, index);
await this.executeQueries(up, down);
table.addIndex(index);
}
/**
* Creates a new indices
*
* @param tableOrName
* @param indices
*/
async createIndices(tableOrName, indices) {
const promises = indices.map((index) => this.createIndex(tableOrName, index));
await Promise.all(promises);
}
/**
* Drops an index.
*
* @param tableOrName
* @param indexOrName
* @param ifExists
*/
async dropIndex(tableOrName, indexOrName, ifExists) {
const table = InstanceChecker_1.InstanceChecker.isTable(tableOrName)
? tableOrName
: await this.getCachedTable(tableOrName);
const index = InstanceChecker_1.InstanceChecker.isTableIndex(indexOrName)
? indexOrName
: table.indices.find((i) => i.name === indexOrName);
if (!index) {
if (ifExists)
return;
throw new error_1.TypeORMError(`Supplied index was not found in table ${table.name}`);
}
// old index may be passed without name. In this case we generate index name manually.
index.name ??= this.generateIndexName(table, index);
const up = this.dropIndexSql(table, index);
const down = this.createIndexSql(table, index);
await this.executeQueries(up, down);
table.removeIndex(index);
}
/**
* Drops an indices from the table.
*
* @param tableOrName
* @param indices
* @param ifExists
*/
async dropIndices(tableOrName, indices, ifExists) {
const promises = indices.map((index) => this.dropIndex(tableOrName, index, ifExists));
await Promise.all(promises);
}
/**
* Clears all table contents.
* Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
*
* @param tablePath
* @param options
* @param options.cascade
*/
async clearTable(tablePath, options) {
if (options?.cascade) {
throw new error_1.TypeORMError(`SqlServer does not support clearing table with cascade option`);
}
await this.query(`TRUNCATE TABLE ${this.escapePath(tablePath)}`);
}
/**
* Removes all tables from the currently connected database.
*
* @param database
*/
async clearDatabase(database) {
if (database) {
const isDatabaseExist = await this.hasDatabase(database);
if (!isDatabaseExist)
return Promise.resolve();
}
const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {
const allViewsSql = database
? `SELECT * FROM "${database}"."INFORMATION_SCHEMA"."VIEWS"`
: `SELECT * FROM "INFORMATION_SCHEMA"."VIEWS"`;
const allViewsResults = await this.query(allViewsSql);
await Promise.all(allViewsResults.map((viewResult) => {
// 'DROP VIEW' does not allow specifying the database name as a prefix to the object name.
const dropTableSql = `DROP VIEW "${viewResult["TABLE_SCHEMA"]}"."${viewResult["TABLE_NAME"]}"`;
return this.query(dropTableSql);
}));
const allTablesSql = database
? `SELECT * FROM "${database}"."INFORMATION_SCHEMA"."TABLES" WHERE "TABLE_TYPE" = 'BASE TABLE'`
: `SELECT * FROM "INFORMATION_SCHEMA"."TABLES" WHERE "TABLE_TYPE" = 'BASE TABLE'`;
const allTablesResults = await this.query(allTablesSql);
if (allTablesResults.length > 0) {
const tablesByCatalog = allTablesResults.reduce((c, { TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME }) => {
c[TABLE_CATALOG] = c[TABLE_CATALOG] ?? [];
c[TABLE_CATALOG].push({ TABLE_SCHEMA, TABLE_NAME });
return c;
}, {});
const foreignKeysSql = Object.entries(tablesByCatalog)
.map(([TABLE_CATALOG, tables]) => {
const conditions = tables
.map(({ TABLE_SCHEMA, TABLE_NAME }) => {
return `("fk"."referenced_object_id" = OBJECT_ID('"${TABLE_CATALOG}"."${TABLE_SCHEMA}"."${TABLE_NAME}"'))`;
})
.join(" OR ");
return (`SELECT DISTINCT '${TABLE_CATALOG}' AS "TABLE_CATALOG", ` +
`OBJECT_SCHEMA_NAME("fk"."parent_object_id", DB_ID('${TABLE_CATALOG}')) AS "TABLE_SCHEMA", ` +
`OBJECT_NAME("fk"."parent_object_id", DB_ID('${TABLE_CATALOG}')) AS "TABLE_NAME", ` +
`"fk"."name" AS "CONSTRAINT_NAME" ` +
`FROM "${TABLE_CATALOG}"."sys"."foreign_keys" AS "fk" ` +
`WHERE (${conditions})`);
})
.join(" UNION ALL ");
const foreignKeys = await this.query(foreignKeysSql);
await Promise.all(foreignKeys.map(async ({ TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_NAME, }) => {
// Disable the constraint first.
await this.query(`ALTER TABLE "${TABLE_CATALOG}"."${TABLE_SCHEMA}"."${TABLE_NAME}" ` +
`NOCHECK CONSTRAINT "${CONSTRAINT_NAME}"`);
await this.query(`ALTER TABLE "${TABLE_CATALOG}"."${TABLE_SCHEMA}"."${TABLE_NAME}" ` +
`DROP CONSTRAINT "${CONSTRAINT_NAME}" -- FROM CLEAR`);
}));
await Promise.all(allTablesResults.map(async (tablesResult) => {
if (tablesResult["TABLE_NAME"].startsWith("#")) {
// don't try to drop temporary tables
return;
}
const dropTableSql = `DROP TABLE "${tablesResult["TABLE_CATALOG"]}"."${tablesResult["TABLE_SCHEMA"]}"."${tablesResult["TABLE_NAME"]}"`;
return this.query(dropTableSql);
}));
}
if (!isAnotherTransactionActive)
await this.commitTransaction();
}
catch (error) {
try {
// we throw original error even if rollback thrown an error
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
}
catch (rollbackError) { }
throw error;
}
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
async loadViews(viewPaths) {
const hasTable = await this.hasTable(this.getTypeormMetadataTableName());
if (!hasTable) {
return [];
}
viewPaths ??= [];
const currentSchema = await this.getCurrentSchema();
const currentDatabase = await this.getCurrentDatabase();
const dbNames = viewPaths
.map((viewPath) => this.driver.parseTableName(viewPath).database)
.filter((database) => database);
if (this.driver.database &&
!dbNames.find((dbName) => dbName === this.driver.database))
dbNames.push(this.driver.database);
const viewsCondition = viewPaths
.map((viewPath) => {
let { schema, tableName: name } = this.driver.parseTableName(viewPath);
schema ??= currentSchema;
return `("T"."SCHEMA" = '${schema}' AND "T"."NAME" = '${name}')`;
})
.join(" OR ");
const query = dbNames
.map((dbName) => {
return (`SELECT "T".*, "V"."CHECK_OPTION" FROM ${this.escapePath(this.getTypeormMetadataTableName())} "t" ` +
`INNER JOIN "${dbName}"."INFORMATION_SCHEMA"."VIEWS" "V" ON "V"."TABLE_SCHEMA" = "T"."SCHEMA" AND "v"."TABLE_NAME" = "T"."NAME" WHERE "T"."TYPE" = '${MetadataTableType_1.MetadataTableType.VIEW}' ${viewsCondition ? `AND (${viewsCondition})` : ""}`);
})
.join(" UNION ALL ");
const dbViews = await this.query(query);
return dbViews.map((dbView) => {
const view = new View_1.View();
const db = dbView["TABLE_CATALOG"] === currentDatabase
? undefined
: dbView["TABLE_CATALOG"];
const schema = dbView["schema"] === currentSchema &&
!this.driver.options.schema
? undefined
: dbView["schema"];
view.database = dbView["TABLE_CATALOG"];
view.schema = dbView["schema"];
view.name = this.driver.buildTableName(dbView["name"], schema, db);
view.expression = dbView["value"];
return view;
});
}
/**
* Loads all tables (with given names) from the database and creates a Table from them.
*
* @param tableNames
*/
async loadTables(tableNames) {
// if no tables given then no need to proceed
if (tableNames?.length === 0) {
return [];
}
const currentSchema = await this.getCurrentSchema();
const currentDatabase = await this.getCurrentDatabase();
const dbTables = [];
if (!tableNames) {
const databasesSql = `SELECT DISTINCT "name" ` +
`FROM "master"."dbo"."sysdatabases" ` +
`WHERE "name" NOT IN ('master', 'model', 'msdb')`;
const dbDatabases = await this.query(databasesSql);
const tablesSql = dbDatabases
.map(({ name }) => {
return (`SELECT DISTINCT "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME" ` +
`FROM "${name}"."INFORMATION_SCHEMA"."TABLES" ` +
`WHERE "TABLE_TYPE" = 'BASE TABLE' AND ` +
`"TABLE_CATALOG" = '${name}' AND ` +
`ISNULL(Objectproperty(Object_id("TABLE_CATALOG" + '.' + "TABLE_SCHEMA" + '.' + "TABLE_NAME"), 'IsMSShipped'), 0) = 0`);
})
.join(" UNION ALL ");
dbTables.push(...(await this.query(tablesSql)));
}
else {
const tableNamesByCatalog = tableNames
.map((tableName) => this.driver.parseTableName(tableName))
.reduce((c, { database, ...other }) => {
database = database ?? currentDatabase;
c[database] = c[database] || [];
c[database].push({
schema: other.schema ?? currentSchema,
tableName: other.tableName,
});
return c;
}, {});
const tablesSql = Object.entries(tableNamesByCatalog)
.map(([database, tables]) => {
const tablesCondition = tables
.map(({ schema, tableName }) => {
return `("TABLE_SCHEMA" = '${schema}' AND "TABLE_NAME" = '${tableName}')`;
})
.join(" OR ");
return (`SELECT DISTINCT "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME" ` +
`FROM "${database}"."INFORMATION_SCHEMA"."TABLES" ` +
`WHERE ` +
`"TABLE_TYPE" = 'BASE TABLE' AND ` +
`"TABLE_CATALOG" = '${database}' AND ` +
`${tablesCondition}`);
})
.join(" UNION ALL ");
dbTables.push(...(await this.query(tablesSql)));
}
// if tables were not found in the db, no need to proceed
if (dbTables.length === 0) {
return [];
}
const dbTablesByCatalog = dbTables.reduce((c, { TABLE_CATALOG, ...other }) => {
c[TABLE_CATALOG] = c[TABLE_CATALOG] || [];
c[TABLE_CATALOG].push(other);
return c;
}, {});
const columnsSql = Object.entries(dbTablesByCatalog)
.map(([TABLE_CATALOG, tables]) => {
const condition = tables
.map(({ TABLE_SCHEMA, TABLE_NAME }) => `("TABLE_SCHEMA" = '${TABLE_SCHEMA}' AND "TABLE_NAME" = '${TABLE_NAME}')`)
.join("OR");
return (`SELECT "COLUMNS".*, "cc"."is_persisted", "cc"."definition" ` +
`FROM "${TABLE_CATALOG}"."INFORMATION_SCHEMA"."COLUMNS" ` +
`LEFT JOIN "${TABLE_CATALOG}"."sys"."computed_columns" "cc" ON COL_NAME("cc"."object_id", "cc"."column_id") = "COLUMN_NAME" ` +
`AND OBJECT_NAME("cc"."object_id", DB_ID('${TABLE_CATALOG}')) = "TABLE_NAME" ` +
`AND OBJECT_SCHEMA_NAME("cc"."object_id", DB_ID('${TABLE_CATALOG}')) = "TABLE_SCHEMA" ` +
`WHERE (${condition})`);
})
.join(" UNION ALL ");
const constraintsSql = Object.entries(dbTablesByCatalog)
.map(([TABLE_CATALOG, tables]) => {
const conditions = tables
.map(({ TABLE_NAME, TABLE_SCHEMA }) => `("columnUsages"."TABLE_SCHEMA" = '${TABLE_SCHEMA}' AND "columnUsages"."TABLE_NAME" = '${TABLE_NAME}')`)
.join(" OR ");
return (`SELECT "columnUsages".*, "tableConstraints"."CONSTRAINT_TYPE", "chk"."definition" ` +
`FROM "${TABLE_CATALOG}"."INFORMATION_SCHEMA"."CONSTRAINT_COLUMN_USAGE" "columnUsages" ` +
`INNER JOIN "${TABLE_CATALOG}"."INFORMATION_SCHEMA"."TABLE_CONSTRAINTS" "tableConstraints" ` +
`ON ` +
`"tableConstraints"."CONSTRAINT_NAME" = "columnUsages"."CONSTRAINT_NAME" AND ` +
`"tableConstraints"."TABLE_SCHEMA" = "columnUsages"."TABLE_SCHEMA" AND ` +
`"tableConstraints"."TABLE_NAME" = "columnUsages"."TABLE_NAME" ` +
`LEFT JOIN "${TABLE_CATALOG}"."sys"."check_constraints" "chk" ` +
`ON ` +
`"chk"."object_id" = OBJECT_ID("columnUsages"."TABLE_CATALOG" + '.' + "columnUsages"."TABLE_SCHEMA" + '.' + "columnUsages"."CONSTRAINT_NAME") ` +
`WHERE ` +
`(${conditions}) AND ` +
`"tableConstraints"."CONSTRAINT_TYPE" IN ('PRIMARY KEY', 'UNIQUE', 'CHECK')`);
})
.join(" UNION ALL ");
const foreignKeysSql = Object.entries(dbTablesByCatalog)
.map(([TABLE_CATALOG, tables]) => {
const conditions = tables
.map(({ TABLE_NAME, TABLE_SCHEMA }) => `("s1"."name" = '${TABLE_SCHEMA}' AND "t1"."name" = '${TABLE_NAME}')`)
.join(" OR ");
return (`SELECT "fk"."name" AS "FK_NAME", '${TABLE_CATALOG}' AS "TABLE_CATALOG", "s1"."name" AS "TABLE_SCHEMA", "t1"."name" AS "TABLE_NAME", ` +
`"col1"."name" AS "COLUMN_NAME", "s2"."name" AS "REF_SCHEMA", "t2"."name" AS "REF_TABLE", "col2"."name" AS "REF_COLUMN", ` +
`"fk"."delete_referential_action_desc" AS "ON_DELETE", "fk"."update_referential_action_desc" AS "ON_UPDATE" ` +
`FROM "${TABLE_CATALOG}"."sys"."foreign_keys" "fk" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."foreign_key_columns" "fkc" ON "fkc"."constraint_object_id" = "fk"."object_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."tables" "t1" ON "t1"."object_id" = "fk"."parent_object_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."schemas" "s1" ON "s1"."schema_id" = "t1"."schema_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."tables" "t2" ON "t2"."object_id" = "fk"."referenced_object_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."schemas" "s2" ON "s2"."schema_id" = "t2"."schema_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."columns" "col1" ON "col1"."column_id" = "fkc"."parent_column_id" AND "col1"."object_id" = "fk"."parent_object_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."columns" "col2" ON "col2"."column_id" = "fkc"."referenced_column_id" AND "col2"."object_id" = "fk"."referenced_object_id" ` +
`WHERE (${conditions})`);
})
.join(" UNION ALL ");
const identityColumnsSql = Object.entries(dbTablesByCatalog)
.map(([TABLE_CATALOG, tables]) => {
const conditions = tables
.map(({ TABLE_NAME, TABLE_SCHEMA }) => `("TABLE_SCHEMA" = '${TABLE_SCHEMA}' AND "TABLE_NAME" = '${TABLE_NAME}')`)
.join(" OR ");
return (`SELECT "TABLE_CATALOG", "TABLE_SCHEMA", "COLUMN_NAME", "TABLE_NAME" ` +
`FROM "${TABLE_CATALOG}"."INFORMATION_SCHEMA"."COLUMNS" ` +
`WHERE ` +
`EXISTS(SELECT 1 FROM "${TABLE_CATALOG}"."sys"."columns" "S" WHERE OBJECT_ID("TABLE_CATALOG" + '.' + "TABLE_SCHEMA" + '.' + "TABLE_NAME") = "S"."OBJECT_ID" AND "COLUMN_NAME" = "S"."NAME" AND "S"."is_identity" = 1) AND ` +
`(${conditions})`);
})
.join(" UNION ALL ");
const dbCollationsSql = `SELECT "NAME", "COLLATION_NAME" FROM "sys"."databases"`;
const indicesSql = Object.entries(dbTablesByCatalog)
.map(([TABLE_CATALOG, tables]) => {
const conditions = tables
.map(({ TABLE_NAME, TABLE_SCHEMA }) => `("s"."name" = '${TABLE_SCHEMA}' AND "t"."name" = '${TABLE_NAME}')`)
.join(" OR ");
return (`SELECT '${TABLE_CATALOG}' AS "TABLE_CATALOG", "s"."name" AS "TABLE_SCHEMA", "t"."name" AS "TABLE_NAME", ` +
`"ind"."name" AS "INDEX_NAME", "col"."name" AS "COLUMN_NAME", "ind"."is_unique" AS "IS_UNIQUE", "ind"."filter_definition" as "CONDITION" ` +
`FROM "${TABLE_CATALOG}"."sys"."indexes" "ind" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."index_columns" "ic" ON "ic"."object_id" = "ind"."object_id" AND "ic"."index_id" = "ind"."index_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."columns" "col" ON "col"."object_id" = "ic"."object_id" AND "col"."column_id" = "ic"."column_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."tables" "t" ON "t"."object_id" = "ind"."object_id" ` +
`INNER JOIN "${TABLE_CATALOG}"."sys"."schemas" "s" ON "s"."schema_id" = "t"."schema_id" ` +
`WHERE ` +
`"ind"."is_primary_key" = 0 AND "ind"."is_unique_constraint" = 0 AND "t"."is_ms_shipped" = 0 AND ` +
`(${conditions})`);
})
.join(" UNION ALL ");
const [dbColumns, dbConstraints, dbForeignKeys, dbIdentityColumns, dbCollations, dbIndices,] = await Promise.all([
this.query(columnsSql),
this.query(constraintsSql),
this.query(foreignKeysSql),
this.query(identityColumnsSql),
this.query(dbCollationsSql),
this.query(indicesSql),
]);
// create table schemas for loaded tables
return await Promise.all(dbTables.map(async (dbTable) => {
const table = new Table_1.Table();
const getSchemaFromKey = (dbObject, key) => {
return dbObject[key] === currentSchema &&
(!this.driver.options.schema ||
this.driver.options.schema === currentSchema)
? undefined
: dbObject[key];
};
// We do not need to join schema and database names, when db or schema is by default.
const db = dbTable["TABLE_CATALOG"] === currentDatabase
? undefined
: dbTable["TABLE_CATALOG"];
const schema = getSchemaFromKey(dbTable, "TABLE_SCHEMA");
table.database = dbTable["TABLE_CATALOG"];
table.schema = dbTable["TABLE_SCHEMA"];
table.name = this.driver.buildTableName(dbTable["TABLE_NAME"], schema, db);
const defaultCollation = dbCollations.find((dbCollation) => dbCollation["NAME"] === dbTable["TABLE_CATALOG"]);
// create columns from the loaded columns
table.columns = await Promise.all(dbColumns
.filter((dbColumn) => dbColumn["TABLE_NAME"] ===
dbTable["TABLE_NAME"] &&
dbColumn["TABLE_SCHEMA"] ===
dbTable["TABLE_SCHEMA"] &&
dbColumn["TABLE_CATALOG"] ===
dbTable["TABLE_CATALOG"])
.map(async (dbColumn) => {
const columnConstraints = dbConstraints.filter((dbConstraint) => dbConstraint["TABLE_NAME"] ===
dbColumn["TABLE_NAME"] &&
dbConstraint["TABLE_SCHEMA"] ===
dbColumn["TABLE_SCHEMA"] &&
dbConstraint["TABLE_CATALOG"] ===
dbColumn["TABLE_CATALOG"] &&
dbConstraint["COLUMN_NAME"] ===
dbColumn["COLUMN_NAME"]);
const uniqueConstraints = columnConstraints.filter((constraint) => constraint["CONSTRAINT_TYPE"] === "UNIQUE");
const isConstraintComposite = uniqueConstraints.every((uniqueConstraint) => {
return dbConstraints.some((dbConstraint) => dbConstraint["CONSTRAINT_TYPE"] ===
"UNIQUE" &&
dbConstraint["CONSTRAINT_NAME"] ===
uniqueConstraint["CONSTRAINT_NAME"] &&
dbConstraint["TABLE_SCHEMA"] ===
dbColumn["TABLE_SCHEMA"] &&
dbConstraint["TABLE_CATALOG"] ===
dbColumn["TABLE_CATALOG"] &&
dbConstraint["COLUMN_NAME"] !==
dbColumn["COLUMN_NAME"]);
});
const isGenerated = !!dbIdentityColumns.find((column) => column["TABLE_NAME"] ===
dbColumn["TABLE_NAME"] &&
column["TABLE_SCHEMA"] ===
dbColumn["TABLE_SCHEMA"] &&
column["TABLE_CATALOG"] ===
dbColumn["TABLE_CATALOG"] &&
column["COLUMN_NAME"] ===
dbColumn["COLUMN_NAME"]);
const tableColumn = new TableColumn_1.TableColumn();
tableColumn.name = dbColumn["COLUMN_NAME"];
tableColumn.type =
dbColumn["DATA_TYPE"].toLowerCase();
// check only columns that have length property
if (this.driver.withLengthColumnTypes.indexOf(tableColumn.type) !== -1 &&
dbColumn["CHARACTER_MAXIMUM_LENGTH"]) {
const length = dbColumn["CHARACTER_MAXIMUM_LENGTH"].toString();
if (length === "-1") {
tableColumn.length = "MAX";
}
else {
if (tableColumn.type === "vector") {
const len = +length;
// NOTE: real returned length is (N*4 + 8) where N is desired dimensions
if (!Number.isNaN(len)) {
tableColumn.length = String((len - 8) / 4);
}
}
else {
tableColumn.length =
!this.isDefaultColumnLength(table, tableColumn, length)
? length
: "";
}
}
}
if (tableColumn.type === "decimal" ||
tableColumn.type === "numeric") {
if (dbColumn["NUMERIC_PRECISION"] !== null &&
!this.isDefaultColumnPrecision(table, tableColumn, dbColumn["NUMERIC_PRECISION"]))
tableColumn.precision =
dbColumn["NUMERIC_PRECISION"];
if (dbColumn["NUMERIC_SCALE"] !== null &&
!this.isDefaultColumnScale(table, tableColumn, dbColumn["NUMERIC_SCALE"]))
tableColumn.scale =
dbColumn["NUMERIC_SCALE"];
}
if (tableColumn.type === "nvarchar") {
// Check if this is an enum
const columnCheckConstraints = columnConstraints.filter((constraint) => constraint["CONSTRAINT_TYPE"] ===
"CHECK");
if (columnCheckConstraints.length) {
// const isEnumRegexp = new RegExp("^\\(\\[" + tableColumn.name + "\\]='[^']+'(?: OR \\[" + tableColumn.name + "\\]='[^']+')*\\)$");
for (const checkConstraint of columnCheckConstraints) {
if (this.isEnumCheckConstraint(checkConstraint["CONSTRAINT_NAME"])) {
// This is an enum constraint, make column into an enum
tableColumn.enum = [];
const enumValueRegexp = new RegExp("\\[" +
tableColumn.name +
"\\]='([^']+)'", "g");
let result;
while ((result = enumValueRegexp.exec(checkConstraint["definition"])) !== null) {
tableColumn.enum.unshift(result[1]);
}
// Skip other column constraints
break;
}
}
}
}
const primaryConstraint = columnConstraints.find((constraint) => constraint["CONSTRAINT_TYPE"] ===
"PRIMARY KEY");
if (primaryConstraint) {
tableColumn.isPrimary = true;
// find another columns involved in primary key constraint
const anotherPrimaryConstraints = dbConstraints.filter((constraint) => constraint["TABLE_NAME"] ===
dbColumn["TABLE_NAME"] &&
constraint["TABLE_SCHEMA"] ===
dbColumn["TABLE_SCHEMA"] &&
constraint["TABLE_CATALOG"] ===
dbColumn["TABLE_CATALOG"] &&
constraint["COLUMN_NAME"] !==
dbColumn["COLUMN_NAME"] &&
constraint["CONSTRAINT_TYPE"] ===
"PRIMARY KEY");
// collect all column names
const columnNames = anotherPrimaryConstraints.map((constraint) => constraint["COLUMN_NAME"]);
columnNames.push(dbColumn["COLUMN_NAME"]);
// build default primary key constraint name
const pkName = this.dataSource.namingStrategy.primaryKeyName(table, columnNames);
// if primary key has user-defined constraint name, write it in table column
if (primaryConstraint["CONSTRAINT_NAME"] !==
pkName) {
tableColumn.primaryKeyConstraintName =
primaryConstraint["CONSTRAINT_NAME"];
}
}
tableColumn.default =
dbColumn["COLUMN_DEFAULT"] !== null &&
dbColumn["COLUMN_DEFAULT"] !== undefined
? this.removeParenthesisFromDefault(dbColumn["COLUMN_DEFAULT"])
: undefined;
tableColumn.isNullable =
dbColumn["IS_NULLABLE"] === "YES";
tableColumn.isUnique =
uniqueConstraints.length > 0 &&
!isConstraintComposite;
tableColumn.isGenerated = isGenerated;
if (isGenerated)
tableColumn.generationStrategy = "increment";
if (tableColumn.default === "newsequentialid()") {
tableColumn.isGenerated = true;
tableColumn.generationStrategy = "uuid";
tableColumn.default = undefined;
}
// todo: unable to get default charset
// tableColumn.charset = dbColumn["CHARACTER_SET_NAME"];
if (dbColumn["COLLATION_NAME"])
tableColumn.collation =
dbColumn["COLLATION_NAME"] ===
defaultCollation["COLLATION_NAME"]
? undefined
: dbColumn["COLLATION_NAME"];
if (tableColumn.type === "datetime2" ||
tableColumn.type === "time" ||
tableColumn.type === "datetimeoffset") {
tableColumn.precision =
!this.isDefaultColumnPrecision(table, tableColumn, dbColumn["DATETIME_PRECISION"])
? dbColumn["DATETIME_PRECISION"]
: undefined;
}
if (dbColumn["is_persisted"] !== null &&
dbColumn["is_persisted"] !== undefined &&
dbColumn["definition"]) {
tableColumn.generatedType =
dbColumn["is_persisted"] === true
? "STORED"
: "VIRTUAL";
// We cannot relay on information_schema.columns.generation_expression, because it is formatted different.
const asExpressionQuery = this.selectTypeormMetadataSql({
database: dbTable["TABLE_CATALOG"],
schema: dbTable["TABLE_SCHEMA"],
table: dbTable["TABLE_NAME"],
type: MetadataTableType_1.MetadataTableType.GENERATED_COLUMN,
name: tableColumn.name,
});
const results = await this.query(asExpressionQuery.query, asExpressionQuery.parameters);
if (results[0]?.value) {
tableColumn.asExpression = results[0].value;
}
else {
tableColumn.asExpression = "";
}
}
return tableColumn;
}));
// find unique constraints of table, group them by constraint name and build TableUnique.
const tableUniqueConstraints = OrmUtils_1.OrmUtils.uniq(dbConstraints.filter((dbConstraint) => dbConstraint["TABLE_NAME"] ===
dbTable["TABLE_NAME"] &&
dbConstraint["TABLE_SCHEMA"] ===
dbTable["TABLE_SCHEMA"] &&
dbConstraint["TABLE_CATALOG"] ===
dbTable["TABLE_CATALOG"] &&
dbConstraint["CONSTRAINT_TYPE"] === "UNIQUE"), (dbConstraint) => dbConstraint["CONSTRAINT_NAME"]);
table.uniques = tableUniqueConstraints.map((constraint) => {
const uniques = dbConstraints.filter((dbC) => dbC["CONSTRAINT_NAME"] ===
constraint["CONSTRAINT_NAME"]);
return new TableUnique_1.TableUnique({
name: constraint["CONSTRAINT_NAME"],
columnNames: uniques.map((u) => u["COLUMN_NAME"]),
});
});
// find check constraints of table, group them by constraint name and build TableCheck.
const tableCheckConstraints = OrmUtils_1.OrmUtils.uniq(dbConstraints.filter((dbConstraint) => dbConstraint["TABLE_NAME"] ===
dbTable["TABLE_NAME"] &&
dbConstraint["TABLE_SCHEMA"] ===
dbTable["TABLE_SCHEMA"] &&
dbConstraint["TABLE_CATALOG"] ===
dbTable["TABLE_CATALOG"] &&
dbConstraint["CONSTRAINT_TYPE"] === "CHECK"), (dbConstraint) => dbConstraint["CONSTRAINT_NAME"]);
table.checks = tableCheckConstraints
.filter((constraint) => !this.isEnumCheckConstraint(constraint["CONSTRAINT_NAME"]))
.map((constraint) => {
const checks = dbConstraints.filter((dbC) => dbC["CONSTRAINT_NAME"] ===
constraint["CONSTRAINT_NAME"]);
return new TableCheck_1.TableCheck({
name: constraint["CONSTRAINT_NAME"],
columnNames: checks.map((c) => c["COLUMN_NAME"]),
expression: constraint["definition"],
});
});
// find foreign key constraints of table, group them by constraint name and build TableForeignKey.
const tableForeignKeyConstraints = OrmUtils_1.OrmUtils.uniq(dbForeignKeys.filter((dbForeignKey) => dbForeignKey["TABLE_NAME"] ===
dbTable["TABLE_NAME"] &&
dbForeignKey["TABLE_SCHEMA"] ===
dbTable["TABLE_SCHEMA"] &&
dbForeignKey["TABLE_CATALOG"] ===
dbTable["TABLE_CATALOG"]), (dbForeignKey) => dbForeignKey["FK_NAME"]);
table.foreignKeys = tableForeignKeyConstraints.map((dbForeignKey) => {
const foreignKeys = dbForeignKeys.filter((dbFk) => dbFk["FK_NAME"] === dbForeignKey["FK_NAME"]);
// if referenced table located in currently used db and schema, we don't need to concat db and schema names to table name.
const db = dbForeignKey["TABLE_CATALOG"] === currentDatabase
? undefined
: dbForeignKey["TABLE_CATALOG"];
const schema = getSchemaFromKey(dbForeignKey, "REF_SCHEMA");
const referencedTableName = this.driver.buildTableName(dbForeignKey["REF_TABLE"], schema, db);
return new TableForeignKey_1.TableForeignKey({
name: dbForeignKey["FK_NAME"],
columnNames: foreignKeys.map((dbFk) => dbFk["COLUMN_NAME"]),
referencedDatabase: dbForeignKey["TABLE_CATALOG"],
referencedSchema: dbForeignKey["REF_SCHEMA"],
referencedTableName: referencedTableName,
referencedColumnNames: foreignKeys.map((dbFk) => dbFk["REF_COLUMN"]),
onDelete: dbForeignKey["ON_DELETE"].replace("_", " "), // SqlServer returns NO_ACTION, instead of NO ACTION
onUpdate: dbForeignKey["ON_UPDATE"].replace("_", " "), // SqlServer returns NO_ACTION, instead of NO ACTION
});
});
// find index constraints of table, group them by constraint name and build TableIndex.
const tableIndexConstraints = OrmUtils_1.OrmUtils.uniq(dbIndices.filter((dbIndex) => dbIndex["TABLE_NAME"] === dbTable["TABLE_NAME"] &&
dbIndex["TABLE_SCHEMA"] ===
dbTable["TABLE_SCHEMA"] &&
dbIndex["TABLE_CATALOG"] ===
dbTable["TABLE_CATALOG"]), (dbIndex) => dbIndex["INDEX_NAME"]);
table.indices = tableIndexConstraints.map((constraint) => {
const indices = dbIndices.filter((index) => {
return (index["TABLE_CATALOG"] ===
constraint["TABLE_CATALOG"] &&
index["TABLE_SCHEMA"] ===
constraint["TABLE_SCHEMA"] &&
index["TABLE_NAME"] === constraint["TABLE_NAME"] &&
index["INDEX_NAME"] === constraint["INDEX_NAME"]);
});
return new TableIndex_1.TableIndex({
table: table,
name: constraint["INDEX_NAME"],
columnNames: indices.map((i) => i["COLUMN_NAME"]),
isUnique: constraint["IS_UNIQUE"],
where: constraint["CONDITION"],
});
});
return table;
}));
}
/**
* Builds and returns SQL for create table.
*
* @param table
* @param createForeignKeys
*/
createTableSql(table, createForeignKeys) {
const columnDefinitions = table.columns
.map((column) => this.buildCreateColumnSql(table, column, false, true))
.join(", ");
let sql = `CREATE TABLE ${this.escapePath(table)} (${columnDefinitions}`;
table.columns
.filter((column) => column.isUnique)
.forEach((column) => {
const isUniqueExist = table.uniques.some((unique) => unique.columnNames.length === 1 &&
unique.columnNames[0] === column.name);
if (!isUniqueExist)
table.uniques.push(new TableUnique_1.TableUnique({
name: this.dataSource.namingStrategy.uniqueConstraintName(table, [column.name]),
columnNames: [column.name],
}));
});
if (table.uniques.length > 0) {
const uniquesSql = table.uniques
.map((unique) => {
const uniqueName = unique.name ??
this.dataSource.namingStrategy.uniqueConstraintName(table, unique.columnNames);
const columnNames = unique.columnNames
.map((columnName) => `"${columnName}"`)
.join(", ");
return `CONSTRAINT "${uniqueName}" UNIQUE (${columnNames})`;
})
.join(", ");
sql += `, ${uniquesSql}`;
}
if (table.checks.length > 0) {
const checksSql = table.checks
.map((check) => {
const checkName = check.name ??
this.dataSource.namingStrategy.checkConstraintName(table, check.expression);
return `CONSTRAINT "${checkName}" CHECK (${check.expression})`;
})
.join(", ");
sql += `, ${checksSql}`;
}
if (table.foreignKeys.length > 0 && createForeignKeys) {
const foreignKeysSql = table.foreignKeys
.map((fk) => {
const columnNames = fk.columnNames
.map((columnName) => `"${columnName}"`)
.join(", ");
fk.name ??= this.dataSource.namingStrategy.foreignKeyName(table, fk.columnNames, this.getTablePath(fk), fk.referencedColumnNames);
const referencedColumnNames = fk.referencedColumnNames
.map((columnName) => `"${columnName}"`)
.join(", ");
let constraint = `CONSTRAINT "${fk.name}" FOREIGN KEY (${columnNames}) REFERENCES ${this.escapePath(this.getTablePath(fk))} (${referencedColumnNames})`;
if (fk.onDelete)
constraint += ` ON DELETE ${fk.onDelete}`;
if (fk.onUpdate)
constraint += ` ON UPDATE ${fk.onUpdate}`;
return constraint;
})
.join(", ");
sql += `, ${foreignKeysSql}`;
}
const primaryColumns = table.columns.filter((column) => column.isPrimary);
if (primaryColumns.length > 0) {
const primaryKeyName = primaryColumns[0].primaryKeyConstraintName ??
this.dataSource.namingStrategy.primaryKeyName(table, primaryColumns.map((column) => column.name));
const columnNames = primaryColumns
.map((column) => `"${column.name}"`)
.join(", ");
sql += `, CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNames})`;
}
sql += `)`;
return new Query_1.Query(sql);
}
/**
* Builds drop table sql.
*
* @param tableOrName
* @param ifExists
*/
dropTableSql(tableOrName, ifExists) {
const query = ifExists
? `DROP TABLE IF EXISTS ${this.escapePath(tableOrName)}`
: `DROP TABLE ${this.escapePath(tableOrName)}`;
return new Query_1.Query(query);
}
createViewSql(view) {
const parsedName = this.driver.parseTableName(view);
// Can't use `escapePath` here because `CREATE VIEW` does not accept database names.
const viewIdentifier = parsedName.schema
? `"${parsedName.schema}"."${parsedName.tableName}"`
: `"${parsedName.tableName}"`;
if (typeof view.expression === "string") {
return new Query_1.Query(`CREATE VIEW ${viewIdentifier} AS ${view.expression}`);
}
else {
return new Query_1.Query(`CREATE VIEW ${viewIdentifier} AS ${view
.expression(this.dataSource)
.getQuery()}`);
}
}
async insertViewDefinitionSql(view) {
const parsedTableName = this.driver.parseTableName(view);
parsedTableName.schema ??= await this.getCurrentSchema();
const expression = typeof view.expression === "string"
? view.expression.trim()
: view.expression(this.dataSource).getQuery();
return this.insertTypeormMetadataSql({
type: MetadataTableType_1.MetadataTableType.VIEW,
database: parsedTableName.database,
schema: parsedTableName.schema,
name: parsedTableName.tableName,
value: expression,
});
}
/**
* Builds drop view sql.
*
* @param viewOrPath
* @param ifExists
*/
dropViewSql(viewOrPath, ifExists) {
const query = ifExists
? `DROP VIEW IF EXISTS ${this.escapePath(viewOrPath)}`
: `DROP VIEW ${this.escapePath(viewOrPath)}`;
return new Query_1.Query(query);
}
/**
* Builds remove view sql.
*
* @param viewOrPath
*/
async deleteViewDefinitionSql(viewOrPath) {
const parsedTableName = this.driver.parseTableName(viewOrPath);
parsedTableName.schema ??= await this.getCurrentSchema();
return this.deleteTypeormMetadataSql({
type: MetadataTableType_1.MetadataTableType.VIEW,
database: parsedTableName.database,
schema: parsedTableName.schema,
name: parsedTableName.tableName,
});
}
/**
* Builds create index sql.
*
* @param table
* @param index
*/
createIndexSql(table, index) {
const columns = index.columnNames
.map((columnName) => `"${columnName}"`)
.join(", ");
return new Query_1.Query(`CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX "${index.name}" ON ${this.escapePath(table)} (${columns}) ${index.where ? "WHERE " + index.where : ""}`);
}
/**
* Builds drop index sql.
*
* @param table
* @param indexOrName
*/
dropIndexSql(table, indexOrName) {
const indexName = InstanceChecker_1.InstanceChecker.isTableIndex(indexOrName)
? indexOrName.name
: indexOrName;
return new Query_1.Query(`DROP INDEX "${indexName}" ON ${this.escapePath(table)}`);
}
/**
* Builds create primary key sql.
*
* @param table
* @param columnNames
* @param constraintName
*/
createPrimaryKeySql(table, columnNames, constraintName) {
const primaryKeyName = constraintName ??
this.dataSource.namingStrategy.primaryKeyName(table, columnNames);
const columnNamesString = columnNames
.map((columnName) => `"${columnName}"`)
.join(", ");
return new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNamesString})`);
}
/**
* Builds drop primary key sql.
*
* @param table
*/
dropPrimaryKeySql(table) {
const columnNames = table.primaryColumns.map((column) => column.name);
const constraintName = table.primaryColumns[0].primaryKeyConstraintName;
const primaryKeyName = constraintName ??
this.dataSource.namingStrategy.primaryKeyName(table, columnNames);
return new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${primaryKeyName}"`);
}
/**
* Builds create unique constraint sql.
*
* @param table
* @param uniqueConstraint
*/
createUniqueConstraintSql(table, uniqueConstraint) {
const columnNames = uniqueConstraint.columnNames
.map((column) => `"` + column + `"`)
.join(", ");
return new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${uniqueConstraint.name}" UNIQUE (${columnNames})`);
}
/**
* Builds drop unique constraint sql.
*
* @param table
* @param uniqueOrName
*/
dropUniqueConstraintSql(table, uniqueOrName) {
const uniqueName = InstanceChecker_1.InstanceChecker.isTableUnique(uniqueOrName)
? uniqueOrName.name
: uniqueOrName;
return new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${uniqueName}"`);
}
/**
* Builds create check constraint sql.
*
* @param table
* @param checkConstraint
*/
createCheckConstraintSql(table, checkConstraint) {
return new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${checkConstraint.name}" CHECK (${checkConstraint.expression})`);
}
/**
* Builds drop check constraint sql.
*
* @param table
* @param checkOrName
*/
dropCheckConstraintSql(table, checkOrName) {
const checkName = InstanceChecker_1.InstanceChecker.isTableCheck(checkOrName)
? checkOrName.name
: checkOrName;
return new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${checkName}"`);
}
/**
* Builds create foreign key sql.
*
* @param table
* @param foreignKey
*/
createForeignKeySql(table, foreignKey) {
const columnNames = foreignKey.columnNames
.map((column) => `"` + column + `"`)
.join(", ");
const referencedColumnNames = foreignKey.referencedColumnNames
.map((column) => `"` + column + `"`)
.join(",");
let sql = `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${foreignKey.name}" FOREIGN KEY (${columnNames}) ` +
`REFERENCES ${this.escapePath(this.getTablePath(foreignKey))}(${referencedColumnNames})`;
if (foreignKey.onDelete)
sql += ` ON DELETE ${foreignKey.onDelete}`;
if (foreignKey.onUpdate)
sql += ` ON UPDATE ${foreignKey.onUpdate}`;
return new Query_1.Query(sql);
}
/**
* Builds drop foreign key sql.
*
* @param table
* @param foreignKeyOrName
*/
dropForeignKeySql(table, foreignKeyOrName) {
const foreignKeyName = InstanceChecker_1.InstanceChecker.isTableForeignKey(foreignKeyOrName)
? foreignKeyOrName.name
: foreignKeyOrName;
return new Query_1.Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${foreignKeyName}"`);
}
/**
* Escapes given table or View path.
*
* @param target
*/
escapePath(target) {
const { database, schema, tableName } = this.driver.parseTableName(target);
if (database && database !== this.driver.database) {
if (schema && schema !== this.driver.searchSchema) {
return `"${database}"."${schema}"."${tableName}"`;
}
return `"${database}".."${tableName}"`;
}
if (schema && schema !== this.driver.searchSchema) {
return `"${schema}"."${tableName}"`;
}
return `"${tableName}"`;
}
/**
* Concat database name and schema name to the foreign key name.
* Needs because FK name is relevant to the schema and database.
*
* @param fkName
* @param schemaName
* @param dbName
*/
buildForeignKeyName(fkName, schemaName, dbName) {
let joinedFkName = fkName;
if (schemaName && schemaName !== this.driver.searchSchema)
joinedFkName = schemaName + "." + joinedFkName;
if (dbName && dbName !== this.driver.database)
joinedFkName = dbName + "." + joinedFkName;
return joinedFkName;
}
/**
* Removes parenthesis around default value.
* Sql server returns default value with parenthesis around.
*
* @example
* ('My text') // for string
* ((1)) // for number
* (newsequentialId()) // for function
*
* @param defaultValue
*/
removeParenthesisFromDefault(defaultValue) {
if (!defaultValue.startsWith("("))
return defaultValue;
const end = defaultValue.lastIndexOf(")");
if (!defaultValue.endsWith(")") || end <= 0)
return defaultValue;
const normalizedDefault = defaultValue.slice(1, end);
return this.removeParenthesisFromDefault(normalizedDefault);
}
/**
* Builds a query for create column.
*
* @param table
* @param column
* @param skipIdentity
* @param createDefault
* @param skipEnum
*/
buildCreateColumnSql(table, column, skipIdentity, createDefault, skipEnum) {
let c = `"${column.name}" ${this.dataSource.driver.createFullType(column)}`;
if (!skipEnum && column.enum) {
const expression = this.getEnumExpression(column);
const checkName = this.dataSource.namingStrategy.checkConstraintName(table, expression, true);
c += ` CONSTRAINT ${checkName} CHECK(${expression})`;
}
if (column.collation)
c += " COLLATE " + column.collation;
if (column.asExpression) {
c += ` AS (${column.asExpression})`;
if (column.generatedType === "STORED") {
c += ` PERSISTED`;
// NOT NULL can be specified for computed columns only if PERSISTED is also specified
if (column.isNullable !== true)
c += " NOT NULL";
}
}
else {
if (column.isNullable !== true)
c += " NOT NULL";
}
if (column.isGenerated === true &&
column.generationStrategy === "increment" &&
!skipIdentity)
// don't use skipPrimary here since updates can update already exist primary without auto inc.
c += " IDENTITY(1,1)";
if (column.default !== undefined &&
column.default !== null &&
createDefault) {
// we create named constraint to be able to delete this constraint when column been dropped
const defaultName = this.dataSource.namingStrategy.defaultConstraintName(table, column.name);
c += ` CONSTRAINT "${defaultName}" DEFAULT ${column.default}`;
}
if (column.isGenerated &&
column.generationStrategy === "uuid" &&
!column.default) {
// we create named constraint to be able to delete this constraint when column been dropped
const defaultName = this.dataSource.namingStrategy.defaultConstraintName(table, column.name);
c += ` CONSTRAINT "${defaultName}" DEFAULT NEWSEQUENTIALID()`;
}
return c;
}
getEnumExpression(column) {
if (!column.enum) {
throw new Error(`Enum is not defined in column ${column.name}`);
}
return (column.name +
" IN (" +
column.enum.map((val) => "'" + val + "'").join(",") +
")");
}
isEnumCheckConstraint(name) {
return name.indexOf("CHK_") !== -1 && name.indexOf("_ENUM") !== -1;
}
/**
* Converts MssqlParameter into real mssql parameter type.
*
* @param parameter
*/
mssqlParameterToNativeParameter(parameter) {
switch (this.driver.normalizeType({ type: parameter.type })) {
case "bit":
return this.driver.mssql.Bit;
case "bigint":
return this.driver.mssql.BigInt;
case "decimal":
return this.driver.mssql.Decimal(...parameter.params);
case "float":
return this.driver.mssql.Float;
case "int":
return this.driver.mssql.Int;
case "money":
return this.driver.mssql.Money;
case "numeric":
return this.driver.mssql.Numeric(...parameter.params);
case "smallint":
return this.driver.mssql.SmallInt;
case "smallmoney":
return this.driver.mssql.SmallMoney;
case "real":
return this.driver.mssql.Real;
case "tinyint":
return this.driver.mssql.TinyInt;
case "char":
if (this.driver.options.options
?.disableAsciiToUnicodeParamConversion) {
return this.driver.mssql.Char(...parameter.params);
}
return this.driver.mssql.NChar(...parameter.params);
case "nchar":
return this.driver.mssql.NChar(...parameter.params);
case "text":
if (this.driver.options.options
?.disableAsciiToUnicodeParamConversion) {
return this.driver.mssql.Text;
}
return this.driver.mssql.Ntext;
case "ntext":
return this.driver.mssql.Ntext;
case "varchar":
if (this.driver.options.options
?.disableAsciiToUnicodeParamConversion) {
return this.driver.mssql.VarChar(...parameter.params);
}
return this.driver.mssql.NVarChar(...parameter.params);
case "nvarchar":
return this.driver.mssql.NVarChar(...parameter.params);
case "xml":
return this.driver.mssql.Xml;
case "time":
return this.driver.mssql.Time(...parameter.params);
case "date":
return this.driver.mssql.Date;
case "datetime":
return this.driver.mssql.DateTime;
case "datetime2":
return this.driver.mssql.DateTime2(...parameter.params);
case "datetimeoffset":
return this.driver.mssql.DateTimeOffset(...parameter.params);
case "smalldatetime":
return this.driver.mssql.SmallDateTime;
case "uniqueidentifier":
return this.driver.mssql.UniqueIdentifier;
case "variant":
return this.driver.mssql.Variant;
case "binary":
return this.driver.mssql.Binary;
case "varbinary":
return this.driver.mssql.VarBinary(...parameter.params);
case "image":
return this.driver.mssql.Image;
case "udt":
return this.driver.mssql.UDT;
case "rowversion":
return this.driver.mssql.RowVersion;
case "vector":
return this.driver.mssql.Ntext;
}
}
/**
* Change table comment.
*
* @param tableOrName
* @param comment
*/
changeTableComment(tableOrName, comment) {
throw new error_1.TypeORMError(`sqlserver driver does not support change table comment.`);
}
}
exports.SqlServerQueryRunner = SqlServerQueryRunner;
//# sourceMappingURL=SqlServerQueryRunner.js.map