added table schema generation

This commit is contained in:
Umed Khudoiberdiev 2016-08-27 13:25:44 +05:00
parent 6eb1cab7db
commit 3ade5cf782
13 changed files with 245 additions and 69 deletions

View File

@ -1,6 +1,6 @@
# TypeORM
[![Join the chat at https://gitter.im/pleerock/typeorm](https://badges.gitter.im/pleerock/typeorm.svg)](https://gitter.im/pleerock/typeorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Join the chat at https://gitter.im/pleerock/typeorm](https://badges.gitter.im/pleerock/typeorm.svg)](https://gitter.im/pleerock/typeorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
> Note: docs are not always up to date because orm is in active development.
> Samples are more up to date, try to find your questions there.

View File

@ -11,10 +11,10 @@
},
"repository": {
"type": "git",
"url": "https://github.com/pleerock/typeorm.git"
"url": "https://github.com/typeorm/typeorm.git"
},
"bugs": {
"url": "https://github.com/pleerock/typeorm/issues"
"url": "https://github.com/typeorm/typeorm/issues"
},
"tags": [
"orm",

View File

@ -11,6 +11,9 @@ const options: ConnectionOptions = {
password: "admin",
database: "test"
},
logging: {
logOnlyFailedQueries: true
},
autoSchemaCreate: true,
entities: [Post]
};

View File

@ -7,6 +7,12 @@ import {IndexMetadata} from "../metadata/IndexMetadata";
import {DatabaseConnection} from "../driver/DatabaseConnection";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {DataTypeNotSupportedByDriverError} from "./error/DataTypeNotSupportedByDriverError";
import {TableSchema} from "../schema-creator/TableSchema";
import {ColumnSchema} from "../schema-creator/ColumnSchema";
import {PrimaryKeySchema} from "../schema-creator/PrimaryKeySchema";
import {ForeignKeySchema} from "../schema-creator/ForeignKeySchema";
import {IndexSchema} from "../schema-creator/IndexSchema";
import {UniqueKeySchema} from "../schema-creator/UniqueKeySchema";
/**
* @internal
@ -18,6 +24,84 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
super();
}
async loadSchemaTables(tableNames: string[]): Promise<TableSchema[]> {
// if no tables given then no need to proceed
if (!tableNames)
return [];
// load tables, columns, indices and foreign keys
const tablesString = tableNames.map(tableName => "'" + tableName + "'").join(",");
const tablesSql = `SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '${this.dbName}' AND TABLE_NAME IN (${tablesString})`;
const columnsSql = `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '${this.dbName}' AND TABLE_NAME IN (${tablesString})`;
const indicesSql = `SELECT * FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = '${this.dbName}' AND TABLE_NAME IN (${tablesString})`;
const foreignKeysSql = `SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = '${this.dbName}' AND REFERENCED_COLUMN_NAME IS NOT NULL`;
const uniqueKeysSql = `SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = '${this.dbName}' AND CONSTRAINT_TYPE = 'UNIQUE'`;
const primaryKeysSql = `SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = '${this.dbName}' AND CONSTRAINT_TYPE = 'PRIMARY KEY'`;
const [dbTables, dbColumns, dbIndices, dbForeignKeys, dbUniqueKeys, primaryKeys]: ObjectLiteral[][] = await Promise.all([
this.query(tablesSql),
this.query(columnsSql),
this.query(indicesSql),
this.query(foreignKeysSql),
this.query(uniqueKeysSql),
this.query(primaryKeysSql),
]);
// if tables were not found in the db, no need to proceed
if (!dbTables.length)
return [];
// create table schemas for loaded tables
return dbTables.map(dbTable => {
const tableSchema = new TableSchema(dbTable["TABLE_NAME"]);
// create column schemas from the loaded columns
tableSchema.columns = dbColumns
.filter(dbColumn => dbColumn["TABLE_NAME"] === tableSchema.name)
.map(dbColumn => {
const columnSchema = new ColumnSchema();
columnSchema.name = dbColumn["COLUMN_NAME"];
columnSchema.type = dbColumn["COLUMN_TYPE"].toLowerCase(); // todo: use normalize type?
columnSchema.default = dbColumn["COLUMN_DEFAULT"];
columnSchema.isNullable = dbColumn["IS_NULLABLE"] === "YES";
columnSchema.isPrimary = dbColumn["COLUMN_KEY"].indexOf("PRI") !== -1;
columnSchema.isGenerated = dbColumn["EXTRA"].indexOf("auto_increment") !== -1;
columnSchema.comment = dbColumn["COLUMN_COMMENT"];
return columnSchema;
});
// create primary key schema
const primaryKey = primaryKeys.find(primaryKey => primaryKey["TABLE_NAME"] === tableSchema.name);
if (primaryKey)
tableSchema.primaryKey = new PrimaryKeySchema(primaryKey["CONSTRAINT_NAME"]);
// create foreign key schemas from the loaded indices
tableSchema.foreignKeys = dbForeignKeys
.filter(dbForeignKey => dbForeignKey["TABLE_NAME"] === tableSchema.name)
.map(dbForeignKey => new ForeignKeySchema(dbForeignKey["CONSTRAINT_NAME"]));
// create unique key schemas from the loaded indices
tableSchema.uniqueKeys = dbUniqueKeys
.filter(dbUniqueKey => dbUniqueKey["TABLE_NAME"] === tableSchema.name)
.map(dbUniqueKey => new UniqueKeySchema(dbUniqueKey["CONSTRAINT_NAME"]));
// create index schemas from the loaded indices
tableSchema.indices = dbIndices
.filter(dbIndex => dbIndex["TABLE_NAME"] === tableSchema.name)
.map(dbIndex => dbIndex["INDEX_NAME"])
.filter((value, index, self) => self.indexOf(value) === index) // unqiue
.map(dbIndexName => {
const columnNames = dbIndices
.filter(dbIndex => dbIndex["TABLE_NAME"] === tableSchema.name && dbIndex["INDEX_NAME"] === dbIndexName)
.map(dbIndex => dbIndex["COLUMN_NAME"]);
return new IndexSchema(dbIndexName, columnNames);
});
return tableSchema;
});
}
/*async getColumnProperties(tableName: string, columnName: string): Promise<{ isNullable: boolean, columnType: string, autoIncrement: boolean }|undefined> {
const sql = `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '${this.dbName}'` +
` AND TABLE_NAME = '${tableName}' AND COLUMN_NAME = '${columnName}'`;
@ -33,6 +117,71 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
};
}*/
async addColumnQuery(tableName: string, column: ColumnMetadata): Promise<void> {
const sql = `ALTER TABLE \`${tableName}\` ADD ${this.buildCreateColumnSql(column, false)}`;
await this.query(sql);
}
async dropColumnQuery(tableName: string, columnName: string): Promise<void> {
const sql = `ALTER TABLE \`${tableName}\` DROP \`${columnName}\``;
await this.query(sql);
}
async addForeignKeyQuery(foreignKey: ForeignKeyMetadata): Promise<void> {
const columnNames = foreignKey.columnNames.map(column => "`" + column + "`").join(", ");
const referencedColumnNames = foreignKey.referencedColumnNames.map(column => "`" + column + "`").join(",");
let sql = `ALTER TABLE ${foreignKey.tableName} ADD CONSTRAINT \`${foreignKey.name}\` ` +
`FOREIGN KEY (${columnNames}) ` +
`REFERENCES \`${foreignKey.referencedTable.name}\`(${referencedColumnNames})`;
if (foreignKey.onDelete) sql += " ON DELETE " + foreignKey.onDelete;
await this.query(sql);
}
async dropForeignKeyQuery(foreignKey: ForeignKeyMetadata): Promise<void>;
async dropForeignKeyQuery(tableName: string, foreignKeyName: string): Promise<void>;
async dropForeignKeyQuery(tableNameOrForeignKey: string|ForeignKeyMetadata, foreignKeyName?: string): Promise<void> {
let tableName = <string> tableNameOrForeignKey;
if (tableNameOrForeignKey instanceof ForeignKeyMetadata) {
tableName = tableNameOrForeignKey.tableName;
foreignKeyName = tableNameOrForeignKey.name;
}
const sql = `ALTER TABLE \`${tableName}\` DROP FOREIGN KEY \`${foreignKeyName}\``;
await this.query(sql);
}
async dropIndex(tableName: string, indexName: string): Promise<void> {
const sql = `ALTER TABLE \`${tableName}\` DROP INDEX \`${indexName}\``;
await this.query(sql);
}
async createIndex(tableName: string, index: IndexMetadata): Promise<void> {
const columns = index.columns.map(column => "`" + column + "`").join(", ");
const sql = `CREATE ${index.isUnique ? "UNIQUE" : ""} INDEX \`${index.name}\` ON \`${tableName}\`(${columns})`;
await this.query(sql);
}
async addUniqueKey(tableName: string, columnName: string, keyName: string): Promise<void> {
const sql = `ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${keyName}\` UNIQUE (\`${columnName}\`)`;
await this.query(sql);
}
async renameColumnQuery(tableName: string, oldColumn: DatabaseColumnProperties, newColumn: ColumnMetadata): Promise<void> {
const sql = `ALTER TABLE \`${tableName}\` CHANGE \`${oldColumn.name}\` \`${newColumn.name}\` ${oldColumn.type}`;
await this.query(sql);
}
async changeColumnQuery(tableName: string, oldColumn: DatabaseColumnProperties, newColumn: ColumnMetadata): Promise<void> {
const sql = `ALTER TABLE \`${tableName}\` CHANGE \`${oldColumn.name}\` ${this.buildCreateColumnSql(newColumn, oldColumn.hasPrimaryKey)}`; // todo: CHANGE OR MODIFY COLUMN ????
await this.query(sql);
}
async createTableQuery(table: TableMetadata, columns: ColumnMetadata[]): Promise<void> {
const columnDefinitions = columns.map(column => this.buildCreateColumnSql(column, false)).join(", ");
const sql = `CREATE TABLE \`${table.name}\` (${columnDefinitions}) ENGINE=InnoDB;`;
await this.query(sql);
}
/**
* todo: reuse getColumns
*/
@ -54,7 +203,7 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
dbData.IS_NULLABLE !== isNullable ||
hasDbColumnAutoIncrement !== column.isGenerated ||
hasDbColumnPrimaryIndex !== column.isPrimary;
}).map(column => {
const dbData = results.find(result => result.COLUMN_NAME === column.name);
const hasDbColumnPrimaryIndex = dbData.COLUMN_KEY.indexOf("PRI") !== -1;
@ -73,38 +222,6 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
return this.query(sql).then(results => !!(results && results.length));
}
addColumnQuery(tableName: string, column: ColumnMetadata): Promise<void> {
const sql = `ALTER TABLE ${tableName} ADD ${this.buildCreateColumnSql(column, false)}`;
return this.query(sql).then(() => {});
}
dropColumnQuery(tableName: string, columnName: string): Promise<void> {
const sql = `ALTER TABLE ${tableName} DROP ${columnName}`;
return this.query(sql).then(() => {});
}
addForeignKeyQuery(foreignKey: ForeignKeyMetadata): Promise<void> {
let sql = `ALTER TABLE ${foreignKey.tableName} ADD CONSTRAINT \`${foreignKey.name}\` ` +
`FOREIGN KEY (${foreignKey.columnNames.join(", ")}) ` +
`REFERENCES ${foreignKey.referencedTable.name}(${foreignKey.referencedColumnNames.join(",")})`;
if (foreignKey.onDelete)
sql += " ON DELETE " + foreignKey.onDelete;
return this.query(sql).then(() => {});
}
dropForeignKeyQuery(foreignKey: ForeignKeyMetadata): Promise<void>;
dropForeignKeyQuery(tableName: string, foreignKeyName: string): Promise<void>;
dropForeignKeyQuery(tableNameOrForeignKey: string|ForeignKeyMetadata, foreignKeyName?: string): Promise<void> {
let tableName = <string> tableNameOrForeignKey;
if (tableNameOrForeignKey instanceof ForeignKeyMetadata) {
tableName = tableNameOrForeignKey.tableName;
foreignKeyName = tableNameOrForeignKey.name;
}
const sql = `ALTER TABLE ${tableName} DROP FOREIGN KEY \`${foreignKeyName}\``;
return this.query(sql).then(() => {});
}
getTableForeignQuery(tableName: string): Promise<string[]> {
const sql = `SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = "${this.dbName}" `
+ `AND TABLE_NAME = "${tableName}" AND REFERENCED_COLUMN_NAME IS NOT NULL`;
@ -140,21 +257,6 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
return this.query(sql).then(results => results && results.length ? results[0].CONSTRAINT_NAME : undefined);
}
dropIndex(tableName: string, indexName: string): Promise<void> {
const sql = `ALTER TABLE ${tableName} DROP INDEX \`${indexName}\``;
return this.query(sql).then(() => {});
}
createIndex(tableName: string, index: IndexMetadata): Promise<void> {
const sql = `CREATE ${index.isUnique ? "UNIQUE" : ""} INDEX \`${index.name}\` ON ${tableName}(${index.columns.join(", ")})`;
return this.query(sql).then(() => {});
}
addUniqueKey(tableName: string, columnName: string, keyName: string): Promise<void> {
const sql = `ALTER TABLE ${tableName} ADD CONSTRAINT ${keyName} UNIQUE (${columnName})`;
return this.query(sql).then(() => {});
}
getTableColumns(tableName: string): Promise<DatabaseColumnProperties[]> {
const sql = `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '${this.dbName}' AND TABLE_NAME = '${tableName}'`;
return this.query(sql).then((results: any[]) => {
@ -170,22 +272,6 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
});
}
renameColumnQuery(tableName: string, oldColumn: DatabaseColumnProperties, newColumn: ColumnMetadata): Promise<void> {
const sql = `ALTER TABLE ${tableName} CHANGE ${oldColumn.name} ${newColumn.name} ${oldColumn.type}`;
return this.query(sql).then(() => {});
}
changeColumnQuery(tableName: string, oldColumn: DatabaseColumnProperties, newColumn: ColumnMetadata): Promise<void> {
const sql = `ALTER TABLE ${tableName} CHANGE ${oldColumn.name} ${this.buildCreateColumnSql(newColumn, oldColumn.hasPrimaryKey)}`; // todo: CHANGE OR MODIFY COLUMN ????
return this.query(sql).then(() => {});
}
createTableQuery(table: TableMetadata, columns: ColumnMetadata[]): Promise<void> {
const columnDefinitions = columns.map(column => this.buildCreateColumnSql(column, false)).join(", ");
const sql = `CREATE TABLE \`${table.name}\` (${columnDefinitions}) ENGINE=InnoDB;`;
return this.query(sql).then(() => {});
}
// -------------------------------------------------------------------------
// Private Methods
// -------------------------------------------------------------------------

View File

@ -7,6 +7,7 @@ import {IndexMetadata} from "../metadata/IndexMetadata";
import {DatabaseConnection} from "../driver/DatabaseConnection";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {DataTypeNotSupportedByDriverError} from "./error/DataTypeNotSupportedByDriverError";
import {TableSchema} from "../schema-creator/TableSchema";
/**
* @internal
@ -17,7 +18,11 @@ export class PostgresSchemaBuilder extends SchemaBuilder {
private dbConnection: DatabaseConnection) {
super();
}
async loadSchemaTables(): Promise<TableSchema[]> {
return Promise.resolve([]);
}
async getChangedColumns(tableName: string, columns: ColumnMetadata[]): Promise<DatabaseColumnProperties[]> {
const dbColumns = await this.getTableColumns(tableName);
return dbColumns.filter(dbColumn => {

View File

@ -2,6 +2,7 @@ import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
import {TableMetadata} from "../metadata/TableMetadata";
import {IndexMetadata} from "../metadata/IndexMetadata";
import {TableSchema} from "../schema-creator/TableSchema";
export interface DatabaseColumnProperties {
name: string;
@ -17,6 +18,7 @@ export interface DatabaseColumnProperties {
*/
export abstract class SchemaBuilder {
abstract loadSchemaTables(tableNames: string[]): Promise<TableSchema[]>;
// abstract getColumnProperties(tableName: string, columnName: string): Promise<{ isNullable: boolean, columnType: string, autoIncrement: boolean }|undefined>;
abstract getChangedColumns(tableName: string, columns: ColumnMetadata[]): Promise<DatabaseColumnProperties[]>;
abstract checkIfTableExist(tableName: string): Promise<boolean>;

View File

@ -0,0 +1,11 @@
export class ColumnSchema {
name: string;
type: string;
default: string;
isNullable: boolean;
isGenerated: boolean;
isPrimary: boolean;
comment: string|undefined;
}

View File

@ -0,0 +1,9 @@
export class ForeignKeySchema {
name: string;
constructor(name: string) {
this.name = name;
}
}

View File

@ -0,0 +1,11 @@
export class IndexSchema {
name: string;
columnNames: string[];
constructor(name: string, columnNames: string[]) {
this.name = name;
this.columnNames = columnNames;
}
}

View File

@ -0,0 +1,9 @@
export class PrimaryKeySchema {
name: string;
constructor(name: string) {
this.name = name;
}
}

View File

@ -5,6 +5,7 @@ import {EntityMetadata} from "../metadata/EntityMetadata";
import {SchemaBuilder} from "../schema-builder/SchemaBuilder";
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
import {IndexMetadata} from "../metadata/IndexMetadata";
import {TableSchema} from "./TableSchema";
/**
* Creates indexes based on the given metadata.
@ -45,6 +46,8 @@ export class SchemaCreator {
*/
async create(): Promise<void> {
const metadatas = this.entityMetadatas;
const tableSchemas = await this.loadSchemaTables(metadatas);
console.log(tableSchemas);
await this.dropForeignKeysForAll(metadatas);
await this.createTablesForAll(metadatas);
await this.updateOldColumnsForAll(metadatas);
@ -61,6 +64,14 @@ export class SchemaCreator {
// Private Methods
// -------------------------------------------------------------------------
/**
* Loads all table schemas from the database.
*/
private loadSchemaTables(metadatas: EntityMetadata[]): Promise<TableSchema[]> {
const tableNames = metadatas.map(metadata => metadata.table.name);
return this.schemaBuilder.loadSchemaTables(tableNames);
}
private dropForeignKeysForAll(metadatas: EntityMetadata[]) {
return Promise.all(metadatas.map(metadata => this.dropForeignKeys(metadata.table, metadata.foreignKeys)));
}

View File

@ -0,0 +1,20 @@
import {ColumnSchema} from "./ColumnSchema";
import {IndexSchema} from "./IndexSchema";
import {ForeignKeySchema} from "./ForeignKeySchema";
import {UniqueKeySchema} from "./UniqueKeySchema";
import {PrimaryKeySchema} from "./PrimaryKeySchema";
export class TableSchema {
name: string;
columns: ColumnSchema[] = [];
indices: IndexSchema[] = [];
foreignKeys: ForeignKeySchema[] = [];
uniqueKeys: UniqueKeySchema[] = [];
primaryKey: PrimaryKeySchema;
constructor(name: string) {
this.name = name;
}
}

View File

@ -0,0 +1,9 @@
export class UniqueKeySchema {
name: string;
constructor(name: string) {
this.name = name;
}
}