diff --git a/src/drivers/MssqlDriver.ts b/src/drivers/MssqlDriver.ts index 3b9b4fe..338603f 100644 --- a/src/drivers/MssqlDriver.ts +++ b/src/drivers/MssqlDriver.ts @@ -59,7 +59,9 @@ export class MssqlDriver extends AbstractDriver { colInfo.isNullable = resp.IS_NULLABLE === "YES"; colInfo.isGenerated = resp.IsIdentity === 1; colInfo.isUnique = resp.IsUnique === 1; - colInfo.default = resp.COLUMN_DEFAULT; + colInfo.default = this.ReturnDefaultValueFunction( + resp.COLUMN_DEFAULT + ); colInfo.sqlType = resp.DATA_TYPE; switch (resp.DATA_TYPE) { case "bigint": @@ -404,4 +406,16 @@ order by ); return resp.recordset.length > 0; } + private ReturnDefaultValueFunction(defVal: string | null): string | null { + if (!defVal) { + return null; + } + if (defVal.startsWith("(") && defVal.endsWith(")")) { + defVal = defVal.slice(1, -1); + } + if (defVal.startsWith(`'`)) { + return `() => "${defVal}"`; + } + return `() => "${defVal}"`; + } } diff --git a/src/drivers/MysqlDriver.ts b/src/drivers/MysqlDriver.ts index b9111be..ba7ff4d 100644 --- a/src/drivers/MysqlDriver.ts +++ b/src/drivers/MysqlDriver.ts @@ -50,7 +50,9 @@ export class MysqlDriver extends AbstractDriver { colInfo.isNullable = resp.IS_NULLABLE === "YES"; colInfo.isGenerated = resp.IsIdentity === 1; colInfo.isUnique = resp.column_key === "UNI"; - colInfo.default = resp.COLUMN_DEFAULT; + colInfo.default = this.ReturnDefaultValueFunction( + resp.COLUMN_DEFAULT + ); colInfo.sqlType = resp.DATA_TYPE; switch (resp.DATA_TYPE) { case "int": @@ -409,4 +411,13 @@ export class MysqlDriver extends AbstractDriver { await promise; return ret; } + private ReturnDefaultValueFunction(defVal: string | null): string | null { + if (!defVal) { + return null; + } + if (defVal === "CURRENT_TIMESTAMP" || defVal.startsWith(`'`)) { + return `() => "${defVal}"`; + } + return `() => "'${defVal}'"`; + } } diff --git a/src/drivers/OracleDriver.ts b/src/drivers/OracleDriver.ts index bf9d143..c1b0e12 100644 --- a/src/drivers/OracleDriver.ts +++ b/src/drivers/OracleDriver.ts @@ -63,7 +63,9 @@ export class OracleDriver extends AbstractDriver { colInfo.default = !resp.DATA_DEFAULT || resp.DATA_DEFAULT.includes('"') ? null - : resp.DATA_DEFAULT; + : this.ReturnDefaultValueFunction( + resp.DATA_DEFAULT + ); colInfo.isUnique = resp.IS_UNIQUE > 0; resp.DATA_TYPE = resp.DATA_TYPE.replace(/\([0-9]+\)/g, ""); colInfo.sqlType = resp.DATA_TYPE.toLowerCase(); @@ -350,4 +352,16 @@ export class OracleDriver extends AbstractDriver { ); return x.rows[0][0] > 0 || x.rows[0].CNT; } + private ReturnDefaultValueFunction(defVal: string | null): string | null { + if (!defVal) { + return null; + } + if (defVal.endsWith(" ")) { + defVal = defVal.slice(0, -1); + } + if (defVal.startsWith(`'`)) { + return `() => "${defVal}"`; + } + return `() => "${defVal}"`; + } } diff --git a/src/drivers/PostgresDriver.ts b/src/drivers/PostgresDriver.ts index 164d788..4716818 100644 --- a/src/drivers/PostgresDriver.ts +++ b/src/drivers/PostgresDriver.ts @@ -60,7 +60,7 @@ export class PostgresDriver extends AbstractDriver { colInfo.isUnique = resp.isunique === "1"; colInfo.default = colInfo.isGenerated ? null - : resp.column_default; + : this.ReturnDefaultValueFunction(resp.column_default); const columnTypes = this.MatchColumnTypes( resp.data_type, @@ -577,4 +577,14 @@ export class PostgresDriver extends AbstractDriver { ); return resp.rowCount > 0; } + private ReturnDefaultValueFunction(defVal: string | null): string | null { + if (!defVal) { + return null; + } + defVal = defVal.replace(/'::[\w ]*/, "'"); + if (defVal.startsWith(`'`)) { + return `() => "${defVal}"`; + } + return `() => "${defVal}"`; + } } diff --git a/src/drivers/SqliteDriver.ts b/src/drivers/SqliteDriver.ts index 5e1cfc1..b0799a0 100644 --- a/src/drivers/SqliteDriver.ts +++ b/src/drivers/SqliteDriver.ts @@ -45,7 +45,9 @@ export class SqliteDriver extends AbstractDriver { colInfo.sqlName = resp.name; colInfo.isNullable = resp.notnull === 0; colInfo.isPrimary = resp.pk > 0; - colInfo.default = resp.dflt_value ? resp.dflt_value : null; + colInfo.default = this.ReturnDefaultValueFunction( + resp.dflt_value + ); colInfo.sqlType = resp.type .replace(/\([0-9 ,]+\)/g, "") .toLowerCase() @@ -344,4 +346,13 @@ export class SqliteDriver extends AbstractDriver { await promise; return ret; } + private ReturnDefaultValueFunction(defVal: string | null): string | null { + if (!defVal) { + return null; + } + if (defVal.startsWith(`'`)) { + return `() => "${defVal}"`; + } + return `() => "${defVal}"`; + } } diff --git a/src/entity.mst b/src/entity.mst index eb5388b..2a8fa93 100644 --- a/src/entity.mst +++ b/src/entity.mst @@ -16,7 +16,7 @@ import {BaseEntity,Column,Entity,Index,JoinColumn,JoinTable,ManyToMany,ManyToOne unique: true,{{/isUnique}}{{#lenght}} length:{{.}},{{/lenght}}{{#width}} width:{{.}},{{/width}}{{#default}} - default:"{{.}}",{{/default}}{{#numericPrecision}} + default: {{.}},{{/default}}{{#numericPrecision}} precision:{{.}},{{/numericPrecision}}{{#numericScale}} scale:{{.}},{{/numericScale}}{{#enumOptions}} enum:[{{.}}],{{/enumOptions}}{{#isArray}} diff --git a/test/drivers/MssqlDriver.test.ts b/test/drivers/MssqlDriver.test.ts index 8ddb9c1..30448e6 100644 --- a/test/drivers/MssqlDriver.test.ts +++ b/test/drivers/MssqlDriver.test.ts @@ -65,7 +65,7 @@ describe('MssqlDriver', function () { response.recordset = new fakeRecordset(); response.recordset.push({ TABLE_NAME: 'name', CHARACTER_MAXIMUM_LENGTH: 0, - COLUMN_DEFAULT: 'a', COLUMN_NAME: 'name', DATA_TYPE: 'int', + COLUMN_DEFAULT: "'a'", COLUMN_NAME: 'name', DATA_TYPE: 'int', IS_NULLABLE: 'YES', NUMERIC_PRECISION: 0, NUMERIC_SCALE: 0, IsIdentity: 1 }) @@ -82,7 +82,7 @@ describe('MssqlDriver', function () { const expected: EntityInfo[] = JSON.parse(JSON.stringify(entities)); expected[0].Columns.push({ lenght: null, - default: 'a', + default: `() => "'a'"`, isNullable: true, isPrimary: false, isGenerated: true, diff --git a/test/integration/defaultValues.test.ts b/test/integration/defaultValues.test.ts new file mode 100644 index 0000000..2e536af --- /dev/null +++ b/test/integration/defaultValues.test.ts @@ -0,0 +1,96 @@ +require('dotenv').config() +import { expect } from "chai"; +import fs = require('fs-extra'); +import path = require('path') +import "reflect-metadata"; +import { EntityFileToJson } from "../utils/EntityFileToJson"; +const chai = require('chai'); +const chaiSubset = require('chai-subset'); +import * as ts from "typescript"; +import { Engine } from "../../src/Engine"; +import * as GTU from "../utils/GeneralTestUtils" + +chai.use(chaiSubset); + +describe("Column default values", async function () { + this.timeout(30000) + this.slow(5000)// compiling created models takes time + + const dbDrivers: string[] = [] + if (process.env.SQLITE_Skip == '0') { dbDrivers.push('sqlite') } + 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') } + if (process.env.MSSQL_Skip == '0') { dbDrivers.push('mssql') } + if (process.env.ORACLE_Skip == '0') { dbDrivers.push('oracle') } + + const examplesPathJS = path.resolve(process.cwd(), 'dist/test/integration/defaultValues') + const examplesPathTS = path.resolve(process.cwd(), 'test/integration/defaultValues') + const files = fs.readdirSync(examplesPathTS) + + for (const dbDriver of dbDrivers) { + for (const folder of files) { + if (dbDriver == folder) { + it(dbDriver, async function () { + + const filesOrgPathJS = path.resolve(examplesPathJS, folder, 'entity') + const filesOrgPathTS = path.resolve(examplesPathTS, folder, 'entity') + const resultsPath = path.resolve(process.cwd(), `output`) + fs.removeSync(resultsPath) + + let engine: Engine; + switch (dbDriver) { + case 'sqlite': + engine = await GTU.createSQLiteModels(filesOrgPathJS, resultsPath) + break; + case 'postgres': + engine = await GTU.createPostgresModels(filesOrgPathJS, resultsPath) + break; + case 'mysql': + engine = await GTU.createMysqlModels(filesOrgPathJS, resultsPath) + break; + case 'mariadb': + engine = await GTU.createMariaDBModels(filesOrgPathJS, resultsPath) + break; + case 'mssql': + engine = await GTU.createMSSQLModels(filesOrgPathJS, resultsPath) + break; + case 'oracle': + engine = await GTU.createOracleDBModels(filesOrgPathJS, resultsPath) + break; + default: + console.log(`Unknown engine type`); + engine = {} as Engine + break; + } + + await engine.createModelFromDatabase() + const filesGenPath = path.resolve(resultsPath, 'entities') + + const filesOrg = fs.readdirSync(filesOrgPathTS).filter((val) => val.toString().endsWith('.ts')) + const filesGen = fs.readdirSync(filesGenPath).filter((val) => val.toString().endsWith('.ts')) + + expect(filesOrg, 'Errors detected in model comparision').to.be.deep.equal(filesGen) + + for (const file of filesOrg) { + const entftj = new EntityFileToJson(); + const jsonEntityOrg = entftj.convert(fs.readFileSync(path.resolve(filesOrgPathTS, file))) + const jsonEntityGen = entftj.convert(fs.readFileSync(path.resolve(filesGenPath, file))) + expect(jsonEntityGen, `Error in file ${file}`).to.containSubset(jsonEntityOrg) + } + const currentDirectoryFiles = fs.readdirSync(filesGenPath). + filter(fileName => fileName.length >= 3 && fileName.substr(fileName.length - 3, 3) === ".ts").map(v => path.resolve(filesGenPath, v)) + const compileErrors = GTU.compileTsFiles(currentDirectoryFiles, { + experimentalDecorators: true, + sourceMap: false, + emitDecoratorMetadata: true, + target: ts.ScriptTarget.ES2016, + moduleResolution: ts.ModuleResolutionKind.NodeJs, + module: ts.ModuleKind.CommonJS + }); + expect(compileErrors, 'Errors detected while compiling generated model').to.be.false; + }); + } + } + } +}) diff --git a/test/integration/defaultValues/mariadb/entity/Post.ts b/test/integration/defaultValues/mariadb/entity/Post.ts new file mode 100644 index 0000000..e81e4c3 --- /dev/null +++ b/test/integration/defaultValues/mariadb/entity/Post.ts @@ -0,0 +1,21 @@ +import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, JoinColumn } from "typeorm"; + +@Entity("Post") +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column("timestamp",{ + default: () => "CURRENT_TIMESTAMP", + }) + createdAt:Date; + + + @Column("varchar",{ + length: 30, + default: () => "'defVal'", + }) + text:string; + +} diff --git a/test/integration/defaultValues/mssql/entity/Post.ts b/test/integration/defaultValues/mssql/entity/Post.ts new file mode 100644 index 0000000..0cde951 --- /dev/null +++ b/test/integration/defaultValues/mssql/entity/Post.ts @@ -0,0 +1,21 @@ +import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, JoinColumn } from "typeorm"; + +@Entity("Post") +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column("datetime",{ + default: () => "getdate()", + }) + createdAt:Date; + + + @Column("varchar",{ + length: 30, + default: () => "'defVal'", + }) + text:string; + +} diff --git a/test/integration/defaultValues/mysql/entity/Post.ts b/test/integration/defaultValues/mysql/entity/Post.ts new file mode 100644 index 0000000..e81e4c3 --- /dev/null +++ b/test/integration/defaultValues/mysql/entity/Post.ts @@ -0,0 +1,21 @@ +import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, JoinColumn } from "typeorm"; + +@Entity("Post") +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column("timestamp",{ + default: () => "CURRENT_TIMESTAMP", + }) + createdAt:Date; + + + @Column("varchar",{ + length: 30, + default: () => "'defVal'", + }) + text:string; + +} diff --git a/test/integration/defaultValues/oracle/entity/Post.ts b/test/integration/defaultValues/oracle/entity/Post.ts new file mode 100644 index 0000000..e81e4c3 --- /dev/null +++ b/test/integration/defaultValues/oracle/entity/Post.ts @@ -0,0 +1,21 @@ +import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, JoinColumn } from "typeorm"; + +@Entity("Post") +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column("timestamp",{ + default: () => "CURRENT_TIMESTAMP", + }) + createdAt:Date; + + + @Column("varchar",{ + length: 30, + default: () => "'defVal'", + }) + text:string; + +} diff --git a/test/integration/defaultValues/postgres/entity/Post.ts b/test/integration/defaultValues/postgres/entity/Post.ts new file mode 100644 index 0000000..391a798 --- /dev/null +++ b/test/integration/defaultValues/postgres/entity/Post.ts @@ -0,0 +1,21 @@ +import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, JoinColumn } from "typeorm"; + +@Entity("Post") +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column("timestamp",{ + default: () => "now()", + }) + createdAt:Date; + + + @Column("varchar",{ + length: 30, + default: () => "'defVal'", + }) + text:string; + +} diff --git a/test/integration/defaultValues/sqlite/entity/Post.ts b/test/integration/defaultValues/sqlite/entity/Post.ts new file mode 100644 index 0000000..697440d --- /dev/null +++ b/test/integration/defaultValues/sqlite/entity/Post.ts @@ -0,0 +1,21 @@ +import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, JoinColumn } from "typeorm"; + +@Entity("Post") +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column("datetime",{ + default: () => "CURRENT_TIMESTAMP", + }) + createdAt:Date; + + + @Column("varchar",{ + length: 30, + default: () => "'defVal'", + }) + text:string; + +} diff --git a/test/utils/EntityFileToJson.ts b/test/utils/EntityFileToJson.ts index 1a88308..300e56c 100644 --- a/test/utils/EntityFileToJson.ts +++ b/test/utils/EntityFileToJson.ts @@ -22,6 +22,7 @@ export class EntityFileToJson { if (badJSON.lastIndexOf(',') == badJSON.length - 3) { badJSON = badJSON.slice(0, badJSON.length - 3) + badJSON[badJSON.length - 2] + badJSON[badJSON.length - 1] } + badJSON = badJSON.replace(/default: \(\) => (.*)/, `default: $1`) col.columnOptions = JSON.parse(badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ')) } else { if (decoratorParameters[0] == '"' && decoratorParameters.endsWith('"')) {