OracleDB support #9

This commit is contained in:
Kononnable 2018-04-15 18:13:33 +02:00
parent 4c6cfe58cf
commit d0fe49b9b8
14 changed files with 334 additions and 76 deletions

View File

@ -45,5 +45,5 @@ services:
# ports:
# - "1521:1521"
# environment:
# DB_SID: "ORCLCDB"
# SYS_PASSWORD: "Oradoc_db1"
# DB_SID: "sys"
# SYS_PASSWORD: "ORCLCDB"

6
package-lock.json generated
View File

@ -6056,9 +6056,9 @@
}
},
"oracledb": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-2.0.15.tgz",
"integrity": "sha1-9+IBtp+ngjUIFV6fNKumXVdCbx0="
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-2.2.0.tgz",
"integrity": "sha512-ywwalyryeJYb5dr1JScyPcNxCeN0zExrKLtorSdptBZqhfS5Dp9KLgGOExc+XMMfEejXGtC/RfiDxKaGn6+VJA=="
},
"orchestrator": {
"version": "0.3.8",

View File

@ -25,7 +25,7 @@
"handlebars": "^4.0.11",
"mssql": "^4.0.4",
"mysql": "^2.15.0",
"oracledb": "^2.0.15",
"oracledb": "^2.2.0",
"pg": "^7.4.0",
"reflect-metadata": "^0.1.10",
"typeorm": "0.2.0-alpha.44",

View File

@ -23,13 +23,13 @@ export class OracleDriver extends AbstractDriver {
}
async GetAllTables(schema: string): Promise<EntityInfo[]> {
let response: any[][] = (await this.Connection.execute(
let response: { TABLE_NAME: string }[] = (await this.Connection.execute(
` SELECT TABLE_NAME FROM all_tables WHERE owner = (select user from dual)`
)).rows!;
let ret: EntityInfo[] = <EntityInfo[]>[];
response.forEach(val => {
let ent: EntityInfo = new EntityInfo();
ent.EntityName = val[0];
ent.EntityName = val.TABLE_NAME;
ent.Columns = <ColumnInfo[]>[];
ent.Indexes = <IndexInfo[]>[];
ret.push(ent);
@ -40,39 +40,153 @@ export class OracleDriver extends AbstractDriver {
entities: EntityInfo[],
schema: string
): Promise<EntityInfo[]> {
let response: any[][] = (await this.Connection
.execute(`SELECT TABLE_NAME, COLUMN_NAME, DATA_DEFAULT, NULLABLE, DATA_TYPE, DATA_LENGTH,
DATA_PRECISION, DATA_SCALE, IDENTITY_COLUMN
FROM USER_TAB_COLUMNS`)).rows!;
let response: {
TABLE_NAME: string;
COLUMN_NAME: string;
DATA_DEFAULT: string;
NULLABLE: string;
DATA_TYPE: string;
DATA_LENGTH: number;
DATA_PRECISION: number;
DATA_SCALE: number;
IDENTITY_COLUMN: string;
IS_UNIQUE: Number;
}[] = (await this.Connection
.execute(`SELECT utc.TABLE_NAME, utc.COLUMN_NAME, DATA_DEFAULT, NULLABLE, DATA_TYPE, DATA_LENGTH,
DATA_PRECISION, DATA_SCALE, IDENTITY_COLUMN,
(select count(*) from USER_CONS_COLUMNS ucc
JOIN USER_CONSTRAINTS uc ON uc.CONSTRAINT_NAME = ucc.CONSTRAINT_NAME and uc.CONSTRAINT_TYPE='U'
where ucc.column_name = utc.COLUMN_NAME AND ucc.table_name = utc.TABLE_NAME) IS_UNIQUE
FROM USER_TAB_COLUMNS utc`)).rows!;
entities.forEach(ent => {
response
.filter(filterVal => {
return filterVal[0] == ent.EntityName;
return filterVal.TABLE_NAME == ent.EntityName;
})
.forEach(resp => {
let colInfo: ColumnInfo = new ColumnInfo();
colInfo.name = resp[1];
colInfo.is_nullable = resp[3] == "Y" ? true : false;
colInfo.is_generated = resp[8] == "YES" ? true : false;
colInfo.default = resp[2];
switch (resp[4].toLowerCase()) {
case "number":
colInfo.ts_type = "number";
colInfo.sql_type = "int";
colInfo.lenght = resp[5] > 0 ? resp[5] : null;
colInfo.name = resp.COLUMN_NAME;
colInfo.is_nullable = resp.NULLABLE == "Y" ? true : false;
colInfo.is_generated =
resp.IDENTITY_COLUMN == "YES" ? true : false;
colInfo.default =
!resp.DATA_DEFAULT || resp.DATA_DEFAULT.includes('"')
? null
: resp.DATA_DEFAULT;
colInfo.is_unique = resp.IS_UNIQUE > 0;
resp.DATA_TYPE = resp.DATA_TYPE.replace(/\([0-9]+\)/g, "");
colInfo.sql_type = resp.DATA_TYPE.toLowerCase();
switch (resp.DATA_TYPE.toLowerCase()) {
case "char":
colInfo.ts_type = "string";
break;
case "nchar":
colInfo.ts_type = "string";
break;
case "nvarchar2":
colInfo.ts_type = "string";
break;
case "varchar2":
colInfo.ts_type = "string";
break;
case "long":
colInfo.ts_type = "string";
break;
case "raw":
colInfo.ts_type = "Buffer";
break;
case "long raw":
colInfo.ts_type = "Buffer";
break;
case "number":
colInfo.ts_type = "number";
break;
case "numeric":
colInfo.ts_type = "number";
break;
case "float":
colInfo.ts_type = "number";
break;
case "dec":
colInfo.ts_type = "number";
break;
case "decimal":
colInfo.ts_type = "number";
break;
case "integer":
colInfo.ts_type = "number";
break;
case "int":
colInfo.ts_type = "number";
break;
case "smallint":
colInfo.ts_type = "number";
break;
case "real":
colInfo.ts_type = "number";
break;
case "double precision":
colInfo.ts_type = "number";
break;
case "date":
colInfo.ts_type = "Date";
break;
case "timestamp":
colInfo.ts_type = "Date";
break;
case "timestamp with time zone":
colInfo.ts_type = "Date";
break;
case "timestamp with local time zone":
colInfo.ts_type = "Date";
break;
case "interval year to month":
colInfo.ts_type = "string";
break;
case "interval day to second":
colInfo.ts_type = "string";
break;
case "bfile":
colInfo.ts_type = "Buffer";
break;
case "blob":
colInfo.ts_type = "Buffer";
break;
case "clob":
colInfo.ts_type = "string";
break;
case "nclob":
colInfo.ts_type = "string";
break;
case "rowid":
colInfo.ts_type = "number";
break;
case "urowid":
colInfo.ts_type = "number";
colInfo.sql_type = "smallint";
colInfo.lenght = resp[5] > 0 ? resp[5] : null;
break;
default:
TomgUtils.LogError(
"Unknown column type:" + resp[4]
"Unknown column type:" + resp.DATA_TYPE
);
break;
}
if (
this.ColumnTypesWithPrecision.some(
v => v == colInfo.sql_type
)
) {
colInfo.numericPrecision = resp.DATA_PRECISION;
colInfo.numericScale = resp.DATA_SCALE;
}
if (
this.ColumnTypesWithLength.some(
v => v == colInfo.sql_type
)
) {
colInfo.lenght =
resp.DATA_LENGTH > 0 ? resp.DATA_LENGTH : null;
}
if (colInfo.sql_type) ent.Columns.push(colInfo);
});
@ -83,8 +197,14 @@ export class OracleDriver extends AbstractDriver {
entities: EntityInfo[],
schema: string
): Promise<EntityInfo[]> {
let response: any[][] = (await this.Connection
.execute(`SELECT ind.TABLE_NAME, ind.INDEX_NAME, col.COLUMN_NAME,ind.UNIQUENESS, CASE WHEN uc.CONSTRAINT_NAME IS NULL THEN 0 ELSE 1 END
let response: {
COLUMN_NAME: string;
TABLE_NAME: string;
INDEX_NAME: string;
UNIQUENESS: string;
ISPRIMARYKEY: number;
}[] = (await this.Connection
.execute(`SELECT ind.TABLE_NAME, ind.INDEX_NAME, col.COLUMN_NAME,ind.UNIQUENESS, CASE WHEN uc.CONSTRAINT_NAME IS NULL THEN 0 ELSE 1 END ISPRIMARYKEY
FROM USER_INDEXES ind
JOIN USER_IND_COLUMNS col ON ind.INDEX_NAME=col.INDEX_NAME
LEFT JOIN USER_CONSTRAINTS uc ON uc.INDEX_NAME = ind.INDEX_NAME
@ -93,29 +213,27 @@ export class OracleDriver extends AbstractDriver {
entities.forEach(ent => {
response
.filter(filterVal => {
return filterVal[0] == ent.EntityName;
return filterVal.TABLE_NAME == ent.EntityName;
})
.forEach(resp => {
let indexInfo: IndexInfo = <IndexInfo>{};
let indexColumnInfo: IndexColumnInfo = <IndexColumnInfo>{};
if (
ent.Indexes.filter(filterVal => {
return filterVal.name == resp[1];
return filterVal.name == resp.INDEX_NAME;
}).length > 0
) {
indexInfo = ent.Indexes.filter(filterVal => {
return filterVal.name == resp[1];
return filterVal.name == resp.INDEX_NAME;
})[0];
} else {
indexInfo.columns = <IndexColumnInfo[]>[];
indexInfo.name = resp[1];
indexInfo.isUnique = resp[3] == "UNIQUE" ? true : false;
indexInfo.isPrimaryKey = resp[4] == 1 ? true : false;
indexInfo.name = resp.INDEX_NAME;
indexInfo.isUnique = resp.UNIQUENESS == "UNIQUE";
indexInfo.isPrimaryKey = resp.ISPRIMARYKEY == 1;
ent.Indexes.push(indexInfo);
}
indexColumnInfo.name = resp[2];
// indexColumnInfo.isIncludedColumn = resp.is_included_column == 1 ? true : false;
// indexColumnInfo.isDescending = resp.is_descending_key == 1 ? true : false;
indexColumnInfo.name = resp.COLUMN_NAME;
indexInfo.columns.push(indexColumnInfo);
});
});
@ -126,36 +244,44 @@ export class OracleDriver extends AbstractDriver {
entities: EntityInfo[],
schema: string
): Promise<EntityInfo[]> {
let response: any[][] = (await this.Connection
.execute(`select owner.TABLE_NAME ownTbl,ownCol.POSITION,ownCol.COLUMN_NAME,
child.TABLE_NAME,childCol.COLUMN_NAME,
let response: {
OWNER_TABLE_NAME: string;
OWNER_POSITION: string;
OWNER_COLUMN_NAME: string;
CHILD_TABLE_NAME: string;
CHILD_COLUMN_NAME: string;
DELETE_RULE: "RESTRICT" | "CASCADE" | "SET NULL" | "NO ACTION";
CONSTRAINT_NAME: string;
}[] = (await this.Connection
.execute(`select owner.TABLE_NAME OWNER_TABLE_NAME,ownCol.POSITION OWNER_POSITION,ownCol.COLUMN_NAME OWNER_COLUMN_NAME,
child.TABLE_NAME CHILD_TABLE_NAME ,childCol.COLUMN_NAME CHILD_COLUMN_NAME,
owner.DELETE_RULE,
4,owner.CONSTRAINT_NAME
owner.CONSTRAINT_NAME
from user_constraints owner
join user_constraints child on owner.r_constraint_name=child.CONSTRAINT_NAME and child.constraint_type in ('P','U')
JOIN USER_CONS_COLUMNS ownCol ON owner.CONSTRAINT_NAME = ownCol.CONSTRAINT_NAME
JOIN USER_CONS_COLUMNS childCol ON child.CONSTRAINT_NAME = childCol.CONSTRAINT_NAME AND ownCol.POSITION=childCol.POSITION
ORDER BY ownTbl ASC, owner.CONSTRAINT_NAME ASC, ownCol.POSITION ASC`))
ORDER BY OWNER_TABLE_NAME ASC, owner.CONSTRAINT_NAME ASC, OWNER_POSITION ASC`))
.rows!;
let relationsTemp: RelationTempInfo[] = <RelationTempInfo[]>[];
response.forEach(resp => {
let rels = relationsTemp.find(val => {
return val.object_id == resp[6];
return val.object_id == resp.CONSTRAINT_NAME;
});
if (rels == undefined) {
rels = <RelationTempInfo>{};
rels.ownerColumnsNames = [];
rels.referencedColumnsNames = [];
rels.actionOnDelete = resp[5];
rels.actionOnDelete = resp.DELETE_RULE;
rels.actionOnUpdate = "NO ACTION";
rels.object_id = resp[6];
rels.ownerTable = resp[0];
rels.referencedTable = resp[3];
rels.object_id = resp.CONSTRAINT_NAME;
rels.ownerTable = resp.OWNER_TABLE_NAME;
rels.referencedTable = resp.CHILD_TABLE_NAME;
relationsTemp.push(rels);
}
rels.ownerColumnsNames.push(resp[2]);
rels.referencedColumnsNames.push(resp[4]);
rels.ownerColumnsNames.push(resp.OWNER_COLUMN_NAME);
rels.referencedColumnsNames.push(resp.CHILD_COLUMN_NAME);
});
relationsTemp.forEach(relationTmp => {
let ownerEntity = entities.find(entitity => {
@ -298,20 +424,32 @@ export class OracleDriver extends AbstractDriver {
password: string,
ssl: boolean
) {
let config: any /*Oracle.IConnectionAttributes*/ = {
user: user,
password: password,
// connectString: `${server}:${port}/ORCLCDB.localdomain/${database}`,
connectString: `${server}:${port}/${database}`,
externalAuth: ssl
};
let config: any;
if (user == String(process.env.ORACLE_UsernameSys)) {
config /*Oracle.IConnectionAttributes*/ = {
user: user,
password: password,
// connectString: `${server}:${port}/ORCLCDB.localdomain/${database}`,
connectString: `${server}:${port}/${database}`,
externalAuth: ssl,
privilege: this.Oracle.SYSDBA
};
} else {
config /*Oracle.IConnectionAttributes*/ = {
user: user,
password: password,
// connectString: `${server}:${port}/ORCLCDB.localdomain/${database}`,
connectString: `${server}:${port}/${database}`,
externalAuth: ssl
};
}
let that = this;
let promise = new Promise<boolean>((resolve, reject) => {
this.Oracle.getConnection(config, function(err, connection) {
if (!err) {
//Connection successfull
that.Connection = connection;
resolve(true);
} else {
TomgUtils.LogError(
@ -327,10 +465,23 @@ export class OracleDriver extends AbstractDriver {
await promise;
}
async CreateDB(dbName: string) {}
async CreateDB(dbName: string) {
var x = await this.Connection.execute(
`CREATE USER ${dbName} IDENTIFIED BY ${String(
process.env.ORACLE_Password
)}`
);
var y = await this.Connection.execute(`GRANT CONNECT TO ${dbName}`);
}
async UseDB(dbName: string) {}
async DropDB(dbName: string) {}
async DropDB(dbName: string) {
var x = await this.Connection.execute(`DROP USER ${dbName} CASCADE`);
}
async CheckIfDBExists(dbName: string): Promise<boolean> {
return true;
var x = await this.Connection.execute(
`select count(*) as CNT from dba_users where username='${dbName.toUpperCase()}'`
);
return x.rows[0][0] > 0 || x.rows[0].CNT;
}
}

View File

@ -11,7 +11,8 @@ import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, Man
{{^relations}} @Column("{{sql_type}}",{ {{#is_generated}}
generated:true,{{/is_generated}}{{#is_nullable}}
nullable:true,{{/is_nullable}}{{^is_nullable}}
nullable:false,{{/is_nullable}}{{#lenght}}
nullable:false,{{/is_nullable}}{{#is_unique}}
unique: true,{{/is_unique}}{{#lenght}}
length:{{.}},{{/lenght}}{{#width}}
width:{{.}},{{/width}}{{#default}}
default:"{{.}}",{{/default}}{{#numericPrecision}}

View File

@ -39,7 +39,7 @@ var argv = Yargs.usage(
.option("e", {
alias: "engine",
describe: "Database engine.",
choices: ["mssql", "postgres", "mysql", "mariadb"],
choices: ["mssql", "postgres", "mysql", "mariadb", "oracle"],
default: "mssql"
})
.option("o", {

View File

@ -7,6 +7,7 @@ export class ColumnInfo {
name: string = "";
default: string | null = null;
is_nullable: boolean = false;
is_unique: boolean = false;
ts_type:
| "number"
| "string"

View File

@ -106,6 +106,7 @@ describe('MssqlDriver', function () {
sql_type: 'int',
ts_type: 'number',
enumOptions: null,
is_unique:false,
relations: <RelationInfo[]>[]
})
let result = await driver.GetCoulmnsFromEntity(entities, 'schema');

View File

@ -24,6 +24,7 @@ describe("Platform specyfic types", async function () {
if (process.env.MYSQL_Skip == '0') dbDrivers.push('mysql')
if (process.env.MARIADB_Skip == '0') dbDrivers.push('mariadb')
if (process.env.MSSQL_Skip == '0') dbDrivers.push('mssql')
if (process.env.ORACLE_Skip == '0') dbDrivers.push('oracle')
let examplesPathJS = path.resolve(process.cwd(), 'dist/test/integration/entityTypes')
@ -55,6 +56,9 @@ describe("Platform specyfic types", async function () {
case 'mariadb':
engine = await GTU.createMariaDBModels(filesOrgPathJS, resultsPath)
break;
case 'oracle':
engine = await GTU.createOracleDBModels(filesOrgPathJS, resultsPath)
break;
default:
console.log(`Unknown engine type`);

View File

@ -0,0 +1,99 @@
import { Entity, PrimaryColumn, Column } from "typeorm";
@Entity("Post")
export class Post {
@PrimaryColumn()
id: number;
@Column()
name: string;
@Column("char")
char: string;
@Column("nchar")
nchar: string;
@Column("nvarchar2")
nvarchar2: string;
@Column("varchar2")
varchar2: string;
@Column("long")
long: string;
@Column("raw")
raw: Buffer;
// @Column("long raw")
// long_raw: Buffer;
@Column("number")
number: number;
@Column("numeric")
numeric: number;
@Column("float")
float: number;
@Column("dec")
dec: number;
@Column("decimal")
decimal: number;
@Column("integer")
integer: number;
@Column("int")
int: number;
@Column("smallint")
smallint: number;
@Column("real")
real: number;
@Column("double precision")
double_precision: number;
@Column("date")
date: Date;
@Column("timestamp")
timestamp: Date;
@Column("timestamp with time zone")
timestamp_with_time_zone: Date;
@Column("timestamp with local time zone")
timestamp_with_local_time_zone: Date;
@Column("interval year to month")
interval_year_to_month: string;
@Column("interval day to second")
interval_day_to_second: string;
@Column("bfile")
bfile: Buffer;
@Column("blob")
blob: Buffer;
@Column("clob")
clob: string;
@Column("nclob")
nclob: string;
@Column("rowid")
rowid: number;
@Column("urowid")
urowid: number;
}

View File

@ -9,8 +9,8 @@ export class EverythingEntity {
@Column()
name: string;
@Column("text")
text: string;
// @Column("text")
// text: string;
@Column({ length: 32 })
shortTextColumn: string;

View File

@ -24,7 +24,7 @@ export class Post {
onDelete: 'CASCADE'
})
@JoinColumn()
@Index({ unique: true })
// @Index({ unique: true })
category: PostCategory;
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
@ -33,7 +33,7 @@ export class Post {
cascade: true
})
@JoinColumn()
@Index({ unique: true })
// @Index({ unique: true })
details: PostDetails;
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
@ -42,7 +42,7 @@ export class Post {
cascade: true,
})
@JoinColumn()
@Index({ unique: true })
// @Index({ unique: true })
image: PostImage;
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
@ -51,7 +51,7 @@ export class Post {
onDelete: 'CASCADE'
})
@JoinColumn()
@Index({ unique: true })
// @Index({ unique: true })
metadata: PostMetadata | null;
// post has relation with details. full cascades here
@ -60,13 +60,13 @@ export class Post {
onDelete: 'CASCADE'
})
@JoinColumn()
@Index({ unique: true })
// @Index({ unique: true })
information: PostInformation;
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
@OneToOne(type => PostAuthor, author => author.post)
@JoinColumn()
@Index({ unique: true })
// @Index({ unique: true })
author: PostAuthor;
}

View File

@ -241,10 +241,11 @@ export async function createMariaDBModels(filesOrgPath: string, resultsPath: str
export async function createOracleDBModels(filesOrgPath: string, resultsPath: string): Promise<Engine> {
let driver: AbstractDriver;
driver = new OracleDriver();
await driver.ConnectToServer(String(process.env.ORACLE_Database), String(process.env.ORACLE_Host), Number(process.env.ORACLE_Port), String(process.env.ORACLE_Username), String(process.env.ORACLE_Password), yn(process.env.ORACLE_SSL));
await driver.ConnectToServer(String(process.env.ORACLE_Database), String(process.env.ORACLE_Host), Number(process.env.ORACLE_Port), String(process.env.ORACLE_UsernameSys), String(process.env.ORACLE_PasswordSys), yn(process.env.ORACLE_SSL));
if (! await driver.CheckIfDBExists(String(process.env.ORACLE_Database)))
await driver.CreateDB(String(process.env.ORACLE_Database));
if (await driver.CheckIfDBExists(String(process.env.ORACLE_Username)))
await driver.DropDB(String(process.env.ORACLE_Username));
await driver.CreateDB(String(process.env.ORACLE_Username));
await driver.DisconnectFromServer();
let connOpt: ConnectionOptions = {
@ -281,11 +282,8 @@ export async function createOracleDBModels(filesOrgPath: string, resultsPath: st
convertCaseEntity: 'none',
convertCaseFile: 'none',
convertCaseProperty: 'none',
});
return engine;
}

View File

@ -13,7 +13,10 @@
"strictNullChecks": true,
"moduleResolution": "node",
"outDir": "dist",
"newLine": "LF"
"newLine": "LF",
"lib": [
"es2017"
]
},
"include": [
"src",