adding oracle support - part 1

This commit is contained in:
Kononnable 2017-12-17 11:38:25 +01:00
parent 54b8671370
commit 5d3dac6be7
5 changed files with 513 additions and 11 deletions

View File

@ -28,13 +28,23 @@ services:
environment:
POSTGRES_PASSWORD: "!Passw0rd"
# # mssql
# mssql:
# image: "microsoft/mssql-server-linux:2017-GA"
# container_name: "typeorm-mg-mssql"
# ports:
# - "1433:1433"
# environment:
# ACCEPT_EULA: "Y"
# SA_PASSWORD: "!Passw0rd"
# mssql
mssql:
image: "microsoft/mssql-server-linux:2017-GA"
container_name: "typeorm-mg-mssql"
ports:
- "1433:1433"
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: "!Passw0rd"
# oracle
oracle:
image: "store/oracle/database-enterprise:12.2.0.1"
container_name: "typeorm-mg-oracle"
ports:
- "1521:1521"
# environment:
# DB_SID: "ORCLCDB"
# SYS_PASSWORD: "Oradoc_db1"

14
package-lock.json generated
View File

@ -73,6 +73,15 @@
"integrity": "sha512-54Dm6NwYeiSQmRB1BLXKr5GELi0wFapR1npi8bnZhEcu84d/yQKqnwwXQ56hZ0RUbTG6L5nqDZaN3dgByQXQRQ==",
"dev": true
},
"@types/oracledb": {
"version": "1.11.34",
"resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-1.11.34.tgz",
"integrity": "sha512-7cgZaKEfYcPPTScxxCoYoLxmmhM/PBobGBfxE3RGzRJl8YKhkyGKyExFu8fTOpF2cPgdfh83NGKBVX7prWzb+Q==",
"dev": true,
"requires": {
"@types/node": "8.0.53"
}
},
"@types/pg": {
"version": "6.1.45",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-6.1.45.tgz",
@ -2443,6 +2452,11 @@
}
}
},
"oracledb": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-2.0.15.tgz",
"integrity": "sha1-9+IBtp+ngjUIFV6fNKumXVdCbx0="
},
"os-locale": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",

View File

@ -27,6 +27,7 @@
"handlebars": "^4.0.11",
"mssql": "^4.0.4",
"mysql": "^2.15.0",
"oracledb": "^2.0.15",
"pg": "^6.4.2",
"reflect-metadata": "^0.1.10",
"typeorm": "^0.1.3",
@ -44,6 +45,7 @@
"@types/mssql": "^4.0.4",
"@types/mysql": "0.0.34",
"@types/node": "^8.0.53",
"@types/oracledb": "^1.11.34",
"@types/pg": "^6.1.45",
"@types/sinon": "^2.3.7",
"chai": "^4.1.2",

471
src/drivers/OracleDriver.ts Normal file
View File

