fixes #537 and fixed issues with schema sync of "default" value

This commit is contained in:
Umed Khudoiberdiev 2017-06-16 19:29:00 +05:00
parent 003e770315
commit e1c08a2410
28 changed files with 367 additions and 338 deletions

View File

@ -49,10 +49,11 @@ More env variable names you can find in `ConnectionOptionsEnvReader` class.
* `nativeInterface` has been removed from a driver interface and implementations.
Now
* now typeorm works with the latest version of mssql (version 4)
* fixed how orm creates default values for SqlServer - now it creates constraints for it as well
### DEPRECATIONS
* `Embedded` decorator is deprecated now. use `@Column(type => SomeEmbedded)` instead now
* `Embedded` decorator is deprecated now. use `@Column(type => SomeEmbedded)` instead
### NEW FEATURES
@ -76,6 +77,7 @@ Now
* fixes [#285](https://github.com/typeorm/typeorm/issues/285) - issue when cli commands rise `CannotCloseNotConnectedError`
* fixes [#309](https://github.com/typeorm/typeorm/issues/309) - issue when `andHaving` didn't work without calling `having` on `QueryBuilder`
* fixes issues with default value being updated by schema sync
# 0.0.10

View File

@ -1,7 +1,7 @@
{
"name": "typeorm",
"private": true,
"version": "0.1.0-alpha.9",
"version": "0.1.0-alpha.10",
"description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL, MongoDB databases.",
"license": "MIT",
"readmeFilename": "README.md",

View File

@ -3,28 +3,36 @@ import {createConnection, ConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
const options: ConnectionOptions = {
type: "oracle",
host: "localhost",
username: "system",
password: "oracle",
port: 1521,
sid: "xe.oracle.docker",
// type: "oracle",
// host: "localhost",
// username: "system",
// password: "oracle",
// port: 1521,
// sid: "xe.oracle.docker",
// "name": "mysql",
// "type": "mysql",
// "host": "localhost",
// "port": 3306,
// "username": "test",
// "password": "test",
// "database": "test",
// type: "postgres",
// host: "localhost",
// port: 5432,
// username: "root",
// password: "admin",
// database: "test"
// type: "mssql",
// host: "192.168.1.10",
// username: "sa",
// password: "admin12345",
// username: "test",
// password: "test",
// database: "test",
// port: 1521
"type": "mssql",
"host": "192.168.1.6",
"username": "sa",
"password": "admin12345",
"database": "test",
// port: 1521,
// type: "sqlite",
// storage: "temp/sqlitedb.db"
// database: "temp/sqlitedb.db",
logging: {
logQueries: true,
logFailedQueryError: false,
logSchemaCreation: true
},
autoSchemaSync: true,

View File

@ -322,7 +322,9 @@ export class Connection {
return result;
} catch (err) {
await usedQueryRunner.rollbackTransaction();
try { // we throw original error even if rollback thrown an error
await usedQueryRunner.rollbackTransaction();
} catch (rollbackError) { }
throw err;
} finally {

View File

@ -9,21 +9,11 @@ import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
export function UpdateDateColumn(options?: ColumnOptions): Function {
return function (object: Object, propertyName: string) {
// const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options) options = {} as ColumnOptions;
// implicitly set a type, because this column's type cannot be anything else except date
// options = Object.assign({ type: Date } as ColumnOptions, options);
// create and register a new column metadata
const args: ColumnMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
// propertyType: reflectedType,
mode: "updateDate",
options: options
options: options ? options : {}
};
getMetadataArgsStorage().columns.push(args);
};

View File

@ -78,4 +78,9 @@ export interface Driver {
*/
normalizeType(column: ColumnMetadata): string;
/**
* Normalizes "default" value of the column.
*/
normalizeDefault(column: ColumnMetadata): string;
}

View File

@ -42,7 +42,9 @@ export class MongoDriver implements Driver {
*/
mappedDataTypes: MappedColumnTypes = {
createDate: "int",
createDateDefault: "",
updateDate: "int",
updateDateDefault: "",
version: "int",
treeLevel: "int",
migrationName: "int",
@ -173,6 +175,13 @@ export class MongoDriver implements Driver {
throw new Error(`MongoDB is schema-less, not supported by this driver.`);
}
/**
* Normalizes "default" value of the column.
*/
normalizeDefault(column: ColumnMetadata): string {
throw new Error(`MongoDB is schema-less, not supported by this driver.`);
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------

View File

@ -542,34 +542,14 @@ export class MongoQueryRunner implements QueryRunner {
/**
* Drops column in the table.
*/
async dropColumn(collectionName: string, columnName: string): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchema: TableSchema, column: ColumnSchema): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchemaOrName: TableSchema|string, columnSchemaOrName: ColumnSchema|string): Promise<void> {
async dropColumn(table: TableSchema, column: ColumnSchema): Promise<void> {
throw new Error(`Schema update queries are not supported by MongoDB driver.`);
}
/**
* Drops the columns in the table.
*/
async dropColumns(collectionName: string, columnNames: string[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchema: TableSchema, columns: ColumnSchema[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchemaOrName: TableSchema|string, columnSchemasOrNames: ColumnSchema[]|string[]): Promise<void> {
async dropColumns(table: TableSchema, columns: ColumnSchema[]): Promise<void> {
throw new Error(`Schema update queries are not supported by MongoDB driver.`);
}

View File

@ -86,11 +86,13 @@ export class MysqlDriver implements Driver {
*/
mappedDataTypes: MappedColumnTypes = {
createDate: "datetime",
createDateDefault: "CURRENT_TIMESTAMP",
updateDate: "datetime",
updateDateDefault: "CURRENT_TIMESTAMP",
version: "int",
treeLevel: "int",
migrationName: "varchar",
migrationTimestamp: "timestamp",
migrationTimestamp: "timestamp"
};
// -------------------------------------------------------------------------
@ -284,6 +286,10 @@ export class MysqlDriver implements Driver {
type += column.type;
}
// normalize shortcuts
if (type === "integer")
type = "int";
if (column.length) {
type += "(" + column.length + ")";
@ -322,6 +328,27 @@ export class MysqlDriver implements Driver {
return type;
}
/**
* Normalizes "default" value of the column.
*/
normalizeDefault(column: ColumnMetadata): string {
if (typeof column.default === "number") {
return "" + column.default;
} else if (typeof column.default === "boolean") {
return column.default === true ? "1" : "0";
} else if (typeof column.default === "function") {
return column.default();
} else if (typeof column.default === "string") {
return `'${column.default}'`;
} else {
return column.default;
}
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------

View File

@ -111,7 +111,9 @@ export class MysqlQueryRunner implements QueryRunner {
await this.commitTransaction();
} catch (error) {
await this.rollbackTransaction();
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
}
@ -503,20 +505,18 @@ export class MysqlQueryRunner implements QueryRunner {
/**
* Drops column in the table.
*/
async dropColumn(tableSchemaOrName: TableSchema|string, columnSchemaOrName: ColumnSchema|string): Promise<void> {
const tableName = tableSchemaOrName instanceof TableSchema ? tableSchemaOrName.name : tableSchemaOrName;
const columnName = columnSchemaOrName instanceof ColumnSchema ? columnSchemaOrName.name : columnSchemaOrName;
return this.query(`ALTER TABLE \`${tableName}\` DROP \`${columnName}\``);
async dropColumn(table: TableSchema, column: ColumnSchema): Promise<void> {
return this.query(`ALTER TABLE \`${table.name}\` DROP \`${column.name}\``);
}
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchemaOrName: TableSchema|string, columnSchemasOrNames: ColumnSchema[]|string[]): Promise<void> {
async dropColumns(table: TableSchema, columns: ColumnSchema[]): Promise<void> {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();
const dropPromises = (columnSchemasOrNames as any[]).map(column => this.dropColumn(tableSchemaOrName as any, column as any));
const dropPromises = columns.map(column => this.dropColumn(table, column));
await Promise.all(dropPromises);
}
@ -648,17 +648,7 @@ export class MysqlQueryRunner implements QueryRunner {
if (column.comment)
c += " COMMENT '" + column.comment + "'";
if (column.default !== undefined && column.default !== null) { // todo: same code in all drivers. make it DRY
if (typeof column.default === "number") {
c += " DEFAULT " + column.default + "";
} else if (typeof column.default === "boolean") {
c += " DEFAULT " + (column.default === true ? "TRUE" : "FALSE") + "";
} else if (typeof column.default === "function") {
c += " DEFAULT " + column.default() + "";
} else if (typeof column.default === "string") {
c += " DEFAULT '" + column.default + "'";
} else {
c += " DEFAULT " + column.default + "";
}
c += " DEFAULT " + column.default;
}
return c;
}

View File

@ -91,7 +91,9 @@ export class OracleDriver implements Driver {
*/
mappedDataTypes: MappedColumnTypes = {
createDate: "datetime",
createDateDefault: "CURRENT_TIMESTAMP",
updateDate: "datetime",
updateDateDefault: "CURRENT_TIMESTAMP",
version: "number",
treeLevel: "number",
migrationName: "varchar",
@ -318,6 +320,27 @@ export class OracleDriver implements Driver {
return type;
}
/**
* Normalizes "default" value of the column.
*/
normalizeDefault(column: ColumnMetadata): string {
if (typeof column.default === "number") {
return "" + column.default;
} else if (typeof column.default === "boolean") {
return column.default === true ? "true" : "false";
} else if (typeof column.default === "function") {
return column.default();
} else if (typeof column.default === "string") {
return `'${column.default}'`;
} else {
return column.default;
}
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------

View File

@ -119,7 +119,9 @@ export class OracleQueryRunner implements QueryRunner {
await this.commitTransaction();
} catch (error) {
await this.rollbackTransaction();
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
@ -614,40 +616,18 @@ AND cons.constraint_name = cols.constraint_name AND cons.owner = cols.owner ORDE
/**
* Drops column in the table.
*/
async dropColumn(tableName: string, columnName: string): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchema: TableSchema, column: ColumnSchema): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchemaOrName: TableSchema|string, columnSchemaOrName: ColumnSchema|string): Promise<void> {
const tableName = tableSchemaOrName instanceof TableSchema ? tableSchemaOrName.name : tableSchemaOrName;
const columnName = columnSchemaOrName instanceof ColumnSchema ? columnSchemaOrName.name : columnSchemaOrName;
return this.query(`ALTER TABLE "${tableName}" DROP COLUMN "${columnName}"`);
async dropColumn(table: TableSchema, column: ColumnSchema): Promise<void> {
return this.query(`ALTER TABLE "${table.name}" DROP COLUMN "${column.name}"`);
}
/**
* Drops the columns in the table.
*/
async dropColumns(tableName: string, columnNames: string[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchema: TableSchema, columns: ColumnSchema[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchemaOrName: TableSchema|string, columnSchemasOrNames: ColumnSchema[]|string[]): Promise<void> {
async dropColumns(table: TableSchema, columns: ColumnSchema[]): Promise<void> {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();
const dropPromises = (columnSchemasOrNames as any[]).map(column => this.dropColumn(tableSchemaOrName as any, column as any));
const dropPromises = columns.map(column => this.dropColumn(table, column));
await Promise.all(dropPromises);
}
@ -819,17 +799,7 @@ AND cons.constraint_name = cols.constraint_name AND cons.owner = cols.owner ORDE
// if (column.comment) // todo: less priority, fix it later
// c += " COMMENT '" + column.comment + "'";
if (column.default !== undefined && column.default !== null) { // todo: same code in all drivers. make it DRY
if (typeof column.default === "number") {
c += " DEFAULT " + column.default + "";
} else if (typeof column.default === "boolean") {
c += " DEFAULT " + (column.default === true ? "TRUE" : "FALSE") + "";
} else if (typeof column.default === "function") {
c += " DEFAULT " + column.default() + "";
} else if (typeof column.default === "string") {
c += " DEFAULT '" + column.default + "'";
} else {
c += " DEFAULT " + column.default + "";
}
c += " DEFAULT " + column.default;
}
return c;

View File

@ -111,7 +111,9 @@ export class PostgresDriver implements Driver {
*/
mappedDataTypes: MappedColumnTypes = {
createDate: "timestamp",
createDateDefault: "now()",
updateDate: "timestamp",
updateDateDefault: "now()",
version: "int",
treeLevel: "int",
migrationName: "varchar",
@ -325,6 +327,15 @@ export class PostgresDriver implements Driver {
} else {
type += column.type;
}
// normalize shortcuts
if (type === "int") {
type = "integer";
} else if (type === "timestamp") {
type = "timestamp without time zone";
}
if (column.length) {
type += "(" + column.length + ")";
@ -340,6 +351,27 @@ export class PostgresDriver implements Driver {
return type;
}
/**
* Normalizes "default" value of the column.
*/
normalizeDefault(column: ColumnMetadata): string {
if (typeof column.default === "number") {
return "" + column.default;
} else if (typeof column.default === "boolean") {
return column.default === true ? "true" : "false";
} else if (typeof column.default === "function") {
return column.default();
} else if (typeof column.default === "string") {
return `'${column.default}'`;
} else {
return column.default;
}
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------

View File

@ -74,6 +74,8 @@ export class PostgresQueryRunner implements QueryRunner {
this.databaseConnectionPromise = new Promise((ok, fail) => {
this.driver.pool.connect((err: any, connection: any, release: Function) => {
if (err) return fail(err);
this.driver.connectedQueryRunners.push(this);
this.databaseConnection = connection;
this.releaseCallback = release;
@ -125,7 +127,9 @@ export class PostgresQueryRunner implements QueryRunner {
await this.commitTransaction();
} catch (error) {
await this.rollbackTransaction();
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
}
@ -340,7 +344,7 @@ where constraint_type = 'PRIMARY KEY' AND c.table_schema = '${this.schemaName}'
const columnSchema = new ColumnSchema();
columnSchema.name = dbColumn["column_name"];
columnSchema.type = columnType;
columnSchema.default = dbColumn["column_default"] !== null && dbColumn["column_default"] !== undefined ? dbColumn["column_default"] : undefined;
columnSchema.default = dbColumn["column_default"] !== null && dbColumn["column_default"] !== undefined ? dbColumn["column_default"].replace(/::character varying/, "") : undefined;
columnSchema.isNullable = dbColumn["is_nullable"] === "YES";
// columnSchema.isPrimary = dbColumn["column_key"].indexOf("PRI") !== -1;
columnSchema.isGenerated = isGenerated;
@ -608,6 +612,16 @@ where constraint_type = 'PRIMARY KEY' AND c.table_schema = '${this.schemaName}'
}
}
if (newColumn.default !== oldColumn.default) {
if (newColumn.default !== null && newColumn.default !== undefined) {
await this.query(`ALTER TABLE "${tableSchema.name}" ALTER COLUMN "${newColumn.name}" SET DEFAULT ${newColumn.default}`);
} else if (oldColumn.default !== null && oldColumn.default !== undefined) {
await this.query(`ALTER TABLE "${tableSchema.name}" ALTER COLUMN "${newColumn.name}" DROP DEFAULT`);
}
}
}
/**
@ -627,40 +641,18 @@ where constraint_type = 'PRIMARY KEY' AND c.table_schema = '${this.schemaName}'
/**
* Drops column in the table.
*/
async dropColumn(tableName: string, columnName: string): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchema: TableSchema, column: ColumnSchema): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchemaOrName: TableSchema|string, columnSchemaOrName: ColumnSchema|string): Promise<void> {
const tableName = tableSchemaOrName instanceof TableSchema ? tableSchemaOrName.name : tableSchemaOrName;
const columnName = columnSchemaOrName instanceof ColumnSchema ? columnSchemaOrName.name : columnSchemaOrName;
return this.query(`ALTER TABLE "${tableName}" DROP "${columnName}"`);
async dropColumn(table: TableSchema, column: ColumnSchema): Promise<void> {
return this.query(`ALTER TABLE "${table.name}" DROP "${column.name}"`);
}
/**
* Drops the columns in the table.
*/
async dropColumns(tableName: string, columnNames: string[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchema: TableSchema, columns: ColumnSchema[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchemaOrName: TableSchema|string, columnSchemasOrNames: ColumnSchema[]|string[]): Promise<void> {
async dropColumns(table: TableSchema, columns: ColumnSchema[]): Promise<void> {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();
const dropPromises = (columnSchemasOrNames as any[]).map(column => this.dropColumn(tableSchemaOrName as any, column as any));
const dropPromises = columns.map(column => this.dropColumn(table, column));
await Promise.all(dropPromises);
}
@ -840,17 +832,7 @@ where constraint_type = 'PRIMARY KEY' AND c.table_schema = '${this.schemaName}'
if (column.isGenerated)
c += " PRIMARY KEY";
if (column.default !== undefined && column.default !== null) { // todo: same code in all drivers. make it DRY
if (typeof column.default === "number") {
c += " DEFAULT " + column.default + "";
} else if (typeof column.default === "boolean") {
c += " DEFAULT " + (column.default === true ? "TRUE" : "FALSE") + "";
} else if (typeof column.default === "function") {
c += " DEFAULT " + column.default() + "";
} else if (typeof column.default === "string") {
c += " DEFAULT '" + column.default + "'";
} else {
c += " DEFAULT " + column.default + "";
}
c += " DEFAULT " + column.default;
}
if (column.isGenerated && column.type === "uuid" && !column.default)
c += " DEFAULT uuid_generate_v4()";

View File

@ -95,7 +95,9 @@ export class SqliteDriver implements Driver {
*/
mappedDataTypes: MappedColumnTypes = {
createDate: "datetime",
createDateDefault: "datetime('now')",
updateDate: "datetime",
updateDateDefault: "datetime('now')",
version: "integer",
treeLevel: "integer",
migrationName: "varchar",
@ -306,6 +308,27 @@ export class SqliteDriver implements Driver {
return type;
}
/**
* Normalizes "default" value of the column.
*/
normalizeDefault(column: ColumnMetadata): string {
if (typeof column.default === "number") {
return "" + column.default;
} else if (typeof column.default === "boolean") {
return column.default === true ? "1" : "0";
} else if (typeof column.default === "function") {
return column.default();
} else if (typeof column.default === "string") {
return `'${column.default}'`;
} else {
return column.default;
}
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------

View File

@ -92,7 +92,9 @@ export class SqliteQueryRunner implements QueryRunner {
await this.commitTransaction();
} catch (error) {
await this.rollbackTransaction();
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
} finally {
@ -581,51 +583,18 @@ export class SqliteQueryRunner implements QueryRunner {
/**
* Drops column in the table.
*/
async dropColumn(tableName: string, columnName: string): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchema: TableSchema, column: ColumnSchema): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchemaOrName: TableSchema|string, columnSchemaOrName: ColumnSchema|string): Promise<void> {
return this.dropColumns(tableSchemaOrName as any, [columnSchemaOrName as any]);
async dropColumn(table: TableSchema, column: ColumnSchema): Promise<void> {
return this.dropColumns(table, [column]);
}
/**
* Drops the columns in the table.
*/
async dropColumns(tableName: string, columnNames: string[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchema: TableSchema, columns: ColumnSchema[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchemaOrName: TableSchema|string, columnSchemasOrNames: ColumnSchema[]|string[]): Promise<void> {
async dropColumns(table: TableSchema, columns: ColumnSchema[]): Promise<void> {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();
const tableSchema = await this.getTableSchema(tableSchemaOrName);
const updatingTableSchema = tableSchema.clone();
const columns = (columnSchemasOrNames as any[]).map(columnSchemasOrName => {
if (typeof columnSchemasOrName === "string") {
const column = tableSchema.columns.find(column => column.name === columnSchemasOrName);
if (!column)
throw new Error(`Cannot drop a column - column "${columnSchemasOrName}" was not found in the "${tableSchema.name}" table.`);
return column;
} else {
return columnSchemasOrName as ColumnSchema;
}
});
const updatingTableSchema = table.clone();
updatingTableSchema.removeColumns(columns);
return this.recreateTable(updatingTableSchema);
}
@ -785,17 +754,7 @@ export class SqliteQueryRunner implements QueryRunner {
c += " PRIMARY KEY AUTOINCREMENT";
if (column.default !== undefined && column.default !== null) { // todo: same code in all drivers. make it DRY
if (typeof column.default === "number") {
c += " DEFAULT " + column.default + "";
} else if (typeof column.default === "boolean") {
c += " DEFAULT " + (column.default === true ? "1" : "0") + "";
} else if (typeof column.default === "function") {
c += " DEFAULT " + column.default() + "";
} else if (typeof column.default === "string") {
c += " DEFAULT '" + column.default + "'";
} else {
c += " DEFAULT " + column.default + "";
}
c += " DEFAULT (" + column.default + ")";
}
return c;

View File

@ -94,7 +94,9 @@ export class SqlServerDriver implements Driver {
*/
mappedDataTypes: MappedColumnTypes = {
createDate: "datetime",
createDateDefault: "getdate()",
updateDate: "datetime",
updateDateDefault: "getdate()",
version: "int",
treeLevel: "int",
migrationName: "varchar",
@ -308,6 +310,11 @@ export class SqlServerDriver implements Driver {
} else {
type += column.type;
}
// make sure aliases to have original type names
if (type === "integer")
type = "int";
if (column.length) {
type += "(" + column.length + ")";
@ -328,6 +335,27 @@ export class SqlServerDriver implements Driver {
return type;
}
/**
* Normalizes "default" value of the column.
*/
normalizeDefault(column: ColumnMetadata): string {
if (typeof column.default === "number") {
return "" + column.default;
} else if (typeof column.default === "boolean") {
return column.default === true ? "1" : "0";
} else if (typeof column.default === "function") {
return "(" + column.default() + ")";
} else if (typeof column.default === "string") {
return `'${column.default}'`;
} else {
return column.default;
}
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------

View File

@ -104,7 +104,9 @@ export class SqlServerQueryRunner implements QueryRunner {
await this.commitTransaction();
} catch (error) {
await this.rollbackTransaction();
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
}
@ -212,7 +214,7 @@ export class SqlServerQueryRunner implements QueryRunner {
let waitingPromiseIndex = this.queryResponsibilityChain.indexOf(waitingPromise);
if (err) {
this.driver.connection.logger.logFailedQuery(query, parameters);
this.driver.connection.logger.logQueryError(err);
this.driver.connection.logger.logQueryError((err.originalError && err.originalError.info) ? err.originalError.info.message : err);
resolveChain();
return fail(err);
}
@ -363,7 +365,6 @@ export class SqlServerQueryRunner implements QueryRunner {
tableSchema.columns = dbColumns
.filter(dbColumn => dbColumn["TABLE_NAME"] === tableSchema.name)
.map(dbColumn => {
const isPrimary = !!dbConstraints.find(dbConstraint => {
return dbConstraint["TABLE_NAME"] === tableSchema.name &&
dbConstraint["COLUMN_NAME"] === dbColumn["COLUMN_NAME"] &&
@ -446,7 +447,7 @@ export class SqlServerQueryRunner implements QueryRunner {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();
const columnDefinitions = table.columns.map(column => this.buildCreateColumnSql(column, false)).join(", ");
const columnDefinitions = table.columns.map(column => this.buildCreateColumnSql(column, false, true)).join(", ");
let sql = `CREATE TABLE "${table.name}" (${columnDefinitions}`;
sql += table.columns
.filter(column => column.isUnique)
@ -476,16 +477,6 @@ export class SqlServerQueryRunner implements QueryRunner {
return result.length ? true : false;
}
/**
* Creates a new column from the column schema in the table.
*/
async addColumn(tableName: string, column: ColumnSchema): Promise<void>;
/**
* Creates a new column from the column schema in the table.
*/
async addColumn(tableSchema: TableSchema, column: ColumnSchema): Promise<void>;
/**
* Creates a new column from the column schema in the table.
*/
@ -494,7 +485,7 @@ export class SqlServerQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
const tableName = tableSchemaOrName instanceof TableSchema ? tableSchemaOrName.name : tableSchemaOrName;
const sql = `ALTER TABLE "${tableName}" ADD ${this.buildCreateColumnSql(column)}`;
const sql = `ALTER TABLE "${tableName}" ADD ${this.buildCreateColumnSql(column, false, true)}`;
return this.query(sql);
}
@ -605,10 +596,10 @@ export class SqlServerQueryRunner implements QueryRunner {
// to update an identy column we have to drop column and recreate it again
if (newColumn.isGenerated !== oldColumn.isGenerated) {
await this.query(`ALTER TABLE "${tableSchema.name}" DROP COLUMN "${newColumn.name}"`);
await this.query(`ALTER TABLE "${tableSchema.name}" ADD ${this.buildCreateColumnSql(newColumn)}`);
await this.query(`ALTER TABLE "${tableSchema.name}" ADD ${this.buildCreateColumnSql(newColumn, false, false)}`);
}
const sql = `ALTER TABLE "${tableSchema.name}" ALTER COLUMN ${this.buildCreateColumnSql(newColumn, true)}`; // todo: CHANGE OR MODIFY COLUMN ????
const sql = `ALTER TABLE "${tableSchema.name}" ALTER COLUMN ${this.buildCreateColumnSql(newColumn, true, false)}`; // todo: CHANGE OR MODIFY COLUMN ????
await this.query(sql);
if (newColumn.isUnique !== oldColumn.isUnique) {
@ -620,6 +611,16 @@ export class SqlServerQueryRunner implements QueryRunner {
}
}
if (newColumn.default !== oldColumn.default) {
if (newColumn.default !== null && newColumn.default !== undefined) {
await this.query(`ALTER TABLE "${tableSchema.name}" DROP CONSTRAINT "df_${newColumn.name}"`);
await this.query(`ALTER TABLE "${tableSchema.name}" ADD CONSTRAINT "df_${newColumn.name}" DEFAULT ${newColumn.default} FOR "${newColumn.name}"`);
} else if (oldColumn.default !== null && oldColumn.default !== undefined) {
await this.query(`ALTER TABLE "${tableSchema.name}" DROP CONSTRAINT "df_${newColumn.name}"`);
}
}
}
/**
@ -639,40 +640,24 @@ export class SqlServerQueryRunner implements QueryRunner {
/**
* Drops column in the table.
*/
async dropColumn(tableName: string, columnName: string): Promise<void>;
async dropColumn(table: TableSchema, column: ColumnSchema): Promise<void> {
/**
* Drops column in the table.
*/
async dropColumn(tableSchema: TableSchema, column: ColumnSchema): Promise<void>;
// drop depend constraints
if (column.default)
await this.query(`ALTER TABLE "${table.name}" DROP CONSTRAINT "df_${column.name}"`);
/**
* Drops column in the table.
*/
async dropColumn(tableSchemaOrName: TableSchema|string, columnSchemaOrName: ColumnSchema|string): Promise<void> {
const tableName = tableSchemaOrName instanceof TableSchema ? tableSchemaOrName.name : tableSchemaOrName;
const columnName = columnSchemaOrName instanceof ColumnSchema ? columnSchemaOrName.name : columnSchemaOrName;
return this.query(`ALTER TABLE "${tableName}" DROP COLUMN "${columnName}"`);
// drop column itself
await this.query(`ALTER TABLE "${table.name}" DROP COLUMN "${column.name}"`);
}
/**
* Drops the columns in the table.
*/
async dropColumns(tableName: string, columnNames: string[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchema: TableSchema, columns: ColumnSchema[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchemaOrName: TableSchema|string, columnSchemasOrNames: ColumnSchema[]|string[]): Promise<void> {
async dropColumns(table: TableSchema, columns: ColumnSchema[]): Promise<void> {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();
const dropPromises = (columnSchemasOrNames as any[]).map(column => this.dropColumn(tableSchemaOrName as any, column as any));
const dropPromises = columns.map(column => this.dropColumn(table, column));
await Promise.all(dropPromises);
}
@ -840,7 +825,7 @@ WHERE columnUsages.TABLE_CATALOG = '${this.dbName}' AND tableConstraints.TABLE_C
/**
* Builds a query for create column.
*/
protected buildCreateColumnSql(column: ColumnSchema, skipIdentity: boolean = false) {
protected buildCreateColumnSql(column: ColumnSchema, skipIdentity: boolean, createDefault: boolean) {
let c = `"${column.name}" ${column.type}`;
if (column.isNullable !== true)
c += " NOT NULL";
@ -850,17 +835,9 @@ WHERE columnUsages.TABLE_CATALOG = '${this.dbName}' AND tableConstraints.TABLE_C
// c += " PRIMARY KEY";
if (column.comment)
c += " COMMENT '" + column.comment + "'";
if (column.default !== undefined && column.default !== null) { // todo: same code in all drivers. make it DRY
if (typeof column.default === "number") {
c += " DEFAULT " + column.default + "";
} else if (typeof column.default === "boolean") {
c += " DEFAULT " + (column.default === true ? "1" : "0") + "";
} else if (typeof column.default === "function") {
c += " DEFAULT " + column.default() + "";
} else if (typeof column.default === "string") {
c += " DEFAULT '" + column.default + "'";
} else {
c += " DEFAULT " + column.default + "";
if (createDefault) {
if (column.default !== undefined && column.default !== null) {
c += ` CONSTRAINT "df_${column.name}" DEFAULT ${column.default}`;
}
}
return c;

View File

@ -11,11 +11,21 @@ export interface MappedColumnTypes {
*/
createDate: ColumnType;
/**
* Default value should be used by a database for "created date" column.
*/
createDateDefault: string;
/**
* Column type for the update date column.
*/
updateDate: ColumnType;
/**
* Default value should be used by a database for "updated date" column.
*/
updateDateDefault: string;
/**
* Column type for the version column.
*/

View File

@ -78,7 +78,9 @@ export class WebsqlDriver implements Driver {
*/
mappedDataTypes: MappedColumnTypes = {
createDate: "datetime",
createDateDefault: "DATETIME()",
updateDate: "datetime",
updateDateDefault: "DATETIME()",
version: "number",
treeLevel: "number",
migrationName: "varchar",
@ -279,4 +281,25 @@ export class WebsqlDriver implements Driver {
return type;
}
/**
* Normalizes "default" value of the column.
*/
normalizeDefault(column: ColumnMetadata): string {
if (typeof column.default === "number") {
return "" + column.default;
} else if (typeof column.default === "boolean") {
return column.default === true ? "1" : "0";
} else if (typeof column.default === "function") {
return column.default();
} else if (typeof column.default === "string") {
return `'${column.default}'`;
} else {
return column.default;
}
}
}

View File

@ -114,7 +114,9 @@ export class WebsqlQueryRunner implements QueryRunner {
await this.commitTransaction();
} catch (error) {
await this.rollbackTransaction();
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
// await this.query(`PRAGMA foreign_keys = ON;`);
@ -610,51 +612,18 @@ export class WebsqlQueryRunner implements QueryRunner {
/**
* Drops column in the table.
*/
async dropColumn(tableName: string, columnName: string): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchema: TableSchema, column: ColumnSchema): Promise<void>;
/**
* Drops column in the table.
*/
async dropColumn(tableSchemaOrName: TableSchema|string, columnSchemaOrName: ColumnSchema|string): Promise<void> {
return this.dropColumns(tableSchemaOrName as any, [columnSchemaOrName as any]);
async dropColumn(table: TableSchema, column: ColumnSchema): Promise<void> {
return this.dropColumns(table, [column]);
}
/**
* Drops the columns in the table.
*/
async dropColumns(tableName: string, columnNames: string[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchema: TableSchema, columns: ColumnSchema[]): Promise<void>;
/**
* Drops the columns in the table.
*/
async dropColumns(tableSchemaOrName: TableSchema|string, columnSchemasOrNames: ColumnSchema[]|string[]): Promise<void> {
async dropColumns(table: TableSchema, columns: ColumnSchema[]): Promise<void> {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();
const tableSchema = await this.getTableSchema(tableSchemaOrName);
const updatingTableSchema = tableSchema.clone();
const columns = (columnSchemasOrNames as any[]).map(columnSchemasOrName => {
if (typeof columnSchemasOrName === "string") {
const column = tableSchema.columns.find(column => column.name === columnSchemasOrName);
if (!column)
throw new Error(`Cannot drop a column - column "${columnSchemasOrName}" was not found in the "${tableSchema.name}" table.`);
return column;
} else {
return columnSchemasOrName as ColumnSchema;
}
});
const updatingTableSchema = table.clone();
updatingTableSchema.removeColumns(columns);
return this.recreateTable(updatingTableSchema);
}
@ -812,19 +781,8 @@ export class WebsqlQueryRunner implements QueryRunner {
c += " UNIQUE";
if (column.isGenerated === true) // don't use skipPrimary here since updates can update already exist primary without auto inc.
c += " PRIMARY KEY AUTOINCREMENT";
if (column.default !== undefined && column.default !== null) { // todo: same code in all drivers. make it DRY
if (typeof column.default === "number") {
c += " DEFAULT " + column.default + "";
} else if (typeof column.default === "boolean") {
c += " DEFAULT " + (column.default === true ? "TRUE" : "FALSE") + "";
} else if (typeof column.default === "function") {
c += " DEFAULT " + column.default() + "";
} else if (typeof column.default === "string") {
c += " DEFAULT '" + column.default + "'";
} else {
c += " DEFAULT " + column.default + "";
}
}
if (column.default !== undefined && column.default !== null) // todo: same code in all drivers. make it DRY
c += ` DEFAULT ${column.default}`;
return c;
}

View File

@ -221,10 +221,16 @@ export class ColumnMetadata {
}
if (this.isTreeLevel)
this.type = options.connection.driver.mappedDataTypes.treeLevel;
if (this.isCreateDate)
if (this.isCreateDate) {
this.type = options.connection.driver.mappedDataTypes.createDate;
if (this.isUpdateDate)
if (!this.default)
this.default = () => options.connection.driver.mappedDataTypes.createDateDefault;
}
if (this.isUpdateDate) {
this.type = options.connection.driver.mappedDataTypes.updateDate;
if (!this.default)
this.default = () => options.connection.driver.mappedDataTypes.updateDateDefault;
}
if (this.isVersion)
this.type = options.connection.driver.mappedDataTypes.version;
}

View File

@ -100,8 +100,11 @@ export class MigrationExecutor {
await this.queryRunner.commitTransaction();
} catch (err) { // rollback transaction if we started it
if (transactionStartedByUs)
await this.queryRunner.rollbackTransaction();
if (transactionStartedByUs) {
try { // we throw original error even if rollback thrown an error
await this.queryRunner.rollbackTransaction();
} catch (rollbackError) { }
}
throw err;
}
@ -161,8 +164,11 @@ export class MigrationExecutor {
await this.queryRunner.commitTransaction();
} catch (err) { // rollback transaction if we started it
if (transactionStartedByUs)
await this.queryRunner.rollbackTransaction();
if (transactionStartedByUs) {
try { // we throw original error even if rollback thrown an error
await this.queryRunner.rollbackTransaction();
} catch (rollbackError) { }
}
throw err;
}

View File

@ -135,9 +135,7 @@ export class SubjectOperationExecutor {
if (isTransactionStartedByItself) {
try {
await this.queryRunner.rollbackTransaction();
} catch (secondaryError) {
}
} catch (rollbackError) { }
}
throw error;

View File

@ -166,26 +166,11 @@ export interface QueryRunner {
*/
changeColumns(table: TableSchema, changedColumns: { oldColumn: ColumnSchema, newColumn: ColumnSchema }[]): Promise<void>;
/**
* Drops the column in the table.
*/
dropColumn(tableName: string, columnName: string): Promise<void>;
/**
* Drops the column in the table.
*/
dropColumn(tableName: string, columnName: string): Promise<void>;
/**
* Drops the column in the table.
*/
dropColumn(table: TableSchema, column: ColumnSchema): Promise<void>;
/**
* Drops the columns in the table.
*/
dropColumns(tableName: string, columnNames: string[]): Promise<void>;
/**
* Drops the columns in the table.
*/

View File

@ -74,7 +74,10 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
await this.queryRunner.commitTransaction();
} catch (error) {
await this.queryRunner.rollbackTransaction();
try { // we throw original error even if rollback thrown an error
await this.queryRunner.rollbackTransaction();
} catch (rollbackError) { }
throw error;
} finally {
@ -244,7 +247,7 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
// generate a map of new/old columns
const newAndOldColumnSchemas = updatedColumnSchemas.map(changedColumnSchema => {
const columnMetadata = metadata.columns.find(column => column.databaseName === changedColumnSchema.name);
const newColumnSchema = ColumnSchema.create(columnMetadata!, this.connection.driver.normalizeType(columnMetadata!));
const newColumnSchema = ColumnSchema.create(columnMetadata!, this.connection.driver.normalizeType(columnMetadata!), this.connection.driver.normalizeDefault(columnMetadata!));
tableSchema.replaceColumn(changedColumnSchema, newColumnSchema);
return {
@ -427,7 +430,11 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
*/
protected metadataColumnsToColumnSchemas(columns: ColumnMetadata[]): ColumnSchema[] {
return columns.map(columnMetadata => {
return ColumnSchema.create(columnMetadata, this.connection.driver.normalizeType(columnMetadata));
return ColumnSchema.create(
columnMetadata,
this.connection.driver.normalizeType(columnMetadata),
this.connection.driver.normalizeDefault(columnMetadata),
);
});
}

View File

@ -102,10 +102,10 @@ export class ColumnSchema {
/**
* Creates a new column based on the given column metadata.
*/
static create(columnMetadata: ColumnMetadata, normalizedType: string): ColumnSchema {
static create(columnMetadata: ColumnMetadata, normalizedType: string, normalizedDefault: string): ColumnSchema {
const columnSchema = new ColumnSchema();
columnSchema.name = columnMetadata.databaseName;
columnSchema.default = columnMetadata.default;
columnSchema.default = normalizedDefault;
columnSchema.comment = columnMetadata.comment;
columnSchema.isGenerated = columnMetadata.isGenerated;
columnSchema.isNullable = columnMetadata.isNullable;

View File

@ -209,10 +209,18 @@ export class TableSchema {
if (!columnMetadata)
return false; // we don't need new columns, we only need exist and changed
// console.log(columnSchema.name, "!==", columnMetadata.databaseName); // ||
// console.log(columnSchema.type, "!==", driver.normalizeType(columnMetadata)); // ||
// console.log(columnSchema.comment, "!==", columnMetadata.comment); // ||
// console.log(this.compareDefaultValues(driver.normalizeDefault(columnMetadata), columnSchema.default)); // || // we included check for generated here, because generated columns already can have default values
// console.log(columnSchema.isNullable, "!==", columnMetadata.isNullable); // ||
// console.log(columnSchema.isUnique, "!==", columnMetadata.isUnique); // ||
// console.log(columnSchema.isGenerated, "!==", columnMetadata.isGenerated); // d;
return columnSchema.name !== columnMetadata.databaseName ||
columnSchema.type !== driver.normalizeType(columnMetadata) ||
columnSchema.comment !== columnMetadata.comment ||
(!columnSchema.isGenerated && !this.compareDefaultValues(columnMetadata.default, columnSchema.default)) || // we included check for generated here, because generated columns already can have default values
(!columnSchema.isGenerated && !this.compareDefaultValues(driver.normalizeDefault(columnMetadata), columnSchema.default)) || // we included check for generated here, because generated columns already can have default values
columnSchema.isNullable !== columnMetadata.isNullable ||
columnSchema.isUnique !== columnMetadata.isUnique ||
// columnSchema.isPrimary !== columnMetadata.isPrimary ||
@ -227,15 +235,36 @@ export class TableSchema {
/**
* Checks if "DEFAULT" values in the column metadata and in the database schema are equal.
*/
protected compareDefaultValues(columnMetadataValue: any, databaseValue: any): boolean {
protected compareDefaultValues(columnMetadataValue: string, databaseValue: string): boolean {
if (typeof columnMetadataValue === "number")
return columnMetadataValue === parseInt(databaseValue);
if (typeof columnMetadataValue === "boolean")
return columnMetadataValue === (!!databaseValue || databaseValue === "false");
if (typeof columnMetadataValue === "function")
return columnMetadataValue() === databaseValue;
// if (typeof columnMetadataValue === "number")
// return columnMetadataValue === parseInt(databaseValue);
// if (typeof columnMetadataValue === "boolean")
// return columnMetadataValue === (!!databaseValue || databaseValue === "false");
// if (typeof columnMetadataValue === "function")
// if (typeof columnMetadataValue === "string" && typeof databaseValue === "string")
// return columnMetadataValue.toLowerCase() === databaseValue.toLowerCase();
if (typeof columnMetadataValue === "string" && typeof databaseValue === "string") {
// we need to cut out "((x))" where x number generated by mssql
columnMetadataValue = columnMetadataValue.replace(/\(\([0-9.]*\)\)$/g, "$1");
databaseValue = databaseValue.replace(/\(\(([0-9.]*?)\)\)$/g, "$1");
// we need to cut out "(" because in mssql we can understand returned value is a string or a function
// as result compare cannot understand if default is really changed or not
columnMetadataValue = columnMetadataValue.replace(/^\(|\)$/g, "");
databaseValue = databaseValue.replace(/^\(|\)$/g, "");
// we need to cut out "'" because in mysql we can understand returned value is a string or a function
// as result compare cannot understand if default is really changed or not
columnMetadataValue = columnMetadataValue.replace(/^'+|'+$/g, "");
databaseValue = databaseValue.replace(/^'+|'+$/g, "");
}
// console.log("columnMetadataValue", columnMetadataValue);
// console.log("databaseValue", databaseValue);
return columnMetadataValue === databaseValue;
}
@ -252,7 +281,7 @@ export class TableSchema {
const tableSchema = new TableSchema(entityMetadata.tableName);
tableSchema.engine = entityMetadata.engine;
entityMetadata.columns.forEach(column => {
tableSchema.columns.push(ColumnSchema.create(column, driver.normalizeType(column)));
tableSchema.columns.push(ColumnSchema.create(column, driver.normalizeType(column), driver.normalizeDefault(column)));
});
return tableSchema;