From 694025cecaba910b0fa9c0d1925ad0b11ebeee00 Mon Sep 17 00:00:00 2001 From: Kononnable Date: Mon, 14 Aug 2017 19:07:13 +0200 Subject: [PATCH] added support for MariaDB; formating in some files --- .travis.yml | 3 + src/Engine.ts | 1 - src/drivers/AbstractDriver.ts | 28 +- src/drivers/MariaDbDriver.ts | 407 +++++++++++++++++++++++++++ src/drivers/MssqlDriver.ts | 38 +-- src/drivers/PostgresDriver.ts | 92 +++--- src/index.ts | 7 +- test/integration/integration.test.ts | 51 +++- 8 files changed, 546 insertions(+), 81 deletions(-) create mode 100644 src/drivers/MariaDbDriver.ts diff --git a/.travis.yml b/.travis.yml index 7933d8c..fe7e114 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ env: - MSSQL_Skip=0 MSSQL_Host=localhost MSSQL_Port=1433 MSSQL_Username=sa MSSQL_Password=!Passw0rd MSSQL_Database=typeorm_mg - POSTGRES_Skip=0 POSTGRES_Host=localhost POSTGRES_Port=5432 POSTGRES_Username=postgres POSTGRES_Password=!Passw0rd POSTGRES_Database=typeorm_mg - MYSQL_Skip=0 MYSQL_Host=localhost MYSQL_Port=3306 MYSQL_Username=root MYSQL_Password=!Passw0rd MYSQL_Database=typeorm_mg + - MARIADB_Skip=0 MARIADB_Host=localhost MARIADB_Port=3307 MARIADB_Username=root MARIADB_Password=!Passw0rd MARIADB_Database=typeorm_mg before_install: - sudo service mysql stop @@ -23,9 +24,11 @@ before_install: - docker pull microsoft/mssql-server-linux - docker pull postgres - docker pull mysql + - docker pull mariadb - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=!Passw0rd' -p 1433:1433 --name mssql -d microsoft/mssql-server-linux - docker run -e 'POSTGRES_PASSWORD=!Passw0rd' -p 5432:5432 --name postgres -d postgres - docker run -e MYSQL_ROOT_PASSWORD=!Passw0rd -p 3306:3306 --name mysql -d mysql + - docker run -e MYSQL_ROOT_PASSWORD=!Passw0rd -p 3307:3306 --name mariadb -d mariadb before_script: diff --git a/src/Engine.ts b/src/Engine.ts index 04cc383..a613511 100644 --- a/src/Engine.ts +++ b/src/Engine.ts @@ -1,4 +1,3 @@ -import { MssqlDriver } from './drivers/MssqlDriver' import { AbstractDriver } from "./drivers/AbstractDriver"; import { DatabaseModel } from './models/DatabaseModel' import * as Handlebars from 'handlebars' diff --git a/src/drivers/AbstractDriver.ts b/src/drivers/AbstractDriver.ts index 672e4af..7f6f399 100644 --- a/src/drivers/AbstractDriver.ts +++ b/src/drivers/AbstractDriver.ts @@ -1,12 +1,12 @@ -import {EntityInfo} from './../models/EntityInfo' -import {DatabaseModel} from './../models/DatabaseModel' +import { EntityInfo } from './../models/EntityInfo' +import { DatabaseModel } from './../models/DatabaseModel' /** * AbstractDriver */ export abstract class AbstractDriver { - async GetDataFromServer(database:string,server:string,port:number,user:string,password:string): Promise { - let dbModel={}; - await this.ConnectToServer(database,server,port,user,password); + async GetDataFromServer(database: string, server: string, port: number, user: string, password: string): Promise { + let dbModel = {}; + await this.ConnectToServer(database, server, port, user, password); dbModel.entities = await this.GetAllTables(); await this.GetCoulmnsFromEntity(dbModel.entities); await this.GetIndexesFromEntity(dbModel.entities); @@ -15,16 +15,16 @@ export abstract class AbstractDriver { this.FindPrimaryColumnsFromIndexes(dbModel) return dbModel; } - abstract async ConnectToServer(database:string,server:string,port:number,user:string,password:string); + abstract async ConnectToServer(database: string, server: string, port: number, user: string, password: string); abstract async GetAllTables(): Promise - abstract async GetCoulmnsFromEntity(entities: EntityInfo[]):Promise; - abstract async GetIndexesFromEntity(entities: EntityInfo[]):Promise; - abstract async GetRelations(entities: EntityInfo[]):Promise; - abstract async FindPrimaryColumnsFromIndexes(dbModel:DatabaseModel); + abstract async GetCoulmnsFromEntity(entities: EntityInfo[]): Promise; + abstract async GetIndexesFromEntity(entities: EntityInfo[]): Promise; + abstract async GetRelations(entities: EntityInfo[]): Promise; + abstract async FindPrimaryColumnsFromIndexes(dbModel: DatabaseModel); abstract async DisconnectFromServer(); - abstract async CreateDB(dbName:string); - abstract async DropDB(dbName:string); - abstract async UseDB(dbName:string); - abstract async CheckIfDBExists(dbName:string):Promise; + abstract async CreateDB(dbName: string); + abstract async DropDB(dbName: string); + abstract async UseDB(dbName: string); + abstract async CheckIfDBExists(dbName: string): Promise; } \ No newline at end of file diff --git a/src/drivers/MariaDbDriver.ts b/src/drivers/MariaDbDriver.ts new file mode 100644 index 0000000..14badc6 --- /dev/null +++ b/src/drivers/MariaDbDriver.ts @@ -0,0 +1,407 @@ +import { AbstractDriver } from './AbstractDriver' +import * as MariaDb from 'mysql' +import { ColumnInfo } from './../models/ColumnInfo' +import { EntityInfo } from './../models/EntityInfo' +import { RelationInfo } from './../models/RelationInfo' +import { DatabaseModel } from './../models/DatabaseModel' +/** + * MariaDb + */ +export class MariaDbDriver extends AbstractDriver { + FindPrimaryColumnsFromIndexes(dbModel: DatabaseModel) { + dbModel.entities.forEach(entity => { + let primaryIndex = entity.Indexes.find(v => v.isPrimaryKey); + if (!primaryIndex) { + console.error(`Table ${entity.EntityName} has no PK.`) + return; + } + entity.Columns.forEach(col => { + if (primaryIndex!.columns.some(cIndex => cIndex.name == col.name)) col.isPrimary = true + }) + }); + } + + async GetAllTables(): Promise { + + let response = await this.ExecQuery<{ TABLE_SCHEMA: string, TABLE_NAME: string }>(`SELECT TABLE_SCHEMA, TABLE_NAME + FROM information_schema.tables + WHERE table_type='BASE TABLE' + AND table_schema like DATABASE()`); + let ret: EntityInfo[] = []; + response.forEach((val) => { + let ent: EntityInfo = new EntityInfo(); + ent.EntityName = val.TABLE_NAME; + ent.Columns = []; + ent.Indexes = []; + ret.push(ent); + }) + return ret; + } + async GetCoulmnsFromEntity(entities: EntityInfo[]): Promise { + let response = await this.ExecQuery<{ + TABLE_NAME: string, COLUMN_NAME: string, COLUMN_DEFAULT: string, + IS_NULLABLE: string, DATA_TYPE: string, CHARACTER_MAXIMUM_LENGTH: number, + NUMERIC_PRECISION: number, NUMERIC_SCALE: number, IsIdentity: number + }>(`SELECT TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,IS_NULLABLE, + DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE, + CASE WHEN EXTRA like '%auto_increment%' THEN 1 ELSE 0 END IsIdentity FROM INFORMATION_SCHEMA.COLUMNS + where TABLE_SCHEMA like DATABASE()`); + entities.forEach((ent) => { + response.filter((filterVal) => { + return filterVal.TABLE_NAME == ent.EntityName; + }).forEach((resp) => { + let colInfo: ColumnInfo = new ColumnInfo(); + colInfo.name = resp.COLUMN_NAME; + colInfo.is_nullable = resp.IS_NULLABLE == 'YES' ? true : false; + colInfo.is_generated = resp.IsIdentity == 1 ? true : false; + colInfo.default = resp.COLUMN_DEFAULT; + switch (resp.DATA_TYPE) { + case "int": + colInfo.ts_type = "number" + colInfo.sql_type = "int" + break; + case "tinyint": + if (resp.NUMERIC_PRECISION == 3) { + colInfo.ts_type = "boolean" + colInfo.sql_type = "boolean" + } else { + colInfo.ts_type = "number" + colInfo.sql_type = "smallint" + } + break; + case "smallint": + colInfo.ts_type = "number" + colInfo.sql_type = "smallint" + break; + case "bit": + colInfo.ts_type = "boolean" + colInfo.sql_type = "boolean" + break; + case "float": + colInfo.ts_type = "number" + colInfo.sql_type = "float" + break; + case "bigint": + colInfo.ts_type = "number" + colInfo.sql_type = "bigint" + break; + case "date": + colInfo.ts_type = "Date" + colInfo.sql_type = "date" + break; + case "time": + colInfo.ts_type = "Date" + colInfo.sql_type = "time" + break; + case "datetime": + colInfo.ts_type = "Date"; + colInfo.sql_type = "datetime" + break; + case "char": + colInfo.ts_type = "string" + colInfo.sql_type = "text" + break; + case "nchar": + colInfo.ts_type = "string" + colInfo.sql_type = "text" + break; + case "text": + colInfo.ts_type = "string" + colInfo.sql_type = "text" + break; + case "ntext": + colInfo.ts_type = "string" + colInfo.sql_type = "text" + break; + case "varchar": + colInfo.ts_type = "string" + colInfo.sql_type = "string" + break; + case "nvarchar": + colInfo.ts_type = "string" + colInfo.sql_type = "string" + break; + case "money": + colInfo.ts_type = "number" + colInfo.sql_type = "decimal" + break; + case "real": + colInfo.ts_type = "number" + colInfo.sql_type = "double" + break; + case "double": + colInfo.ts_type = "number" + colInfo.sql_type = "double" + break; + case "decimal": + colInfo.ts_type = "number" + colInfo.sql_type = "decimal" + colInfo.numericPrecision = resp.NUMERIC_PRECISION + colInfo.numericScale = resp.NUMERIC_SCALE + break; + case "xml": + colInfo.ts_type = "string" + colInfo.sql_type = "text" + break; + default: + console.error("Unknown column type:" + resp.DATA_TYPE); + break; + } + colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null; + if (colInfo.sql_type) ent.Columns.push(colInfo); + }) + }) + return entities; + } + async GetIndexesFromEntity(entities: EntityInfo[]): Promise { + let response = await this.ExecQuery<{ + TableName: string, IndexName: string, ColumnName: string, is_unique: number, + is_primary_key: number//, is_descending_key: number//, is_included_column: number + }>(`SELECT TABLE_NAME TableName,INDEX_NAME IndexName,COLUMN_NAME ColumnName,CASE WHEN NON_UNIQUE=0 THEN 1 ELSE 0 END is_unique, + CASE WHEN INDEX_NAME='PRIMARY' THEN 1 ELSE 0 END is_primary_key + FROM information_schema.statistics sta + WHERE table_schema like DATABASE(); + `); + entities.forEach((ent) => { + response.filter((filterVal) => { + return filterVal.TableName == ent.EntityName; + }).forEach((resp) => { + let indexInfo: IndexInfo = {}; + let indexColumnInfo: IndexColumnInfo = {}; + if (ent.Indexes.filter((filterVal) => { + return filterVal.name == resp.IndexName + }).length > 0) { + indexInfo = ent.Indexes.filter((filterVal) => { + return filterVal.name == resp.IndexName + })[0]; + } else { + indexInfo.columns = []; + indexInfo.name = resp.IndexName; + indexInfo.isUnique = resp.is_unique == 1 ? true : false; + indexInfo.isPrimaryKey = resp.is_primary_key == 1 ? true : false; + ent.Indexes.push(indexInfo); + } + indexColumnInfo.name = resp.ColumnName; + // indexColumnInfo.isIncludedColumn = resp.is_included_column == 1 ? true : false; + // indexColumnInfo.isDescending = resp.is_descending_key == 1 ? true : false; + indexInfo.columns.push(indexColumnInfo); + + }) + }) + + return entities; + } + async GetRelations(entities: EntityInfo[]): Promise { + let response = await this.ExecQuery<{ + TableWithForeignKey: string, FK_PartNo: number, ForeignKeyColumn: string, + TableReferenced: string, ForeignKeyColumnReferenced: string, + onDelete: "RESTRICT" | "CASCADE" | "SET NULL", + onUpdate: "RESTRICT" | "CASCADE" | "SET NULL", object_id: string + }>(`SELECT + CU.TABLE_NAME TableWithForeignKey, + CU.ORDINAL_POSITION FK_PartNo, + CU.COLUMN_NAME ForeignKeyColumn, + CU.REFERENCED_TABLE_NAME TableReferenced, + CU.REFERENCED_COLUMN_NAME ForeignKeyColumnReferenced, + RC.DELETE_RULE onDelete, + RC.UPDATE_RULE onUpdate, + CU.CONSTRAINT_NAME object_id + FROM + INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU + JOIN + INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC ON CU.CONSTRAINT_NAME=RC.CONSTRAINT_NAME + WHERE + TABLE_SCHEMA = SCHEMA() + AND CU.REFERENCED_TABLE_NAME IS NOT NULL; + `); + let relationsTemp: RelationTempInfo[] = []; + response.forEach((resp) => { + let rels = relationsTemp.find((val) => { + return val.object_id == resp.object_id; + }) + if (rels == undefined) { + rels = {}; + rels.ownerColumnsNames = []; + rels.referencedColumnsNames = []; + rels.actionOnDelete = resp.onDelete; + rels.actionOnUpdate = resp.onUpdate; + rels.object_id = resp.object_id; + rels.ownerTable = resp.TableWithForeignKey; + rels.referencedTable = resp.TableReferenced; + relationsTemp.push(rels); + } + rels.ownerColumnsNames.push(resp.ForeignKeyColumn); + rels.referencedColumnsNames.push(resp.ForeignKeyColumnReferenced); + }) + relationsTemp.forEach((relationTmp) => { + let ownerEntity = entities.find((entitity) => { + return entitity.EntityName == relationTmp.ownerTable; + }) + if (!ownerEntity) { + console.error(`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity model ${relationTmp.ownerTable}.`) + return; + } + let referencedEntity = entities.find((entitity) => { + return entitity.EntityName == relationTmp.referencedTable; + }) + if (!referencedEntity) { + console.error(`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity model ${relationTmp.referencedTable}.`) + return; + } + let ownerColumn = ownerEntity.Columns.find((column) => { + return column.name == relationTmp.ownerColumnsNames[0]; + }) + if (!ownerColumn) { + console.error(`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity column ${relationTmp.ownerTable}.${ownerColumn}.`) + return; + } + let relatedColumn = referencedEntity.Columns.find((column) => { + return column.name == relationTmp.referencedColumnsNames[0]; + }) + if (!relatedColumn) { + console.error(`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity column ${relationTmp.referencedTable}.${relatedColumn}.`) + return; + } + let ownColumn: ColumnInfo = ownerColumn; + let isOneToMany: boolean; + isOneToMany = false; + let index = ownerEntity.Indexes.find( + (index) => { + return index.isUnique && index.columns.some(col => { + return col.name == ownerColumn!.name + }) + } + ) + if (!index) { + isOneToMany = true; + } else { + isOneToMany = false; + } + let ownerRelation = new RelationInfo() + ownerRelation.actionOnDelete = relationTmp.actionOnDelete + ownerRelation.actionOnUpdate = relationTmp.actionOnUpdate + ownerRelation.isOwner = true + ownerRelation.relatedColumn = relatedColumn.name.toLowerCase() + ownerRelation.relatedTable = relationTmp.referencedTable + ownerRelation.ownerTable = relationTmp.ownerTable + ownerRelation.ownerColumn = ownerEntity.EntityName.toLowerCase() + (isOneToMany ? 's' : '') + ownerRelation.relationType = isOneToMany ? "ManyToOne" : "OneToOne" + ownerColumn.relations.push(ownerRelation) + if (isOneToMany) { + let col = new ColumnInfo() + col.name = ownerEntity.EntityName.toLowerCase() + 's' + let referencedRelation = new RelationInfo(); + col.relations.push(referencedRelation) + referencedRelation.actionOnDelete = relationTmp.actionOnDelete + referencedRelation.actionOnUpdate = relationTmp.actionOnUpdate + referencedRelation.isOwner = false + referencedRelation.relatedColumn = ownerColumn.name + referencedRelation.relatedTable = relationTmp.ownerTable + referencedRelation.ownerTable = relationTmp.referencedTable + referencedRelation.ownerColumn = relatedColumn.name.toLowerCase() + referencedRelation.relationType = "OneToMany" + referencedEntity.Columns.push(col) + } else { + let col = new ColumnInfo() + col.name = ownerEntity.EntityName.toLowerCase() + let referencedRelation = new RelationInfo(); + col.relations.push(referencedRelation) + referencedRelation.actionOnDelete = relationTmp.actionOnDelete + referencedRelation.actionOnUpdate = relationTmp.actionOnUpdate + referencedRelation.isOwner = false + referencedRelation.relatedColumn = ownerColumn.name + referencedRelation.relatedTable = relationTmp.ownerTable + referencedRelation.ownerTable = relationTmp.referencedTable + referencedRelation.ownerColumn = relatedColumn.name.toLowerCase() + referencedRelation.relationType = "OneToOne" + + referencedEntity.Columns.push(col) + } + }) + return entities; + } + async DisconnectFromServer() { + let promise = new Promise( + (resolve, reject) => { + this.Connection.end((err) => { + if (!err) { + //Connection successfull + resolve(true) + } + else { + console.error('Error disconnecting to MariaDb Server.') + console.error(err.message) + process.abort() + reject(err) + } + }); + } + ) + + if (this.Connection) + await promise; + + } + + private Connection: MariaDb.IConnection; + async ConnectToServer(database: string, server: string, port: number, user: string, password: string) { + let config: MariaDb.IConnectionConfig = { + database: database, + host: server, + port: port, + user: user, + password: password, + } + + + let promise = new Promise( + (resolve, reject) => { + this.Connection = MariaDb.createConnection(config) + + this.Connection.connect((err) => { + if (!err) { + //Connection successfull + resolve(true) + } + else { + console.error('Error connecting to MariaDb Server.') + console.error(err.message) + process.abort() + reject(err) + } + }); + } + ) + + await promise; + } + async CreateDB(dbName: string) { + let resp = await this.ExecQuery(`CREATE DATABASE ${dbName}; `) + } + async UseDB(dbName: string) { + let resp = await this.ExecQuery(`USE ${dbName}; `) + } + async DropDB(dbName: string) { + let resp = await this.ExecQuery(`DROP DATABASE ${dbName}; `) + } + async CheckIfDBExists(dbName: string): Promise { + let resp = await this.ExecQuery(`SHOW DATABASES LIKE '${dbName}' `) + return resp.length > 0; + } + async ExecQuery(sql: string): Promise> { + let ret: Array = []; + let that = this; + let query = this.Connection.query(sql) + let stream = query.stream({}); + let promise = new Promise( + (resolve, reject) => { + stream.on('data', + chunk => { + ret.push(chunk) + }); + stream.on('end', () => resolve(true)); + }) + await promise; + return ret; + } +} \ No newline at end of file diff --git a/src/drivers/MssqlDriver.ts b/src/drivers/MssqlDriver.ts index 2e262a4..d67a41f 100644 --- a/src/drivers/MssqlDriver.ts +++ b/src/drivers/MssqlDriver.ts @@ -189,13 +189,13 @@ ORDER BY ent.Indexes.push(indexInfo); } indexColumnInfo.name = resp.ColumnName; - // indexColumnInfo.isIncludedColumn = resp.is_included_column == 1 ? true : false; - // indexColumnInfo.isDescending = resp.is_descending_key == 1 ? true : false; + // indexColumnInfo.isIncludedColumn = resp.is_included_column == 1 ? true : false; + // indexColumnInfo.isDescending = resp.is_descending_key == 1 ? true : false; indexInfo.columns.push(indexColumnInfo); }) }) - + return entities; } async GetRelations(entities: EntityInfo[]): Promise { @@ -301,12 +301,12 @@ order by ownerRelation.relatedColumn = relatedColumn.name.toLowerCase() ownerRelation.relatedTable = relationTmp.referencedTable ownerRelation.ownerTable = relationTmp.ownerTable - ownerRelation.ownerColumn = ownerEntity.EntityName.toLowerCase()+(isOneToMany ? 's':'') + ownerRelation.ownerColumn = ownerEntity.EntityName.toLowerCase() + (isOneToMany ? 's' : '') ownerRelation.relationType = isOneToMany ? "ManyToOne" : "OneToOne" ownerColumn.relations.push(ownerRelation) if (isOneToMany) { let col = new ColumnInfo() - col.name = ownerEntity.EntityName.toLowerCase() +'s' + col.name = ownerEntity.EntityName.toLowerCase() + 's' let referencedRelation = new RelationInfo(); col.relations.push(referencedRelation) referencedRelation.actionOnDelete = relationTmp.actionOnDelete @@ -329,7 +329,7 @@ order by referencedRelation.relatedColumn = ownerColumn.name referencedRelation.relatedTable = relationTmp.ownerTable referencedRelation.ownerTable = relationTmp.referencedTable - referencedRelation.ownerColumn = relatedColumn.name.toLowerCase() + referencedRelation.ownerColumn = relatedColumn.name.toLowerCase() referencedRelation.relationType = "OneToOne" referencedEntity.Columns.push(col) @@ -376,21 +376,21 @@ order by await promise; } - async CreateDB(dbName:string){ + async CreateDB(dbName: string) { let request = new MSSQL.Request(this.Connection); - let resp =await request.query(`CREATE DATABASE ${dbName}; `) + let resp = await request.query(`CREATE DATABASE ${dbName}; `) } - async UseDB(dbName:string){ - let request = new MSSQL.Request(this.Connection); - let resp =await request.query(`USE ${dbName}; `) - } - async DropDB(dbName:string){ - let request = new MSSQL.Request(this.Connection); - let resp =await request.query(`DROP DATABASE ${dbName}; `) - } - async CheckIfDBExists(dbName:string):Promise{ + async UseDB(dbName: string) { let request = new MSSQL.Request(this.Connection); - let resp =await request.query(`SELECT name FROM master.sys.databases WHERE name = N'${dbName}' `) - return resp.length>0; + let resp = await request.query(`USE ${dbName}; `) + } + async DropDB(dbName: string) { + let request = new MSSQL.Request(this.Connection); + let resp = await request.query(`DROP DATABASE ${dbName}; `) + } + async CheckIfDBExists(dbName: string): Promise { + let request = new MSSQL.Request(this.Connection); + let resp = await request.query(`SELECT name FROM master.sys.databases WHERE name = N'${dbName}' `) + return resp.length > 0; } } \ No newline at end of file diff --git a/src/drivers/PostgresDriver.ts b/src/drivers/PostgresDriver.ts index c1d7fd7..4d6ae3d 100644 --- a/src/drivers/PostgresDriver.ts +++ b/src/drivers/PostgresDriver.ts @@ -1,6 +1,5 @@ import { AbstractDriver } from './AbstractDriver' import * as PG from 'pg' - import { ColumnInfo } from './../models/ColumnInfo' import { EntityInfo } from './../models/EntityInfo' import { RelationInfo } from './../models/RelationInfo' @@ -9,7 +8,7 @@ import { DatabaseModel } from './../models/DatabaseModel' * PostgresDriver */ export class PostgresDriver extends AbstractDriver { - private Connection:PG.Client; + private Connection: PG.Client; FindPrimaryColumnsFromIndexes(dbModel: DatabaseModel) { dbModel.entities.forEach(entity => { @@ -25,10 +24,10 @@ export class PostgresDriver extends AbstractDriver { } async GetAllTables(): Promise { - + let response: { table_schema: string, table_name: string }[] = (await this.Connection.query("SELECT table_schema,table_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND table_schema = 'public' ")).rows; - + let ret: EntityInfo[] = []; response.forEach((val) => { let ent: EntityInfo = new EntityInfo(); @@ -58,22 +57,22 @@ export class PostgresDriver extends AbstractDriver { colInfo.name = resp.column_name; colInfo.is_nullable = resp.is_nullable == 'YES' ? true : false; colInfo.is_generated = resp.isidentity == 'YES' ? true : false; - colInfo.default = colInfo.is_generated?'':resp.column_default; + colInfo.default = colInfo.is_generated ? '' : resp.column_default; switch (resp.data_type) { //TODO:change types to postgres - case "integer": + case "integer": colInfo.ts_type = "number" colInfo.sql_type = "int" break; - case "character varying": + case "character varying": colInfo.ts_type = "string" colInfo.sql_type = "text" break; - case "text": + case "text": colInfo.ts_type = "string" colInfo.sql_type = "text" break; - case "smallint": + case "smallint": colInfo.ts_type = "number" colInfo.sql_type = "smallint" break; @@ -181,8 +180,8 @@ export class PostgresDriver extends AbstractDriver { ent.Indexes.push(indexInfo); } indexColumnInfo.name = resp.columnname; - if (resp.is_primary_key==0) { - indexInfo.isPrimaryKey=false; + if (resp.is_primary_key == 0) { + indexInfo.isPrimaryKey = false; } // indexColumnInfo.isIncludedColumn = resp.is_included_column == 1 ? true : false; //indexColumnInfo.isDescending = resp.is_descending_key == 1 ? true : false; @@ -190,7 +189,7 @@ export class PostgresDriver extends AbstractDriver { }) }) - + return entities; } async GetRelations(entities: EntityInfo[]): Promise { @@ -307,12 +306,12 @@ export class PostgresDriver extends AbstractDriver { ownerRelation.relatedColumn = relatedColumn.name.toLowerCase() ownerRelation.relatedTable = relationTmp.referencedTable ownerRelation.ownerTable = relationTmp.ownerTable - ownerRelation.ownerColumn = ownerEntity.EntityName.toLowerCase()+(isOneToMany ? 's':'') + ownerRelation.ownerColumn = ownerEntity.EntityName.toLowerCase() + (isOneToMany ? 's' : '') ownerRelation.relationType = isOneToMany ? "ManyToOne" : "OneToOne" ownerColumn.relations.push(ownerRelation) if (isOneToMany) { let col = new ColumnInfo() - col.name = ownerEntity.EntityName.toLowerCase() +'s' + col.name = ownerEntity.EntityName.toLowerCase() + 's' let referencedRelation = new RelationInfo(); col.relations.push(referencedRelation) referencedRelation.actionondelete = relationTmp.actionOnDelete @@ -335,7 +334,7 @@ export class PostgresDriver extends AbstractDriver { referencedRelation.relatedColumn = ownerColumn.name referencedRelation.relatedTable = relationTmp.ownerTable referencedRelation.ownerTable = relationTmp.referencedTable - referencedRelation.ownerColumn = relatedColumn.name.toLowerCase() + referencedRelation.ownerColumn = relatedColumn.name.toLowerCase() referencedRelation.relationType = "OneToOne" referencedEntity.Columns.push(col) @@ -344,36 +343,39 @@ export class PostgresDriver extends AbstractDriver { return entities; } async DisconnectFromServer() { - if (this.Connection){ - let promise = new Promise( - (resolve, reject) => { this.Connection.end((err) => { - if (!err) { - //Connection successfull - resolve(true) - } - else { - console.error('Error connecting to Postgres Server.') - console.error(err.message) - process.abort() - reject(err) - } - }); - }) - await promise; + if (this.Connection) { + let promise = new Promise( + (resolve, reject) => { + this.Connection.end((err) => { + if (!err) { + //Connection successfull + resolve(true) + } + else { + console.error('Error connecting to Postgres Server.') + console.error(err.message) + process.abort() + reject(err) + } + }); + }) + await promise; } } async ConnectToServer(database: string, server: string, port: number, user: string, password: string) { - this.Connection=new PG.Client({database: database, + this.Connection = new PG.Client({ + database: database, host: server, port: port, user: user, - password: password}) - + password: password + }) + let promise = new Promise( (resolve, reject) => { - this.Connection.connect( (err) => { + this.Connection.connect((err) => { if (!err) { //Connection successfull resolve(true) @@ -391,18 +393,18 @@ export class PostgresDriver extends AbstractDriver { await promise; } - async CreateDB(dbName:string){ - + async CreateDB(dbName: string) { + let resp = await this.Connection.query(`CREATE DATABASE ${dbName}; `) } - async UseDB(dbName:string){ - let resp =await this.Connection.query(`USE ${dbName}; `) - } - async DropDB(dbName:string){ - let resp =await this.Connection.query(`DROP DATABASE ${dbName}; `) + async UseDB(dbName: string) { + let resp = await this.Connection.query(`USE ${dbName}; `) } - async CheckIfDBExists(dbName:string):Promise{ - let resp =await this.Connection.query(`SELECT datname FROM pg_database WHERE datname ='${dbName}' `) - return resp.rowCount>0; + async DropDB(dbName: string) { + let resp = await this.Connection.query(`DROP DATABASE ${dbName}; `) + } + async CheckIfDBExists(dbName: string): Promise { + let resp = await this.Connection.query(`SELECT datname FROM pg_database WHERE datname ='${dbName}' `) + return resp.rowCount > 0; } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index bc7aecc..1e18b40 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { AbstractDriver } from "./drivers/AbstractDriver"; import { MssqlDriver } from './drivers/MssqlDriver'; import { PostgresDriver } from "./drivers/PostgresDriver"; import { MysqlDriver } from "./drivers/MysqlDriver"; +import { MariaDbDriver } from "./drivers/MariaDbDriver"; import { Engine } from './Engine' import * as Yargs from 'yargs' import path = require('path') @@ -37,7 +38,7 @@ var argv = Yargs .option('e', { alias: 'engine', describe: 'Database engine.', - choices: ['mssql', 'postgres', 'mysql'], + choices: ['mssql', 'postgres', 'mysql', 'mariadb'], default: 'mssql' }) .option('o', { @@ -63,6 +64,10 @@ switch (argv.e) { driver = new MysqlDriver(); standardPort = 3306; break; + case 'mariadb': + driver = new MysqlDriver(); + standardPort = 3306; + break; default: console.error('Database engine not recognized.') process.abort(); diff --git a/test/integration/integration.test.ts b/test/integration/integration.test.ts index 04f6462..1c91a2d 100644 --- a/test/integration/integration.test.ts +++ b/test/integration/integration.test.ts @@ -15,13 +15,14 @@ var chaiSubset = require('chai-subset'); import * as ts from "typescript"; import { PostgresDriver } from "../../src/drivers/PostgresDriver"; import { MysqlDriver } from "../../src/drivers/MysqlDriver"; +import { MariaDbDriver } from "../../src/drivers/MariaDbDriver"; chai.use(chaiSubset); describe("integration tests", async function () { - this.timeout(10000) + this.timeout(20000) this.slow(5000)//compiling created models takes time let examplesPathJS = path.resolve(process.cwd(), 'dist/test/integration/examples') let examplesPathTS = path.resolve(process.cwd(), 'test/integration/examples') @@ -31,6 +32,7 @@ describe("integration tests", async function () { if (process.env.MSSQL_Skip == '0') dbDrivers.push('mssql') if (process.env.POSTGRES_Skip == '0') dbDrivers.push('postgres') if (process.env.MYSQL_Skip == '0') dbDrivers.push('mysql') + if (process.env.MARIADB_Skip == '0') dbDrivers.push('mariadb') for (let folder of files) { @@ -54,6 +56,10 @@ describe("integration tests", async function () { case 'mysql': engine = await createMysqlModels(filesOrgPathJS, resultsPath) break; + case 'mariadb': + engine = await createMariaDBModels(filesOrgPathJS, resultsPath) + break; + default: console.log(`Unknown engine type`); engine = {} @@ -227,6 +233,49 @@ async function createMysqlModels(filesOrgPath: string, resultsPath: string): Pro + return engine; +} +async function createMariaDBModels(filesOrgPath: string, resultsPath: string): Promise { + let driver: AbstractDriver; + driver = new MariaDbDriver(); + await driver.ConnectToServer(`mysql`, process.env.MARIADB_Host, process.env.MARIADB_Port, process.env.MARIADB_Username, process.env.MARIADB_Password); + + if (! await driver.CheckIfDBExists(process.env.MARIADB_Database)) + await driver.CreateDB(process.env.MARIADB_Database); + await driver.DisconnectFromServer(); + + let connOpt: ConnectionOptions = { + driver: { + database: process.env.MARIADB_Database, + host: process.env.MARIADB_Host, + password: process.env.MARIADB_Password, + type: 'mariadb', + username: process.env.MARIADB_Username, + port: process.env.MARIADB_Port + }, + dropSchemaOnConnection: true, + autoSchemaSync: true, + entities: [path.resolve(filesOrgPath, '*.js')], + } + let conn = await createConnection(connOpt) + + if (conn.isConnected) + await conn.close() + + driver = new MariaDbDriver(); + let engine = new Engine( + driver, { + host: process.env.MARIADB_Host, + port: process.env.MARIADB_Port, + databaseName: process.env.MARIADB_Database, + user: process.env.MARIADB_Username, + password: process.env.MARIADB_Password, + databaseType: 'mariadb', + resultsPath: resultsPath + }); + + + return engine; }