@ -0,0 +1,471 @@
import { AbstractDriver } from './AbstractDriver'
import * as MSSQL from 'mssql'
import { ColumnInfo } from './../models/ColumnInfo'
import { EntityInfo } from './../models/EntityInfo'
import { RelationInfo } from './../models/RelationInfo'
import { DatabaseModel } from './../models/DatabaseModel'
/**
* OracleDriver
*/
export class OracleDriver 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(schema: string): Promise<EntityInfo[]> {
let request = new MSSQL.Request(this.Connection)
let response: { TABLE_SCHEMA: string, TABLE_NAME: string }[]
= (await request.query(`SELECT TABLE_SCHEMA,TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA='${schema}'`)).recordset;
let ret: EntityInfo[] = <EntityInfo[]>[];
response.forEach((val) => {
let ent: EntityInfo = new EntityInfo();
ent.EntityName = val.TABLE_NAME;
ent.Columns = <ColumnInfo[]>[];
ent.Indexes = <IndexInfo[]>[];
ret.push(ent);
})
return ret;
}
async GetCoulmnsFromEntity(entities: EntityInfo[], schema: string): Promise<EntityInfo[]> {
let request = new MSSQL.Request(this.Connection)
let response: {
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
}[]
= (await request.query(`SELECT TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,IS_NULLABLE,
DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE,
COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') IsIdentity FROM INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA='${schema}'`)).recordset;
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"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "tinyint":
colInfo.ts_type = "number"
colInfo.sql_type = "smallint"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "smallint":
colInfo.ts_type = "number"
colInfo.sql_type = "smallint"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "bit":
colInfo.ts_type = "boolean"
colInfo.sql_type = "boolean"
break;
case "float":
colInfo.ts_type = "number"
colInfo.sql_type = "float"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
colInfo.numericPrecision = resp.NUMERIC_PRECISION
break;
case "bigint":
colInfo.ts_type = "string"
colInfo.sql_type = "bigint"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
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 = "char"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "nchar":
colInfo.ts_type = "string"
colInfo.sql_type = "nchar"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "text":
colInfo.ts_type = "string"
colInfo.sql_type = "text"
break;
case "ntext":
colInfo.ts_type = "string"
colInfo.sql_type = "ntext"
break;
case "uniqueidentifier":
colInfo.ts_type = "string"
colInfo.sql_type = "uniqueidentifier"
break;
case "varchar":
colInfo.ts_type = "string"
colInfo.sql_type = "varchar"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "binary":
colInfo.ts_type = "Buffer"
colInfo.sql_type = "binary"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "varbinary":
colInfo.ts_type = "Buffer"
colInfo.sql_type = "varbinary"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "image":
colInfo.ts_type = "Buffer"
colInfo.sql_type = "image"
break;
case "nvarchar":
colInfo.ts_type = "string"
colInfo.sql_type = "nvarchar"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "money":
colInfo.ts_type = "number"
colInfo.sql_type = "decimal"
break;
case "smallmoney":
colInfo.ts_type = "number"
colInfo.sql_type = "smallmoney"
break;
case "real":
colInfo.ts_type = "number"
colInfo.sql_type = "double"
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "decimal":
colInfo.ts_type = "number"
colInfo.sql_type = "decimal"
colInfo.numericPrecision = resp.NUMERIC_PRECISION
colInfo.numericScale = resp.NUMERIC_SCALE
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "numeric":
colInfo.ts_type = "number"
colInfo.sql_type = "numeric"
colInfo.numericPrecision = resp.NUMERIC_PRECISION
colInfo.numericScale = resp.NUMERIC_SCALE
colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null;
break;
case "datetime2":
colInfo.ts_type = "Date"
colInfo.sql_type = "datetime2"
colInfo.numericPrecision = resp.NUMERIC_PRECISION
break;
case "time":
colInfo.ts_type = "Date"
colInfo.sql_type = "time"
colInfo.numericPrecision = resp.NUMERIC_PRECISION
break;
case "datetimeoffset":
colInfo.ts_type = "Date"
colInfo.sql_type = "datetimeoffset"
colInfo.numericPrecision = resp.NUMERIC_PRECISION
break;
case "smalldatetime":
colInfo.ts_type = "Date"
colInfo.sql_type = "smalldatetime"
break;
case "xml":
colInfo.ts_type = "string"
colInfo.sql_type = "text"
break;
default:
console.error("Unknown column type:" + resp.DATA_TYPE);
break;
}
if (colInfo.sql_type) ent.Columns.push(colInfo);
})
})
return entities;
}
async GetIndexesFromEntity(entities: EntityInfo[], schema: string): Promise<EntityInfo[]> {
let request = new MSSQL.Request(this.Connection)
let response: {
TableName: string, IndexName: string, ColumnName: string, is_unique: number,
is_primary_key: number//, is_descending_key: number//, is_included_column: number
}[]
= (await request.query(`SELECT
TableName = t.name,
IndexName = ind.name,
ColumnName = col.name,
ind.is_unique,
ind.is_primary_key
-- ,ic.is_descending_key,
-- ic.is_included_column
FROM
sys.indexes ind
INNER JOIN
sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id
INNER JOIN
sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id
INNER JOIN
sys.tables t ON ind.object_id = t.object_id
INNER JOIN
sys.schemas s on s.schema_id=t.schema_id
WHERE
t.is_ms_shipped = 0 and s.name='${schema}'
ORDER BY
t.name, ind.name, ind.index_id, ic.key_ordinal;`)).recordset;
entities.forEach((ent) => {
response.filter((filterVal) => {
return filterVal.TableName == ent.EntityName;
}).forEach((resp) => {
let indexInfo: IndexInfo = <IndexInfo>{};
let indexColumnInfo: 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 = <IndexColumnInfo[]>[];
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[], schema: string): Promise<EntityInfo[]> {
let request = new MSSQL.Request(this.Connection)
let response: {
TableWithForeignKey: string, FK_PartNo: number, ForeignKeyColumn: string,
TableReferenced: string, ForeignKeyColumnReferenced: string,
onDelete: "RESTRICT" | "CASCADE" | "SET NULL",
onUpdate: "RESTRICT" | "CASCADE" | "SET NULL", object_id: number
}[]
= (await request.query(`select
parentTable.name as TableWithForeignKey,
fkc.constraint_column_id as FK_PartNo,
parentColumn.name as ForeignKeyColumn,
referencedTable.name as TableReferenced,
referencedColumn.name as ForeignKeyColumnReferenced,
fk.delete_referential_action_desc as onDelete,
fk.update_referential_action_desc as onUpdate,
fk.object_id
from
sys.foreign_keys fk
inner join
sys.foreign_key_columns as fkc on fkc.constraint_object_id=fk.object_id
inner join
sys.tables as parentTable on fkc.parent_object_id = parentTable.object_id
inner join
sys.columns as parentColumn on fkc.parent_object_id = parentColumn.object_id and fkc.parent_column_id = parentColumn.column_id
inner join
sys.tables as referencedTable on fkc.referenced_object_id = referencedTable.object_id
inner join
sys.columns as referencedColumn on fkc.referenced_object_id = referencedColumn.object_id and fkc.referenced_column_id = referencedColumn.column_id
inner join
sys.schemas as parentSchema on parentSchema.schema_id=parentTable.schema_id
where
fk.is_disabled=0 and fk.is_ms_shipped=0 and parentSchema.name='${schema}'
order by
TableWithForeignKey, FK_PartNo`)).recordset;
let relationsTemp: RelationTempInfo[] = <RelationTempInfo[]>[];
response.forEach((resp) => {
let rels = relationsTemp.find((val) => {
return val.object_id == resp.object_id;
})
if (rels == undefined) {
rels = <RelationTempInfo>{};
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()
let columnName = ownerEntity.EntityName.toLowerCase() + (isOneToMany ? 's' : '')
if (referencedEntity.Columns.filter((filterVal) => {
return filterVal.name == columnName;
}).length>0){
for (let i=2;i<=ownerEntity.Columns.length;i++){
columnName = ownerEntity.EntityName.toLowerCase() + (isOneToMany ? 's' : '')+i.toString();
if (referencedEntity.Columns.filter((filterVal) => {
return filterVal.name == columnName;
}).length==0) break;
}
}
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 = columnName
ownerRelation.relationType = isOneToMany ? "ManyToOne" : "OneToOne"
ownerColumn.relations.push(ownerRelation)
if (isOneToMany) {
let col = new ColumnInfo()
col.name = columnName
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 = columnName
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() {
if (this.Connection)
await this.Connection.close();
}
private Connection: MSSQL.ConnectionPool;
async ConnectToServer(database: string, server: string, port: number, user: string, password: string, ssl: boolean) {
let config: MSSQL.config = {
database: database,
server: server,
port: port,
user: user,
password: password,
options: {
encrypt: ssl, // Use this if you're on Windows Azure
appName: 'typeorm-model-generator'
}
}
let promise = new Promise<boolean>(
(resolve, reject) => {
this.Connection = new MSSQL.ConnectionPool(config, (err) => {
if (!err) {
//Connection successfull
resolve(true)
}
else {
console.error('Error connecting to MSSQL Server.')
console.error(err.message)
process.abort()
reject(err)
}
});
}
)
await promise;
}
async CreateDB(dbName: string) {
let request = new MSSQL.Request(this.Connection);
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<boolean> {
let request = new MSSQL.Request(this.Connection);
let resp = await request.query(`SELECT name FROM master.sys.databases WHERE name = N'${dbName}' `)
return resp.recordset.length > 0;
}
}

View File

@ -3,6 +3,7 @@ import { MssqlDriver } from './drivers/MssqlDriver';
import { PostgresDriver } from "./drivers/PostgresDriver";
import { MysqlDriver } from "./drivers/MysqlDriver";
import { MariaDbDriver } from "./drivers/MariaDbDriver";
import { OracleDriver } from "./drivers/OracleDriver";
import { Engine } from './Engine'
import * as Yargs from 'yargs'
import path = require('path')
@ -38,7 +39,7 @@ var argv = Yargs
.option('e', {
alias: 'engine',
describe: 'Database engine.',
choices: ['mssql', 'postgres', 'mysql', 'mariadb'],
choices: ['mssql', 'postgres', 'mysql', 'mariadb','oracle'],
default: 'mssql'
})
.option('o', {
@ -75,10 +76,14 @@ switch (argv.e) {
driver = new MysqlDriver();
standardPort = 3306;
break;
case 'mariadb':
case 'mariadb':
driver = new MysqlDriver();
standardPort = 3306;
break;
case 'oracle':
driver = new OracleDriver();
standardPort = 1521;
break;
default:
console.error('Database engine not recognized.')
process.abort();