From d1407ab5d946d12419fba615a7b92b272ad124e3 Mon Sep 17 00:00:00 2001 From: Kononnable Date: Thu, 10 Oct 2019 22:18:22 +0200 Subject: [PATCH] oracledb implementation --- src/drivers/OracleDriver.ts | 519 +++++++++--------- .../entityTypes/oracle/entity/Post.ts | 12 +- .../sample18-lazy-relations/entity/Post.ts | 4 +- 3 files changed, 278 insertions(+), 257 deletions(-) diff --git a/src/drivers/OracleDriver.ts b/src/drivers/OracleDriver.ts index d58eeba..971044a 100644 --- a/src/drivers/OracleDriver.ts +++ b/src/drivers/OracleDriver.ts @@ -2,13 +2,12 @@ import * as TypeormDriver from "typeorm/driver/oracle/OracleDriver"; import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults"; import * as TomgUtils from "../Utils"; import AbstractDriver from "./AbstractDriver"; -import EntityInfo from "../oldModels/EntityInfo"; -import ColumnInfo from "../oldModels/ColumnInfo"; -import IndexInfo from "../oldModels/IndexInfo"; -import IndexColumnInfo from "../oldModels/IndexColumnInfo"; -import RelationTempInfo from "../oldModels/RelationTempInfo"; import IConnectionOptions from "../IConnectionOptions"; import { Entity } from "../models/Entity"; +import { Column } from "../models/Column"; +import { Index } from "../models/Index"; +import IGenerationOptions from "../IGenerationOptions"; +import { RelationInternal } from "../models/RelationInternal"; export default class OracleDriver extends AbstractDriver { public defaultValues: DataTypeDefaults = new TypeormDriver.OracleDriver({ @@ -49,257 +48,281 @@ export default class OracleDriver extends AbstractDriver { }; public async GetCoulmnsFromEntity(entities: Entity[]): Promise { - throw new Error(); - // TODO: Remove - // const 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!; + const 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 => filterVal.TABLE_NAME === ent.tsEntityName) - // .forEach(resp => { - // const colInfo: ColumnInfo = new ColumnInfo(); - // colInfo.tsName = resp.COLUMN_NAME; - // colInfo.options.name = resp.COLUMN_NAME; - // colInfo.options.nullable = resp.NULLABLE === "Y"; - // colInfo.options.generated = resp.IDENTITY_COLUMN === "YES"; - // colInfo.options.default = - // !resp.DATA_DEFAULT || resp.DATA_DEFAULT.includes('"') - // ? null - // : OracleDriver.ReturnDefaultValueFunction( - // resp.DATA_DEFAULT - // ); - // colInfo.options.unique = resp.IS_UNIQUE > 0; - // const DATA_TYPE = resp.DATA_TYPE.replace(/\([0-9]+\)/g, ""); - // colInfo.options.type = DATA_TYPE.toLowerCase() as any; - // switch (DATA_TYPE.toLowerCase()) { - // case "char": - // colInfo.tsType = "string"; - // break; - // case "nchar": - // colInfo.tsType = "string"; - // break; - // case "nvarchar2": - // colInfo.tsType = "string"; - // break; - // case "varchar2": - // colInfo.tsType = "string"; - // break; - // case "long": - // colInfo.tsType = "string"; - // break; - // case "raw": - // colInfo.tsType = "Buffer"; - // break; - // case "long raw": - // colInfo.tsType = "Buffer"; - // break; - // case "number": - // colInfo.tsType = "number"; - // break; - // case "numeric": - // colInfo.tsType = "number"; - // break; - // case "float": - // colInfo.tsType = "number"; - // break; - // case "dec": - // colInfo.tsType = "number"; - // break; - // case "decimal": - // colInfo.tsType = "number"; - // break; - // case "integer": - // colInfo.tsType = "number"; - // break; - // case "int": - // colInfo.tsType = "number"; - // break; - // case "smallint": - // colInfo.tsType = "number"; - // break; - // case "real": - // colInfo.tsType = "number"; - // break; - // case "double precision": - // colInfo.tsType = "number"; - // break; - // case "date": - // colInfo.tsType = "Date"; - // break; - // case "timestamp": - // colInfo.tsType = "Date"; - // break; - // case "timestamp with time zone": - // colInfo.tsType = "Date"; - // break; - // case "timestamp with local time zone": - // colInfo.tsType = "Date"; - // break; - // case "interval year to month": - // colInfo.tsType = "string"; - // break; - // case "interval day to second": - // colInfo.tsType = "string"; - // break; - // case "bfile": - // colInfo.tsType = "Buffer"; - // break; - // case "blob": - // colInfo.tsType = "Buffer"; - // break; - // case "clob": - // colInfo.tsType = "string"; - // break; - // case "nclob": - // colInfo.tsType = "string"; - // break; - // case "rowid": - // colInfo.tsType = "number"; - // break; - // case "urowid": - // colInfo.tsType = "number"; - // break; - // default: - // TomgUtils.LogError( - // `Unknown column type:${DATA_TYPE}` - // ); - // break; - // } - // if ( - // this.ColumnTypesWithPrecision.some( - // v => v === colInfo.options.type - // ) - // ) { - // colInfo.options.precision = resp.DATA_PRECISION; - // colInfo.options.scale = resp.DATA_SCALE; - // } - // if ( - // this.ColumnTypesWithLength.some( - // v => v === colInfo.options.type - // ) - // ) { - // colInfo.options.length = - // resp.DATA_LENGTH > 0 ? resp.DATA_LENGTH : undefined; - // } + entities.forEach(ent => { + response + .filter(filterVal => filterVal.TABLE_NAME === ent.tscName) + .forEach(resp => { + const tscName = resp.COLUMN_NAME; + const options: Partial = {}; + options.name = resp.COLUMN_NAME; + if (resp.NULLABLE === "Y") options.nullable = true; + if (resp.IS_UNIQUE > 0) options.unique = true; + const generated = + resp.IDENTITY_COLUMN === "YES" ? true : undefined; + const defaultValue = + !resp.DATA_DEFAULT || resp.DATA_DEFAULT.includes('"') + ? undefined + : OracleDriver.ReturnDefaultValueFunction( + resp.DATA_DEFAULT + ); + const DATA_TYPE = resp.DATA_TYPE.replace(/\([0-9]+\)/g, ""); + const columnType = DATA_TYPE.toLowerCase() as any; + let tscType = ""; + switch (DATA_TYPE.toLowerCase()) { + case "char": + tscType = "string"; + break; + case "nchar": + tscType = "string"; + break; + case "nvarchar2": + tscType = "string"; + break; + case "varchar2": + tscType = "string"; + break; + case "long": + tscType = "string"; + break; + case "raw": + tscType = "Buffer"; + break; + case "long raw": + tscType = "Buffer"; + break; + case "number": + tscType = "number"; + break; + case "numeric": + tscType = "number"; + break; + case "float": + tscType = "number"; + break; + case "dec": + tscType = "number"; + break; + case "decimal": + tscType = "number"; + break; + case "integer": + tscType = "number"; + break; + case "int": + tscType = "number"; + break; + case "smallint": + tscType = "number"; + break; + case "real": + tscType = "number"; + break; + case "double precision": + tscType = "number"; + break; + case "date": + tscType = "Date"; + break; + case "timestamp": + tscType = "Date"; + break; + case "timestamp with time zone": + tscType = "Date"; + break; + case "timestamp with local time zone": + tscType = "Date"; + break; + case "interval year to month": + tscType = "string"; + break; + case "interval day to second": + tscType = "string"; + break; + case "bfile": + tscType = "Buffer"; + break; + case "blob": + tscType = "Buffer"; + break; + case "clob": + tscType = "string"; + break; + case "nclob": + tscType = "string"; + break; + case "rowid": + tscType = "number"; + break; + case "urowid": + tscType = "number"; + break; + default: + TomgUtils.LogError( + `Unknown column type:${DATA_TYPE}` + ); + break; + } + if ( + this.ColumnTypesWithPrecision.some( + v => v === columnType + ) + ) { + if (resp.DATA_PRECISION !== null) { + options.precision = resp.DATA_PRECISION; + } + if (resp.DATA_SCALE !== null) { + options.scale = resp.DATA_SCALE; + } + } + if ( + this.ColumnTypesWithLength.some(v => v === columnType) + ) { + options.length = + resp.DATA_LENGTH > 0 ? resp.DATA_LENGTH : undefined; + } - // if (colInfo.options.type) { - // ent.Columns.push(colInfo); - // } - // }); - // }); - // return entities; + if (columnType) { + ent.columns.push({ + generated, + type: columnType, + default: defaultValue, + options: { name: "", ...options }, // TODO: Change + tscName, + tscType + }); + } + }); + }); + return entities; } public async GetIndexesFromEntity(entities: Entity[]): Promise { - throw new Error(); - // TODO: Remove - // const 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 - // ORDER BY col.INDEX_NAME ASC ,col.COLUMN_POSITION ASC`)).rows!; + const 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 + ORDER BY col.INDEX_NAME ASC ,col.COLUMN_POSITION ASC`)).rows!; - // entities.forEach(ent => { - // response - // .filter(filterVal => filterVal.TABLE_NAME === ent.tsEntityName) - // .forEach(resp => { - // let indexInfo: IndexInfo = {} as IndexInfo; - // const indexColumnInfo: IndexColumnInfo = {} as IndexColumnInfo; - // if ( - // ent.Indexes.filter( - // filterVal => filterVal.name === resp.INDEX_NAME - // ).length > 0 - // ) { - // indexInfo = ent.Indexes.find( - // filterVal => filterVal.name === resp.INDEX_NAME - // )!; - // } else { - // indexInfo.columns = [] as IndexColumnInfo[]; - // indexInfo.name = resp.INDEX_NAME; - // indexInfo.isUnique = resp.UNIQUENESS === "UNIQUE"; - // indexInfo.isPrimaryKey = resp.ISPRIMARYKEY === 1; - // ent.Indexes.push(indexInfo); - // } - // indexColumnInfo.name = resp.COLUMN_NAME; - // indexInfo.columns.push(indexColumnInfo); - // }); - // }); + entities.forEach(ent => { + const entityIndices = response.filter( + filterVal => filterVal.TABLE_NAME === ent.tscName + ); + const indexNames = new Set(entityIndices.map(v => v.INDEX_NAME)); + indexNames.forEach(indexName => { + const records = entityIndices.filter( + v => v.INDEX_NAME === indexName + ); + const indexInfo: Index = { + columns: [], + options: {}, + name: records[0].INDEX_NAME + }; + if (records[0].ISPRIMARYKEY === 1) indexInfo.primary = true; + if (records[0].UNIQUENESS === "UNIQUE") + indexInfo.options.unique = true; + records.forEach(record => { + indexInfo.columns.push(record.COLUMN_NAME); + }); + ent.indices.push(indexInfo); + }); + }); - // return entities; + return entities; } - public async GetRelations(entities: Entity[]): Promise { - throw new Error(); - // TODO: Remove - // const 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, - // 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 OWNER_TABLE_NAME ASC, owner.CONSTRAINT_NAME ASC, OWNER_POSITION ASC`)) - // .rows!; + public async GetRelations( + entities: Entity[], + schema: string, + dbNames: string, + generationOptions: IGenerationOptions + ): Promise { + const 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, + 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 OWNER_TABLE_NAME ASC, owner.CONSTRAINT_NAME ASC, OWNER_POSITION ASC`)) + .rows!; - // const relationsTemp: RelationTempInfo[] = [] as RelationTempInfo[]; - // response.forEach(resp => { - // let rels = relationsTemp.find( - // val => val.objectId === resp.CONSTRAINT_NAME - // ); - // if (rels === undefined) { - // rels = {} as RelationTempInfo; - // rels.ownerColumnsNames = []; - // rels.referencedColumnsNames = []; - // rels.actionOnDelete = - // resp.DELETE_RULE === "NO ACTION" ? null : resp.DELETE_RULE; - // rels.actionOnUpdate = null; - // rels.objectId = resp.CONSTRAINT_NAME; - // rels.ownerTable = resp.OWNER_TABLE_NAME; - // rels.referencedTable = resp.CHILD_TABLE_NAME; - // relationsTemp.push(rels); - // } - // rels.ownerColumnsNames.push(resp.OWNER_COLUMN_NAME); - // rels.referencedColumnsNames.push(resp.CHILD_COLUMN_NAME); - // }); - // const retVal = OracleDriver.GetRelationsFromRelationTempInfo( - // relationsTemp, - // entities - // ); - // return retVal; + const relationsTemp: RelationInternal[] = [] as RelationInternal[]; + const relationKeys = new Set(response.map(v => v.CONSTRAINT_NAME)); + + relationKeys.forEach(relationId => { + const rows = response.filter(v => v.CONSTRAINT_NAME === relationId); + const ownerTable = entities.find( + v => v.sqlName === rows[0].OWNER_TABLE_NAME + ); + const relatedTable = entities.find( + v => v.sqlName === rows[0].CHILD_TABLE_NAME + ); + + if (!ownerTable || !relatedTable) { + TomgUtils.LogError( + `Relation between tables ${rows[0].OWNER_TABLE_NAME} and ${rows[0].CHILD_TABLE_NAME} wasn't found in entity model.`, + true + ); + return; + } + const internal: RelationInternal = { + ownerColumns: [], + relatedColumns: [], + ownerTable, + relatedTable + }; + if (rows[0].DELETE_RULE !== "NO ACTION") { + internal.onDelete = rows[0].DELETE_RULE; + } + rows.forEach(row => { + internal.ownerColumns.push(row.OWNER_COLUMN_NAME); + internal.relatedColumns.push(row.CHILD_COLUMN_NAME); + }); + relationsTemp.push(internal); + }); + + const retVal = OracleDriver.GetRelationsFromRelationTempInfo( + relationsTemp, + entities, + generationOptions + ); + return retVal; } public async DisconnectFromServer() { @@ -373,10 +396,10 @@ export default class OracleDriver extends AbstractDriver { private static ReturnDefaultValueFunction( defVal: string | null - ): string | null { + ): string | undefined { let defaultVal = defVal; if (!defaultVal) { - return null; + return undefined; } if (defaultVal.endsWith(" ")) { defaultVal = defaultVal.slice(0, -1); diff --git a/test/integration/entityTypes/oracle/entity/Post.ts b/test/integration/entityTypes/oracle/entity/Post.ts index ea69e96..ced4844 100644 --- a/test/integration/entityTypes/oracle/entity/Post.ts +++ b/test/integration/entityTypes/oracle/entity/Post.ts @@ -2,7 +2,6 @@ import { Entity, PrimaryColumn, Column } from "typeorm"; @Entity("Post") export class Post { - @PrimaryColumn() id: number; @@ -58,7 +57,7 @@ export class Post { real: number; @Column("double precision") - double_precision: number; + doublePrecision: number; @Column("date") date: Date; @@ -67,16 +66,16 @@ export class Post { timestamp: Date; @Column("timestamp with time zone") - timestamp_with_time_zone: Date; + timestampWithTimeZone: Date; @Column("timestamp with local time zone") - timestamp_with_local_time_zone: Date; + timestampWithLocalTimeZone: Date; @Column("interval year to month") - interval_year_to_month: string; + intervalYearToMonth: string; @Column("interval day to second") - interval_day_to_second: string; + intervalDayToSecond: string; @Column("bfile") bfile: Buffer; @@ -95,5 +94,4 @@ export class Post { @Column("urowid") urowid: number; - } diff --git a/test/integration/examples/sample18-lazy-relations/entity/Post.ts b/test/integration/examples/sample18-lazy-relations/entity/Post.ts index 2283783..e5418d6 100644 --- a/test/integration/examples/sample18-lazy-relations/entity/Post.ts +++ b/test/integration/examples/sample18-lazy-relations/entity/Post.ts @@ -24,8 +24,8 @@ export class Post { @ManyToOne(type => Author, author => author.posts, { lazy: true, // cascade: ["insert"], - onDelete: "SET NULL", - onUpdate: "CASCADE" + onDelete: "SET NULL" + // onUpdate: "CASCADE" - onUpdate not supported on oracledb }) author: Promise;