Merge branch 'next'

This commit is contained in:
Kononnable 2020-01-08 20:41:55 +01:00
commit 285e705c92
90 changed files with 7410 additions and 4401 deletions

View File

@ -2,4 +2,5 @@
test/integration/defaultValues/**/*.ts
test/integration/entityTypes/**/*.ts
test/integration/examples/**/*.ts
test/integration/github-issues/**/*.ts
test/integration/github-issues/**/*.ts
dist/**/*.d.ts

View File

@ -28,8 +28,7 @@ module.exports = {
"no-plusplus": ["error", { allowForLoopAfterthoughts: true }],
"@typescript-eslint/no-non-null-assertion": ["off"],
"@typescript-eslint/no-object-literal-type-assertion": ["off"],
"import/extensions": ["off"],
"no-param-reassign": ["off"],
"@typescript-eslint/no-explicit-any": ["off"],
"no-loop-func": ["off"]
@ -37,17 +36,8 @@ module.exports = {
settings: {
"import/resolver": {
node: {
extensions: [".js", ".jsx", ".ts", ".tsx"]
extensions: [".ts"]
}
}
},
overrides: [
{
files: ["**/*.test.ts"],
rules: {
"no-unused-expressions": "off",
"func-names": "off"
}
}
]
}
};

View File

@ -1,8 +1,8 @@
language: node_js
node_js:
- 13
- 12
- 10
- 8
cache: npm
sudo: required
services:
@ -35,14 +35,17 @@ before_install:
- if [ -z "$DOCKER_USERNAME" ]; then export ORACLE_Skip=1; else images=(${images[@]} oracle oracle_client); fi
- if [ -n "$DOCKER_USERNAME" ]; then docker-compose up -d ${images[@]}; fi
- echo ${images[@]}
- docker-compose pull --parallel --ignore-pull-failures ${images[@]}
- docker-compose pull --ignore-pull-failures ${images[@]}
- docker-compose up -d ${images[@]}
before_script:
- if [ -n "$DOCKER_USERNAME" ]; then mkdir /opt/oracle; npm i oracledb --no-save; docker cp typeorm-mg-oracle-client:/usr/lib/oracle/12.2/client64/lib /opt/oracle/instantclient_12_2; fi
- export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2:$LD_LIBRARY_PATH
script:
- travis_retry sh -c 'sleep 60 && npm t -- --colors'
- travis_retry sh -c 'sleep 90 && npm t -- --colors'
after_success:
- codecov
after_failure:
- docker ps
- docker-compose logs
dd:
secure: lONUbDz2a1LWId+Z2tTaxajK7MilX/XbQ875FYD6EE09DQ0xcoPdq2/KW5/pxuN1tz4QzTG7izMwra3XWtkBySxqFwJDUOibsgYVgn+EMMuPWNMNnQgXqTTmHbtbm1L1aSMHu4bCu4cJkJBX6503R0Kv4Hbdr2LFnSUI/9KqrevA1cVyksN71BlNBdRtvnHInwB5wNNvGULSLT+DR6qGytLGjq4ZF+pW7dH3A1LNGfDY4ivGPHt9eAWGHcVuESmudO1ADmf6XTZAJVdKqDy5eJguK48XyAqRmTc1vBxDJmCNDaU/mV6fkUoEkCjn9XfG5nJLOKviycc1j/OCuuWuqojmTlRInPGV8GDT8lNivwqLBMzvKoKgSQQROEVus4xzo64M808dFbUS30et3++O589X/7P9Wjmt+6HawcEsSq5TQfEutyB+tM9OwedTkB5Fwwmymuqx23zCAJ2orP7WoIG/ApmnKu6LmpoM340UxxSGkurztQP1OqFrf4u8kDVp9/xzqnd7qSfEd8iKvvb1bOykWGxx6dhyThCdSNyT5GQL3aub3LV6g0UB37lbhB+BVSrOAhN0r1cIWT2wr2mRxwoepObmrcNQ+AOUUXE/RcONsiEQr+STsEIjJb7bTANljRYMKpiPdsAdhvDaUZRyu8KBArTCDPotanzwQFERcw8=

View File

@ -1,10 +1,40 @@
# Changelog
## Unreleased
## 0.4.0
* change default case conversions for generated files (#196)
* enum type safety (#205)
* postgress geography type support (#232)
### BREAKING CHANGES
* Custom NamingStrategy is now defined as separate functions instead of a class
* Rework how parameters are passed to the library:
- Utilization of partial config files
- Option to save .tomg-config file without connection options
* Generation of separate fields for primary columns which are used in relations(old approach won't be compatible with typeorm@0.3.0)
* Remove timeout parameter - query timeout is now set by default to 1 hr
* Change default case conversions for generated files (#196)
### FIXES
* Support complex relationships (#117)
* Proper support for many to many relations
* Proper pluralization of entity names (#142)
* Skip generation of imports not used by entity definition files
* Skip generation of indices generated by PKs or relations
* Columns with unrecognized sql type are generated with unknown type
* Generation of proper type for nullable columns
* Fixed specifying custom naming strategy by relative path (#171)
* Disallow generation of relationId fields with lazy relations(not supported in typeorm)
### ENHANCEMENTS
* Support for old oracle versions (#195)
* New options:
* Disable column name pluralization (#142)
* Ignore tables parameter (#120)
* Generation of index file (#174)
* Default exports on generated models
* Enum type safety (#205)
* Mysql set type support (#91)
* Postgres geography type support (#232)
* Make generated models compatible with common ESLint rules
* Ability to use typeorm-model-generator as library in other projects (but no guarantees about api compatibility between different version)
## 0.3.5

View File

@ -38,7 +38,7 @@ services:
# mssql
mssql:
image: "mcr.microsoft.com/mssql/server:2017-GA-ubuntu"
image: "mcr.microsoft.com/mssql/server:2017-latest-ubuntu"
container_name: "typeorm-mg-mssql"
ports:
- "1433:1433"

4532
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,15 @@
"version": "0.3.5",
"description": "Generates models for TypeORM from existing databases.",
"bin": "bin/typeorm-model-generator",
"main": "./dist/src/library.js",
"types": "./dist/src/library.d.ts",
"scripts": {
"start": "ts-node ./src/index.ts",
"build": "npm run clean && tsc && ncp src/entity.mst dist/src/entity.mst",
"build": "npm run clean && tsc && ncp src/templates/ dist/src/templates/ && ncp package.json dist/package.json",
"prepare": "npm run build",
"pretest": "tsc --noEmit",
"test": "nyc --reporter=lcov ts-node ./node_modules/mocha/bin/_mocha test/**/*.test.ts -- -R spec --bail",
"posttest": "eslint ./**/*.ts ./src/**/*.ts ./test/**/*.ts",
"test": "nyc --reporter=lcov ts-node ./node_modules/mocha/bin/_mocha test/**/*.test.ts -- --bail",
"posttest": "eslint ./src/**/*.ts ./test/**/*.ts",
"clean": "rimraf coverage output dist",
"prettier": "prettier --write ./src/*.ts ./src/**/*.ts"
},
@ -24,59 +26,64 @@
},
"homepage": "https://github.com/Kononnable/typeorm-model-generator#readme",
"dependencies": {
"change-case": "^3.1.0",
"change-case": "^4.1.1",
"fs-extra": "^8.1.0",
"handlebars": "^4.1.2",
"inquirer": "^6.5.0",
"mssql": "^5.1.0",
"handlebars": "^4.5.3",
"inquirer": "^7.0.2",
"mssql": "^6.0.1",
"mysql": "^2.17.1",
"pg": "^7.12.0",
"pg": "^7.17.0",
"pluralize": "^8.0.0",
"prettier": "^1.19.1",
"reflect-metadata": "^0.1.13",
"sqlite3": "^4.0.9",
"typeorm": "^0.2.18",
"typescript": "^3.5.3",
"yargs": "^13.3.0",
"yn": "^2.0.0"
"sqlite3": "^4.1.1",
"typeorm": "^0.2.22",
"typescript": "^3.7.4",
"yargs": "^15.1.0",
"yn": "^3.1.1"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/chai-as-promised": "^7.1.1",
"@types/chai-subset": "^1.3.2",
"@types/fs-extra": "^8.0.0",
"@types/array.prototype.flatmap": "^1.2.0",
"@types/chai": "^4.2.7",
"@types/chai-as-promised": "^7.1.2",
"@types/chai-subset": "^1.3.3",
"@types/eslint": "^6.1.3",
"@types/fs-extra": "^8.0.1",
"@types/handlebars": "^4.1.0",
"@types/inquirer": "^6.5.0",
"@types/mocha": "^5.2.7",
"@types/mssql": "^4.0.16",
"@types/mysql": "^2.15.6",
"@types/node": "^12.6.9",
"@types/oracledb": "^3.1.3",
"@types/pg": "^7.4.14",
"@types/sinon": "^7.0.13",
"@types/sqlite3": "^3.1.5",
"@types/yargs": "^12.0.1",
"@types/yn": "^3.1.0",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0",
"@typescript-eslint/typescript-estree": "^1.13.0",
"@types/mssql": "^6.0.0",
"@types/mysql": "^2.15.8",
"@types/node": "^13.1.4",
"@types/oracledb": "^4.1.1",
"@types/pg": "^7.14.1",
"@types/pluralize": "0.0.29",
"@types/prettier": "^1.19.0",
"@types/sinon": "^7.5.1",
"@types/sqlite3": "^3.1.6",
"@types/yargs": "^13.0.4",
"@typescript-eslint/eslint-plugin": "^2.14.0",
"@typescript-eslint/parser": "^2.14.0",
"@typescript-eslint/typescript-estree": "^2.14.0",
"array.prototype.flatmap": "^1.2.3",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"codecov": "^3.5.0",
"dotenv": "^8.0.0",
"eslint": "^6.1.0",
"codecov": "^3.6.1",
"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"husky": "^3.0.2",
"lint-staged": "^9.2.1",
"mocha": "^6.2.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-import": "^2.19.1",
"husky": "^3.1.0",
"lint-staged": "^9.5.0",
"mocha": "^7.0.0",
"ncp": "^2.0.0",
"nyc": "^14.1.1",
"prettier": "^1.18.2",
"rimraf": "^2.6.3",
"sinon": "^7.3.2",
"sinon-chai": "^3.3.0",
"ts-node": "^8.3.0"
"nyc": "^15.0.0",
"rimraf": "^3.0.0",
"sinon": "^7.5.0",
"sinon-chai": "^3.4.0",
"ts-node": "^8.5.4"
},
"husky": {
"hooks": {

View File

@ -1,15 +0,0 @@
import RelationInfo from "./models/RelationInfo";
import EntityInfo from "./models/EntityInfo";
import ColumnInfo from "./models/ColumnInfo";
export default abstract class AbstractNamingStrategy {
public abstract relationName(
columnName: string,
relation: RelationInfo,
dbModel: EntityInfo[]
): string;
public abstract entityName(entityName: string, entity?: EntityInfo): string;
public abstract columnName(columnName: string, column?: ColumnInfo): string;
}

View File

@ -1,22 +1,16 @@
import * as Handlebars from "handlebars";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import * as TomgUtils from "./Utils";
import AbstractDriver from "./drivers/AbstractDriver";
import MssqlDriver from "./drivers/MssqlDriver";
import MariaDbDriver from "./drivers/MariaDbDriver";
import IConnectionOptions from "./IConnectionOptions";
import IGenerationOptions from "./IGenerationOptions";
import EntityInfo from "./models/EntityInfo";
import PostgresDriver from "./drivers/PostgresDriver";
import MysqlDriver from "./drivers/MysqlDriver";
import OracleDriver from "./drivers/OracleDriver";
import SqliteDriver from "./drivers/SqliteDriver";
import NamingStrategy from "./NamingStrategy";
import AbstractNamingStrategy from "./AbstractNamingStrategy";
import changeCase = require("change-case");
import fs = require("fs");
import path = require("path");
import modelCustomizationPhase from "./ModelCustomization";
import modelGenerationPhase from "./ModelGeneration";
import { Entity } from "./models/Entity";
export function createDriver(driverName: string): AbstractDriver {
switch (driverName) {
@ -42,8 +36,12 @@ export async function createModelFromDatabase(
driver: AbstractDriver,
connectionOptions: IConnectionOptions,
generationOptions: IGenerationOptions
) {
let dbModel = await dataCollectionPhase(driver, connectionOptions);
): Promise<void> {
let dbModel = await dataCollectionPhase(
driver,
connectionOptions,
generationOptions
);
if (dbModel.length === 0) {
TomgUtils.LogError(
"Tables not found in selected database. Skipping creation of typeorm model.",
@ -60,439 +58,8 @@ export async function createModelFromDatabase(
}
export async function dataCollectionPhase(
driver: AbstractDriver,
connectionOptions: IConnectionOptions
) {
return driver.GetDataFromServer(connectionOptions);
}
export function modelCustomizationPhase(
dbModel: EntityInfo[],
generationOptions: IGenerationOptions,
defaultValues: DataTypeDefaults
) {
let namingStrategy: AbstractNamingStrategy;
if (
generationOptions.customNamingStrategyPath &&
generationOptions.customNamingStrategyPath !== ""
) {
// eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires
const req = require(generationOptions.customNamingStrategyPath);
namingStrategy = new req.NamingStrategy();
} else {
namingStrategy = new NamingStrategy();
}
let retVal = setRelationId(generationOptions, dbModel);
retVal = applyNamingStrategy(namingStrategy, retVal);
retVal = addImportsAndGenerationOptions(retVal, generationOptions);
retVal = removeColumnDefaultProperties(retVal, defaultValues);
return retVal;
}
function removeColumnDefaultProperties(
dbModel: EntityInfo[],
defaultValues: DataTypeDefaults
) {
if (!defaultValues) {
return dbModel;
}
dbModel.forEach(entity => {
entity.Columns.forEach(column => {
const defVal = defaultValues[column.options.type as any];
if (defVal) {
if (
column.options.length &&
defVal.length &&
column.options.length === defVal.length
) {
column.options.length = undefined;
}
if (
column.options.precision &&
defVal.precision &&
column.options.precision === defVal.precision &&
column.options.scale &&
defVal.scale &&
column.options.scale === defVal.scale
) {
column.options.precision = undefined;
column.options.scale = undefined;
}
if (
column.options.width &&
defVal.width &&
column.options.width === defVal.width
) {
column.options.width = undefined;
}
}
});
});
return dbModel;
}
function addImportsAndGenerationOptions(
dbModel: EntityInfo[],
generationOptions: IGenerationOptions
) {
dbModel.forEach(element => {
element.Imports = [];
element.Columns.forEach(column => {
column.relations.forEach(relation => {
if (element.tsEntityName !== relation.relatedTable) {
element.Imports.push(relation.relatedTable);
}
});
});
element.GenerateConstructor = generationOptions.generateConstructor;
element.IsActiveRecord = generationOptions.activeRecord;
element.Imports.filter((elem, index, self) => {
return index === self.indexOf(elem);
});
if (generationOptions.skipSchema) {
element.Schema = undefined;
element.Database = undefined;
}
});
return dbModel;
}
function setRelationId(
generationOptions: IGenerationOptions,
model: EntityInfo[]
) {
if (generationOptions.relationIds) {
model.forEach(ent => {
ent.Columns.forEach(col => {
col.relations.forEach(rel => {
rel.relationIdField = rel.isOwner;
});
});
});
}
return model;
}
export function modelGenerationPhase(
connectionOptions: IConnectionOptions,
generationOptions: IGenerationOptions,
databaseModel: EntityInfo[]
) {
createHandlebarsHelpers(generationOptions);
const templatePath = path.resolve(__dirname, "entity.mst");
const template = fs.readFileSync(templatePath, "UTF-8");
const resultPath = generationOptions.resultsPath;
if (!fs.existsSync(resultPath)) {
fs.mkdirSync(resultPath);
}
let entitesPath = resultPath;
if (!generationOptions.noConfigs) {
createTsConfigFile(resultPath);
createTypeOrmConfig(resultPath, connectionOptions);
entitesPath = path.resolve(resultPath, "./entities");
if (!fs.existsSync(entitesPath)) {
fs.mkdirSync(entitesPath);
}
}
const compliedTemplate = Handlebars.compile(template, {
noEscape: true
});
databaseModel.forEach(element => {
let casedFileName = "";
switch (generationOptions.convertCaseFile) {
case "camel":
casedFileName = changeCase.camelCase(element.tsEntityName);
break;
case "param":
casedFileName = changeCase.paramCase(element.tsEntityName);
break;
case "pascal":
casedFileName = changeCase.pascalCase(element.tsEntityName);
break;
case "none":
casedFileName = element.tsEntityName;
break;
default:
throw new Error("Unknown case style");
}
const resultFilePath = path.resolve(entitesPath, `${casedFileName}.ts`);
const rendered = compliedTemplate(element);
fs.writeFileSync(resultFilePath, rendered, {
encoding: "UTF-8",
flag: "w"
});
});
}
function createHandlebarsHelpers(generationOptions: IGenerationOptions) {
Handlebars.registerHelper("curly", open => (open ? "{" : "}"));
Handlebars.registerHelper("toEntityName", str => {
let retStr = "";
switch (generationOptions.convertCaseEntity) {
case "camel":
retStr = changeCase.camelCase(str);
break;
case "pascal":
retStr = changeCase.pascalCase(str);
break;
case "none":
retStr = str;
break;
default:
throw new Error("Unknown case style");
}
return retStr;
});
Handlebars.registerHelper("concat", (stra, strb) => {
return stra + strb;
});
Handlebars.registerHelper("toFileName", str => {
let retStr = "";
switch (generationOptions.convertCaseFile) {
case "camel":
retStr = changeCase.camelCase(str);
break;
case "param":
retStr = changeCase.paramCase(str);
break;
case "pascal":
retStr = changeCase.pascalCase(str);
break;
case "none":
retStr = str;
break;
default:
throw new Error("Unknown case style");
}
return retStr;
});
Handlebars.registerHelper("printPropertyVisibility", () =>
generationOptions.propertyVisibility !== "none"
? `${generationOptions.propertyVisibility} `
: ""
);
Handlebars.registerHelper("toPropertyName", str => {
let retStr = "";
switch (generationOptions.convertCaseProperty) {
case "camel":
retStr = changeCase.camelCase(str);
break;
case "pascal":
retStr = changeCase.pascalCase(str);
break;
case "none":
retStr = str;
break;
default:
throw new Error("Unknown case style");
}
return retStr;
});
Handlebars.registerHelper("toLowerCase", str => str.toLowerCase());
Handlebars.registerHelper("tolowerCaseFirst", str =>
changeCase.lowerCaseFirst(str)
);
Handlebars.registerHelper("strictMode", () =>
generationOptions.strictMode ? generationOptions.strictMode : ""
);
Handlebars.registerHelper("toLazy", str => {
if (generationOptions.lazy) {
return `Promise<${str}>`;
}
return str;
});
Handlebars.registerHelper({
and: (v1, v2) => v1 && v2,
eq: (v1, v2) => v1 === v2,
gt: (v1, v2) => v1 > v2,
gte: (v1, v2) => v1 >= v2,
lt: (v1, v2) => v1 < v2,
lte: (v1, v2) => v1 <= v2,
ne: (v1, v2) => v1 !== v2,
or: (v1, v2) => v1 || v2
});
}
// TODO:Move to mustache template file
function createTsConfigFile(resultPath) {
fs.writeFileSync(
path.resolve(resultPath, "tsconfig.json"),
`{"compilerOptions": {
"lib": ["es5", "es6"],
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
}}`,
{ encoding: "UTF-8", flag: "w" }
);
}
function createTypeOrmConfig(
resultPath: string,
connectionOptions: IConnectionOptions
) {
if (connectionOptions.schemaName === "") {
fs.writeFileSync(
path.resolve(resultPath, "ormconfig.json"),
`[
{
"name": "default",
"type": "${connectionOptions.databaseType}",
"host": "${connectionOptions.host}",
"port": ${connectionOptions.port},
"username": "${connectionOptions.user}",
"password": "${connectionOptions.password}",
"database": "${connectionOptions.databaseName}",
"synchronize": false,
"entities": [
"entities/*.js"
]
}
]`,
{ encoding: "UTF-8", flag: "w" }
);
} else {
fs.writeFileSync(
path.resolve(resultPath, "ormconfig.json"),
`[
{
"name": "default",
"type": "${connectionOptions.databaseType}",
"host": "${connectionOptions.host}",
"port": ${connectionOptions.port},
"username": "${connectionOptions.user}",
"password": "${connectionOptions.password}",
"database": "${connectionOptions.databaseName}",
"schema": "${connectionOptions.schemaName}",
"synchronize": false,
"entities": [
"entities/*.js"
]
}
]`,
{ encoding: "UTF-8", flag: "w" }
);
}
}
function applyNamingStrategy(
namingStrategy: AbstractNamingStrategy,
dbModel: EntityInfo[]
) {
let retval = changeRelationNames(dbModel);
retval = changeEntityNames(retval);
retval = changeColumnNames(retval);
return retval;
function changeRelationNames(model: EntityInfo[]) {
model.forEach(entity => {
entity.Columns.forEach(column => {
column.relations.forEach(relation => {
const newName = namingStrategy.relationName(
column.tsName,
relation,
model
);
model.forEach(entity2 => {
entity2.Columns.forEach(column2 => {
column2.relations.forEach(relation2 => {
if (
relation2.relatedTable ===
entity.tsEntityName &&
relation2.ownerColumn === column.tsName
) {
relation2.ownerColumn = newName;
}
if (
relation2.relatedTable ===
entity.tsEntityName &&
relation2.relatedColumn === column.tsName
) {
relation2.relatedColumn = newName;
}
if (relation.isOwner) {
entity.Indexes.forEach(ind => {
ind.columns
.filter(
col =>
col.name === column.tsName
)
.forEach(col => {
col.name = newName;
});
});
}
});
});
});
column.tsName = newName;
});
});
});
return dbModel;
}
function changeColumnNames(model: EntityInfo[]) {
model.forEach(entity => {
entity.Columns.forEach(column => {
const newName = namingStrategy.columnName(
column.tsName,
column
);
entity.Indexes.forEach(index => {
index.columns
.filter(column2 => column2.name === column.tsName)
.forEach(column2 => {
column2.name = newName;
});
});
model.forEach(entity2 => {
entity2.Columns.forEach(column2 => {
column2.relations
.filter(
relation =>
relation.relatedTable ===
entity.tsEntityName &&
relation.relatedColumn === column.tsName
)
.forEach(v => {
v.relatedColumn = newName;
});
column2.relations
.filter(
relation =>
relation.relatedTable ===
entity.tsEntityName &&
relation.ownerColumn === column.tsName
)
.forEach(v => {
v.ownerColumn = newName;
});
});
});
column.tsName = newName;
});
});
return model;
}
function changeEntityNames(entities: EntityInfo[]) {
entities.forEach(entity => {
const newName = namingStrategy.entityName(
entity.tsEntityName,
entity
);
entities.forEach(entity2 => {
entity2.Columns.forEach(column => {
column.relations.forEach(relation => {
if (relation.ownerTable === entity.tsEntityName) {
relation.ownerTable = newName;
}
if (relation.relatedTable === entity.tsEntityName) {
relation.relatedTable = newName;
}
});
});
});
entity.tsEntityName = newName;
});
return entities;
}
generationOptions: IGenerationOptions
): Promise<Entity[]> {
return driver.GetDataFromServer(connectionOptions, generationOptions);
}

View File

@ -1,19 +1,35 @@
export default class IConnectionOptions {
public host: string = "";
// TODO: change name
public port: number = 0;
public databaseName: string = "";
public user: string = "";
public password: string = "";
public databaseType: string = "";
public schemaName: string = "";
public ssl: boolean = false;
public timeout?: number;
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
export default interface IConnectionOptions {
host: string;
port: number;
databaseName: string;
user: string;
password: string;
databaseType:
| "mssql"
| "postgres"
| "mysql"
| "mariadb"
| "oracle"
| "sqlite";
schemaName: string;
ssl: boolean;
skipTables: string[];
}
export function getDefaultConnectionOptions(): IConnectionOptions {
const connectionOptions: IConnectionOptions = {
host: "127.0.0.1",
port: 0,
databaseName: "",
user: "",
password: "",
databaseType: undefined as any,
schemaName: "",
ssl: false,
skipTables: []
};
return connectionOptions;
}

View File

@ -1,28 +1,44 @@
export default class IGenerationOptions {
public resultsPath: string = "";
import path = require("path");
public noConfigs: boolean = false;
// TODO: change name
public convertCaseFile: "pascal" | "param" | "camel" | "none" = "none";
public convertCaseEntity: "pascal" | "camel" | "none" = "none";
public convertCaseProperty: "pascal" | "camel" | "none" = "none";
public propertyVisibility: "public" | "protected" | "private" | "none" =
"none";
public lazy: boolean = false;
public activeRecord: boolean = false;
public generateConstructor: boolean = false;
public customNamingStrategyPath: string = "";
public relationIds: boolean = false;
public strictMode: false | "?" | "!" = false;
public skipSchema: boolean = false;
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
export default interface IGenerationOptions {
resultsPath: string;
pluralizeNames: boolean;
noConfigs: boolean;
convertCaseFile: "pascal" | "param" | "camel" | "none";
convertCaseEntity: "pascal" | "camel" | "none";
convertCaseProperty: "pascal" | "camel" | "none";
propertyVisibility: "public" | "protected" | "private" | "none";
lazy: boolean;
activeRecord: boolean;
generateConstructor: boolean;
customNamingStrategyPath: string;
relationIds: boolean;
strictMode: "none" | "?" | "!";
skipSchema: boolean;
indexFile: boolean;
exportType: "named" | "default";
}
export function getDefaultGenerationOptions(): IGenerationOptions {
const generationOptions: IGenerationOptions = {
resultsPath: path.resolve(process.cwd(), "output"),
pluralizeNames: true,
noConfigs: false,
convertCaseFile: "pascal",
convertCaseEntity: "pascal",
convertCaseProperty: "camel",
propertyVisibility: "none",
lazy: false,
activeRecord: false,
generateConstructor: false,
customNamingStrategyPath: "",
relationIds: false,
strictMode: "none",
skipSchema: false,
indexFile: false,
exportType: "named"
};
return generationOptions;
}

357
src/ModelCustomization.ts Normal file
View File

@ -0,0 +1,357 @@
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import { DefaultNamingStrategy } from "typeorm/naming-strategy/DefaultNamingStrategy";
import { Entity } from "./models/Entity";
import IGenerationOptions from "./IGenerationOptions";
import * as NamingStrategy from "./NamingStrategy";
import * as TomgUtils from "./Utils";
import { Relation } from "./models/Relation";
import { RelationId } from "./models/RelationId";
import { Column } from "./models/Column";
type NamingStrategy = {
enablePluralization: (value: boolean) => void;
relationIdName: (
relationId: RelationId,
relation: Relation,
owner: Entity
) => string;
relationName: (relation: Relation, owner: Entity) => string;
columnName: (columnName: string, column?: Column) => string;
entityName: (entityName: string, entity?: Entity) => string;
};
export default function modelCustomizationPhase(
dbModel: Entity[],
generationOptions: IGenerationOptions,
defaultValues: DataTypeDefaults
): Entity[] {
const namingStrategy: NamingStrategy = {
enablePluralization: NamingStrategy.enablePluralization,
columnName: NamingStrategy.columnName,
entityName: NamingStrategy.entityName,
relationIdName: NamingStrategy.relationIdName,
relationName: NamingStrategy.relationName
};
if (
generationOptions.customNamingStrategyPath &&
generationOptions.customNamingStrategyPath !== ""
) {
// TODO: change form of logging
const req = TomgUtils.requireLocalFile(
generationOptions.customNamingStrategyPath
) as Partial<NamingStrategy>;
if (req.columnName) {
console.log(
`[${new Date().toLocaleTimeString()}] Using custom naming strategy for column names.`
);
namingStrategy.columnName = req.columnName;
} else {
console.log(
`[${new Date().toLocaleTimeString()}] Using standard naming strategy for column names.`
);
}
if (req.entityName) {
console.log(
`[${new Date().toLocaleTimeString()}] Using custom naming strategy for entity names.`
);
namingStrategy.entityName = req.entityName;
} else {
console.log(
`[${new Date().toLocaleTimeString()}] Using standard naming strategy for entity names.`
);
}
if (req.relationIdName) {
console.log(
`[${new Date().toLocaleTimeString()}] Using custom naming strategy for relationId field names.`
);
namingStrategy.relationIdName = req.relationIdName;
} else {
console.log(
`[${new Date().toLocaleTimeString()}] Using standard naming strategy for relationId field names.`
);
}
if (req.relationName) {
console.log(
`[${new Date().toLocaleTimeString()}] Using custom naming strategy for relation field names.`
);
namingStrategy.relationName = req.relationName;
} else {
console.log(
`[${new Date().toLocaleTimeString()}] Using standard naming strategy for relation field names.`
);
}
if (req.enablePluralization) {
console.log(
`[${new Date().toLocaleTimeString()}] Using custom pluralization method for OneToMany, ManyToMany relation field names.`
);
namingStrategy.enablePluralization = req.enablePluralization;
} else {
console.log(
`[${new Date().toLocaleTimeString()}] Using custom pluralization method for OneToMany, ManyToMany relation field names.`
);
}
}
namingStrategy.enablePluralization(generationOptions.pluralizeNames);
let retVal = removeIndicesGeneratedByTypeorm(dbModel);
retVal = removeColumnsInRelation(dbModel);
retVal = applyNamingStrategy(namingStrategy, dbModel);
retVal = addImportsAndGenerationOptions(retVal, generationOptions);
retVal = removeColumnDefaultProperties(retVal, defaultValues);
return retVal;
}
function removeIndicesGeneratedByTypeorm(dbModel: Entity[]): Entity[] {
// TODO: Support typeorm CustomNamingStrategy
const namingStrategy = new DefaultNamingStrategy();
dbModel.forEach(entity => {
entity.indices = entity.indices.filter(
v =>
!(
v.name.startsWith(`sqlite_autoindex_`) ||
(v.primary && v.name === "PRIMARY")
)
);
const primaryColumns = entity.columns
.filter(v => v.primary)
.map(v => v.tscName);
entity.indices = entity.indices.filter(
v =>
!(
v.primary &&
v.name ===
namingStrategy.primaryKeyName(
entity.tscName,
primaryColumns
)
)
);
entity.relations
.filter(v => v.joinColumnOptions)
.forEach(rel => {
const columnNames = rel.joinColumnOptions!.map(v => v.name);
const idxName = namingStrategy.relationConstraintName(
entity.tscName,
columnNames
);
const fkName = namingStrategy.foreignKeyName(
entity.tscName,
columnNames
);
entity.indices = entity.indices.filter(
v => v.name !== idxName && v.name !== fkName
);
});
});
return dbModel;
}
function removeColumnsInRelation(dbModel: Entity[]): Entity[] {
dbModel.forEach(entity => {
entity.columns = entity.columns.filter(
col =>
!col.isUsedInRelationAsOwner ||
col.isUsedInRelationAsReferenced ||
entity.indices.some(idx =>
idx.columns.some(v => v === col.tscName)
) ||
col.primary
);
});
return dbModel;
}
function removeColumnDefaultProperties(
dbModel: Entity[],
defaultValues: DataTypeDefaults
): Entity[] {
if (!defaultValues) {
return dbModel;
}
dbModel.forEach(entity => {
entity.columns.forEach(column => {
const defVal = defaultValues[column.tscType];
if (defVal) {
if (
column.options.length &&
defVal.length &&
column.options.length === defVal.length
) {
column.options.length = undefined;
}
if (
column.options.precision &&
defVal.precision &&
column.options.precision === defVal.precision &&
column.options.scale &&
defVal.scale &&
column.options.scale === defVal.scale
) {
column.options.precision = undefined;
column.options.scale = undefined;
}
if (
column.options.width &&
defVal.width &&
column.options.width === defVal.width
) {
column.options.width = undefined;
}
}
});
});
return dbModel;
}
function findFileImports(dbModel: Entity[]) {
dbModel.forEach(entity => {
entity.relations.forEach(relation => {
if (
relation.relatedTable !== entity.tscName &&
!entity.fileImports.some(v => v === relation.relatedTable)
) {
entity.fileImports.push(relation.relatedTable);
}
});
});
return dbModel;
}
function addImportsAndGenerationOptions(
dbModel: Entity[],
generationOptions: IGenerationOptions
): Entity[] {
dbModel = findFileImports(dbModel);
dbModel.forEach(entity => {
entity.relations.forEach(relation => {
if (generationOptions.lazy) {
if (!relation.relationOptions) {
relation.relationOptions = {};
}
relation.relationOptions.lazy = true;
}
});
if (generationOptions.skipSchema) {
entity.schema = undefined;
entity.database = undefined;
}
if (generationOptions.activeRecord) {
entity.activeRecord = true;
}
if (generationOptions.generateConstructor) {
entity.generateConstructor = true;
}
});
return dbModel;
}
function applyNamingStrategy(
namingStrategy: NamingStrategy,
dbModel: Entity[]
): Entity[] {
let retVal = changeRelationNames(dbModel);
retVal = changeRelationIdNames(retVal);
retVal = changeEntityNames(retVal);
retVal = changeColumnNames(retVal);
return retVal;
function changeRelationIdNames(model: Entity[]): Entity[] {
model.forEach(entity => {
entity.relationIds.forEach(relationId => {
const oldName = relationId.fieldName;
const relation = entity.relations.find(
v => v.fieldName === relationId.relationField
)!;
let newName = namingStrategy.relationIdName(
relationId,
relation,
entity
);
newName = TomgUtils.findNameForNewField(
newName,
entity,
oldName
);
entity.indices.forEach(index => {
index.columns = index.columns.map(column2 =>
column2 === oldName ? newName : column2
);
});
relationId.fieldName = newName;
});
});
return dbModel;
}
function changeRelationNames(model: Entity[]): Entity[] {
model.forEach(entity => {
entity.relations.forEach(relation => {
const oldName = relation.fieldName;
let newName = namingStrategy.relationName(relation, entity);
newName = TomgUtils.findNameForNewField(
newName,
entity,
oldName
);
const relatedEntity = model.find(
v => v.tscName === relation.relatedTable
)!;
const relation2 = relatedEntity.relations.find(
v => v.fieldName === relation.relatedField
)!;
entity.relationIds
.filter(v => v.relationField === oldName)
.forEach(v => {
v.relationField = newName;
});
relation.fieldName = newName;
relation2.relatedField = newName;
if (relation.relationOptions) {
entity.indices.forEach(ind => {
ind.columns.map(column2 =>
column2 === oldName ? newName : column2
);
});
}
});
});
return dbModel;
}
function changeColumnNames(model: Entity[]): Entity[] {
model.forEach(entity => {
entity.columns.forEach(column => {
const oldName = column.tscName;
let newName = namingStrategy.columnName(column.tscName, column);
newName = TomgUtils.findNameForNewField(
newName,
entity,
oldName
);
entity.indices.forEach(index => {
index.columns = index.columns.map(column2 =>
column2 === oldName ? newName : column2
);
});
column.tscName = newName;
});
});
return model;
}
function changeEntityNames(entities: Entity[]): Entity[] {
entities.forEach(entity => {
const newName = namingStrategy.entityName(entity.tscName, entity);
entities.forEach(entity2 => {
entity2.relations.forEach(relation => {
if (relation.relatedTable === entity.tscName) {
relation.relatedTable = newName;
}
});
});
entity.tscName = newName;
});
return entities;
}
}

269
src/ModelGeneration.ts Normal file
View File

@ -0,0 +1,269 @@
import * as Handlebars from "handlebars";
import * as Prettier from "prettier";
import * as changeCase from "change-case";
import * as fs from "fs";
import * as path from "path";
import IConnectionOptions from "./IConnectionOptions";
import IGenerationOptions from "./IGenerationOptions";
import { Entity } from "./models/Entity";
import { Relation } from "./models/Relation";
export default function modelGenerationPhase(
connectionOptions: IConnectionOptions,
generationOptions: IGenerationOptions,
databaseModel: Entity[]
): void {
createHandlebarsHelpers(generationOptions);
const resultPath = generationOptions.resultsPath;
if (!fs.existsSync(resultPath)) {
fs.mkdirSync(resultPath);
}
let entitiesPath = resultPath;
if (!generationOptions.noConfigs) {
createTsConfigFile(resultPath);
createTypeOrmConfig(resultPath, connectionOptions);
entitiesPath = path.resolve(resultPath, "./entities");
if (!fs.existsSync(entitiesPath)) {
fs.mkdirSync(entitiesPath);
}
}
if (generationOptions.indexFile) {
createIndexFile(databaseModel, generationOptions, entitiesPath);
}
generateModels(databaseModel, generationOptions, entitiesPath);
}
function generateModels(
databaseModel: Entity[],
generationOptions: IGenerationOptions,
entitiesPath: string
) {
const entityTemplatePath = path.resolve(
__dirname,
"templates",
"entity.mst"
);
const entityTemplate = fs.readFileSync(entityTemplatePath, "UTF-8");
const entityCompliedTemplate = Handlebars.compile(entityTemplate, {
noEscape: true
});
databaseModel.forEach(element => {
let casedFileName = "";
switch (generationOptions.convertCaseFile) {
case "camel":
casedFileName = changeCase.camelCase(element.tscName);
break;
case "param":
casedFileName = changeCase.paramCase(element.tscName);
break;
case "pascal":
casedFileName = changeCase.pascalCase(element.tscName);
break;
case "none":
casedFileName = element.tscName;
break;
default:
throw new Error("Unknown case style");
}
const resultFilePath = path.resolve(
entitiesPath,
`${casedFileName}.ts`
);
const rendered = entityCompliedTemplate(element);
const withImportStatements = removeUnusedImports(rendered);
const formatted = Prettier.format(withImportStatements, {
parser: "typescript"
});
fs.writeFileSync(resultFilePath, formatted, {
encoding: "UTF-8",
flag: "w"
});
});
}
function createIndexFile(
databaseModel: Entity[],
generationOptions: IGenerationOptions,
entitiesPath: string
) {
const templatePath = path.resolve(__dirname, "templates", "index.mst");
const template = fs.readFileSync(templatePath, "UTF-8");
const compliedTemplate = Handlebars.compile(template, {
noEscape: true
});
const rendered = compliedTemplate({ entities: databaseModel });
const formatted = Prettier.format(rendered, {
parser: "typescript"
});
let fileName = "index";
switch (generationOptions.convertCaseFile) {
case "camel":
fileName = changeCase.camelCase(fileName);
break;
case "param":
fileName = changeCase.paramCase(fileName);
break;
case "pascal":
fileName = changeCase.pascalCase(fileName);
break;
default:
}
const resultFilePath = path.resolve(entitiesPath, `${fileName}.ts`);
fs.writeFileSync(resultFilePath, formatted, {
encoding: "UTF-8",
flag: "w"
});
}
function removeUnusedImports(rendered: string) {
const openBracketIndex = rendered.indexOf("{") + 1;
const closeBracketIndex = rendered.indexOf("}");
const imports = rendered
.substring(openBracketIndex, closeBracketIndex)
.split(",");
const restOfEntityDefinition = rendered.substring(closeBracketIndex);
const distinctImports = imports.filter(
v =>
restOfEntityDefinition.indexOf(`@${v}(`) !== -1 ||
(v === "BaseEntity" && restOfEntityDefinition.indexOf(v) !== -1)
);
return `${rendered.substring(0, openBracketIndex)}${distinctImports.join(
","
)}${restOfEntityDefinition}`;
}
function createHandlebarsHelpers(generationOptions: IGenerationOptions): void {
Handlebars.registerHelper("json", context => {
const json = JSON.stringify(context);
const withoutQuotes = json.replace(/"([^(")"]+)":/g, "$1:");
return withoutQuotes.slice(1, withoutQuotes.length - 1);
});
Handlebars.registerHelper("toEntityName", str => {
let retStr = "";
switch (generationOptions.convertCaseEntity) {
case "camel":
retStr = changeCase.camelCase(str);
break;
case "pascal":
retStr = changeCase.pascalCase(str);
break;
case "none":
retStr = str;
break;
default:
throw new Error("Unknown case style");
}
return retStr;
});
Handlebars.registerHelper("toFileName", str => {
let retStr = "";
switch (generationOptions.convertCaseFile) {
case "camel":
retStr = changeCase.camelCase(str);
break;
case "param":
retStr = changeCase.paramCase(str);
break;
case "pascal":
retStr = changeCase.pascalCase(str);
break;
case "none":
retStr = str;
break;
default:
throw new Error("Unknown case style");
}
return retStr;
});
Handlebars.registerHelper("printPropertyVisibility", () =>
generationOptions.propertyVisibility !== "none"
? `${generationOptions.propertyVisibility} `
: ""
);
Handlebars.registerHelper("toPropertyName", str => {
let retStr = "";
switch (generationOptions.convertCaseProperty) {
case "camel":
retStr = changeCase.camelCase(str);
break;
case "pascal":
retStr = changeCase.pascalCase(str);
break;
case "none":
retStr = str;
break;
default:
throw new Error("Unknown case style");
}
return retStr;
});
Handlebars.registerHelper(
"toRelation",
(entityType: string, relationType: Relation["relationType"]) => {
let retVal = entityType;
if (relationType === "ManyToMany" || relationType === "OneToMany") {
retVal = `${retVal}[]`;
}
if (generationOptions.lazy) {
retVal = `Promise<${retVal}>`;
}
return retVal;
}
);
Handlebars.registerHelper("defaultExport", () =>
generationOptions.exportType === "default" ? "default" : ""
);
Handlebars.registerHelper("localImport", (entityName: string) =>
generationOptions.exportType === "default"
? entityName
: `{${entityName}}`
);
Handlebars.registerHelper("strictMode", () =>
generationOptions.strictMode !== "none"
? generationOptions.strictMode
: ""
);
Handlebars.registerHelper({
and: (v1, v2) => v1 && v2,
eq: (v1, v2) => v1 === v2,
gt: (v1, v2) => v1 > v2,
gte: (v1, v2) => v1 >= v2,
lt: (v1, v2) => v1 < v2,
lte: (v1, v2) => v1 <= v2,
ne: (v1, v2) => v1 !== v2,
or: (v1, v2) => v1 || v2
});
}
function createTsConfigFile(outputPath: string): void {
const templatePath = path.resolve(__dirname, "templates", "tsconfig.mst");
const template = fs.readFileSync(templatePath, "UTF-8");
const compliedTemplate = Handlebars.compile(template, {
noEscape: true
});
const rendered = compliedTemplate({});
const formatted = Prettier.format(rendered, { parser: "json" });
const resultFilePath = path.resolve(outputPath, "tsconfig.json");
fs.writeFileSync(resultFilePath, formatted, {
encoding: "UTF-8",
flag: "w"
});
}
function createTypeOrmConfig(
outputPath: string,
connectionOptions: IConnectionOptions
): void {
const templatePath = path.resolve(__dirname, "templates", "ormconfig.mst");
const template = fs.readFileSync(templatePath, "UTF-8");
const compliedTemplate = Handlebars.compile(template, {
noEscape: true
});
const rendered = compliedTemplate(connectionOptions);
const formatted = Prettier.format(rendered, { parser: "json" });
const resultFilePath = path.resolve(outputPath, "ormconfig.json");
fs.writeFileSync(resultFilePath, formatted, {
encoding: "UTF-8",
flag: "w"
});
}

View File

@ -1,74 +1,75 @@
import AbstractNamingStrategy from "./AbstractNamingStrategy";
import RelationInfo from "./models/RelationInfo";
import EntityInfo from "./models/EntityInfo";
import { plural } from "pluralize";
import * as changeCase from "change-case";
import { Relation } from "./models/Relation";
import { RelationId } from "./models/RelationId";
import changeCase = require("change-case");
let pluralize: boolean;
/* eslint-disable class-methods-use-this */
export default class NamingStrategy extends AbstractNamingStrategy {
public relationName(
columnOldName: string,
relation: RelationInfo,
dbModel: EntityInfo[]
): string {
const isRelationToMany = relation.isOneToMany || relation.isManyToMany;
const ownerEntity = dbModel.find(
v => v.tsEntityName === relation.ownerTable
)!;
let columnName = changeCase.camelCase(columnOldName);
if (
columnName.toLowerCase().endsWith("id") &&
!columnName.toLowerCase().endsWith("guid")
) {
columnName = columnName.substring(
0,
columnName.toLowerCase().lastIndexOf("id")
);
}
if (!Number.isNaN(parseInt(columnName[columnName.length - 1], 10))) {
columnName = columnName.substring(0, columnName.length - 1);
}
if (!Number.isNaN(parseInt(columnName[columnName.length - 1], 10))) {
columnName = columnName.substring(0, columnName.length - 1);
}
columnName += isRelationToMany ? "s" : "";
if (
relation.relationType !== "ManyToMany" &&
columnOldName !== columnName
) {
if (ownerEntity.Columns.some(v => v.tsName === columnName)) {
columnName += "_";
for (let i = 2; i <= ownerEntity.Columns.length; i++) {
columnName =
columnName.substring(
0,
columnName.length - i.toString().length
) + i.toString();
if (
ownerEntity.Columns.every(
v =>
v.tsName !== columnName ||
columnName === columnOldName
)
) {
break;
}
}
}
}
return columnName;
}
public entityName(entityName: string): string {
return entityName;
}
public columnName(columnName: string): string {
return columnName;
}
export function enablePluralization(value: boolean) {
pluralize = value;
}
/* eslint-enable class-methods-use-this */
export function relationIdName(
relationId: RelationId,
relation: Relation
): string {
const columnOldName = relationId.fieldName;
const isRelationToMany =
relation.relationType === "OneToMany" ||
relation.relationType === "ManyToMany";
let newColumnName = changeCase.camelCase(
columnOldName.replace(/[0-9]$/, "")
);
if (!Number.isNaN(parseInt(newColumnName[newColumnName.length - 1], 10))) {
newColumnName = newColumnName.substring(0, newColumnName.length - 1);
}
if (!Number.isNaN(parseInt(newColumnName[newColumnName.length - 1], 10))) {
newColumnName = newColumnName.substring(0, newColumnName.length - 1);
}
if (isRelationToMany && pluralize) {
newColumnName = plural(newColumnName);
}
return newColumnName;
}
export function relationName(relation: Relation): string {
const columnOldName = relation.fieldName;
const isRelationToMany =
relation.relationType === "OneToMany" ||
relation.relationType === "ManyToMany";
let newColumnName = changeCase.camelCase(
columnOldName.replace(/[0-9]$/, "")
);
if (
newColumnName.toLowerCase().endsWith("id") &&
!newColumnName.toLowerCase().endsWith("guid")
) {
newColumnName = newColumnName.substring(
0,
newColumnName.toLowerCase().lastIndexOf("id")
);
}
if (!Number.isNaN(parseInt(newColumnName[newColumnName.length - 1], 10))) {
newColumnName = newColumnName.substring(0, newColumnName.length - 1);
}
if (!Number.isNaN(parseInt(newColumnName[newColumnName.length - 1], 10))) {
newColumnName = newColumnName.substring(0, newColumnName.length - 1);
}
if (isRelationToMany && pluralize) {
newColumnName = plural(newColumnName);
}
return newColumnName;
}
export function entityName(oldEntityName: string): string {
return oldEntityName;
}
export function columnName(oldColumnName: string): string {
return oldColumnName;
}

View File

@ -1,18 +1,19 @@
import * as changeCase from "change-case";
import * as path from "path";
import * as packagejson from "../package.json";
import { Entity } from "./models/Entity";
export function LogError(
errText: string,
isABug: boolean = true,
passedError?: any
) {
isABug = true,
passedError?: string | ErrorConstructor
): void {
let errObject = passedError;
console.error(errText);
console.error(`Error occured in typeorm-model-generator.`);
console.error(`Error occurred in typeorm-model-generator.`);
console.error(`${packageVersion()} node@${process.version}`);
console.error(
`If you think this is a bug please open an issue including this log on ${
(packagejson as any).bugs.url
}`
`If you think this is a bug please open an issue including this log on ${packagejson.bugs.url}`
);
if (isABug && !passedError) {
errObject = new Error().stack;
@ -21,6 +22,61 @@ export function LogError(
console.error(errObject);
}
}
export function packageVersion() {
return `${(packagejson as any).name}@${(packagejson as any).version}`;
export function packageVersion(): string {
return `${packagejson.name}@${packagejson.version}`;
}
export function findNameForNewField(
_fieldName: string,
entity: Entity,
columnOldName = ""
): string {
let fieldName = _fieldName;
const validNameCondition = () =>
(entity.columns.every(
v =>
changeCase.camelCase(v.tscName) !==
changeCase.camelCase(fieldName)
) &&
entity.relations.every(
v =>
changeCase.camelCase(v.fieldName) !==
changeCase.camelCase(fieldName)
) &&
entity.relationIds.every(
v =>
changeCase.camelCase(v.fieldName) !==
changeCase.camelCase(fieldName)
)) ||
(columnOldName &&
changeCase.camelCase(columnOldName) ===
changeCase.camelCase(fieldName));
if (!validNameCondition()) {
fieldName += "_";
for (
let i = 2;
i <= entity.columns.length + entity.relations.length;
i++
) {
fieldName =
fieldName.substring(0, fieldName.length - i.toString().length) +
i.toString();
if (validNameCondition()) {
break;
}
}
}
return fieldName;
}
export function requireLocalFile(fileName: string): any {
try {
// eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires
return require(fileName);
} catch (err) {
if (!path.isAbsolute(fileName)) {
// eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires
return require(path.resolve(process.cwd(), fileName));
}
throw err;
}
}

View File

@ -3,14 +3,15 @@ import {
WithPrecisionColumnType,
WithWidthColumnType
} from "typeorm/driver/types/ColumnTypes";
import { JoinColumnOptions, RelationOptions } from "typeorm";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import * as TomgUtils from "../Utils";
import EntityInfo from "../models/EntityInfo";
import RelationInfo from "../models/RelationInfo";
import ColumnInfo from "../models/ColumnInfo";
import IConnectionOptions from "../IConnectionOptions";
import IndexInfo from "../models/IndexInfo";
import RelationTempInfo from "../models/RelationTempInfo";
import { Entity } from "../models/Entity";
import { RelationInternal } from "../models/RelationInternal";
import { Relation } from "../models/Relation";
import IGenerationOptions from "../IGenerationOptions";
import { Column } from "../models/Column";
export default abstract class AbstractDriver {
public abstract standardPort: number;
@ -68,7 +69,8 @@ export default abstract class AbstractDriver {
public abstract GetAllTablesQuery: (
schema: string,
dbNames: string
dbNames: string,
tableNames: string[]
) => Promise<
{
TABLE_SCHEMA: string;
@ -77,131 +79,136 @@ export default abstract class AbstractDriver {
}[]
>;
public static FindManyToManyRelations(dbModel: EntityInfo[]) {
let retval = dbModel;
const manyToManyEntities = retval.filter(
public static FindManyToManyRelations(dbModel: Entity[]) {
let retVal = dbModel;
const manyToManyEntities = retVal.filter(
entity =>
entity.Columns.filter(column => {
return (
column.relations.length === 1 &&
!column.relations[0].isOneToMany &&
column.relations[0].isOwner
);
}).length === entity.Columns.length
entity.relations.length === 2 &&
entity.relations.every(
v => v.joinColumnOptions && v.relationType !== "ManyToMany"
) &&
entity.relations[0].relatedTable !==
entity.relations[1].relatedTable &&
entity.relations[0].joinColumnOptions!.length ===
entity.relations[1].joinColumnOptions!.length &&
entity.columns.length ===
entity.columns.filter(c => c.primary).length &&
entity.columns
.map(v => v.tscName)
.filter(
v =>
!entity.relations[0]
.joinColumnOptions!.map(x => x.name)
.some(jc => jc === v) &&
!entity.relations[1]
.joinColumnOptions!.map(x => x.name)
.some(jc => jc === v)
).length === 0
);
manyToManyEntities.forEach(entity => {
const relations: RelationInfo[] = [];
const joinColumnMap = new Map<string, string>();
manyToManyEntities.forEach(junctionEntity => {
const firstEntity = dbModel.find(
v => v.tscName === junctionEntity.relations[0].relatedTable
)!;
const secondEntity = dbModel.find(
v => v.tscName === junctionEntity.relations[1].relatedTable
)!;
entity.Columns.forEach(column => {
column.relations.forEach(relation => {
joinColumnMap.set(relation.relatedTable, column.tsName);
relations.push(relation);
});
});
const firstRelation = firstEntity.relations.find(
v => v.relatedTable === junctionEntity.tscName
)!;
const secondRelation = secondEntity.relations.find(
v => v.relatedTable === junctionEntity.tscName
)!;
const namesOfRelatedTables = relations
.map(v => v.relatedTable)
.filter((v, i, s) => s.indexOf(v) === i);
const [
firstRelatedTable,
secondRelatedTable
] = namesOfRelatedTables;
firstRelation.relationType = "ManyToMany";
secondRelation.relationType = "ManyToMany";
firstRelation.relatedTable = secondEntity.tscName;
secondRelation.relatedTable = firstEntity.tscName;
if (namesOfRelatedTables.length === 2) {
const relatedTable1 = retval.find(
v => v.tsEntityName === firstRelatedTable
)!;
relatedTable1.Columns = relatedTable1.Columns.filter(
v =>
!v.tsName
.toLowerCase()
.startsWith(entity.tsEntityName.toLowerCase())
);
const relatedTable2 = retval.find(
v => v.tsEntityName === namesOfRelatedTables[1]
)!;
relatedTable2.Columns = relatedTable2.Columns.filter(
v =>
!v.tsName
.toLowerCase()
.startsWith(entity.tsEntityName.toLowerCase())
);
retval = retval.filter(ent => {
return ent.tsEntityName !== entity.tsEntityName;
});
firstRelation.fieldName = TomgUtils.findNameForNewField(
secondEntity.tscName,
firstEntity
);
secondRelation.fieldName = TomgUtils.findNameForNewField(
firstEntity.tscName,
secondEntity
);
firstRelation.relatedField = secondRelation.fieldName;
secondRelation.relatedField = firstRelation.fieldName;
const column1 = new ColumnInfo();
column1.tsName = secondRelatedTable;
column1.options.name = entity.sqlEntityName;
const col1Rel = new RelationInfo();
col1Rel.relatedTable = secondRelatedTable;
col1Rel.relatedColumn = secondRelatedTable;
col1Rel.relationType = "ManyToMany";
col1Rel.isOwner = true;
col1Rel.ownerColumn = firstRelatedTable;
col1Rel.joinColumn = joinColumnMap.get(namesOfRelatedTables[0]);
col1Rel.inverseJoinColumn = joinColumnMap.get(
namesOfRelatedTables[1]
);
column1.relations.push(col1Rel);
relatedTable1.Columns.push(column1);
const column2 = new ColumnInfo();
column2.tsName = firstRelatedTable;
const col2Rel = new RelationInfo();
col2Rel.relatedTable = firstRelatedTable;
col2Rel.relatedColumn = secondRelatedTable;
col2Rel.joinColumn = joinColumnMap.get(namesOfRelatedTables[1]);
col2Rel.inverseJoinColumn = joinColumnMap.get(
namesOfRelatedTables[0]
);
col2Rel.relationType = "ManyToMany";
col2Rel.isOwner = false;
column2.relations.push(col2Rel);
relatedTable2.Columns.push(column2);
firstRelation.joinTableOptions = {
name: junctionEntity.sqlName,
joinColumns: junctionEntity.relations[0].joinColumnOptions!.map(
(v, i) => {
return {
referencedColumnName: v.referencedColumnName,
name: junctionEntity.relations[0]
.joinColumnOptions![i].name
};
}
),
inverseJoinColumns: junctionEntity.relations[1].joinColumnOptions!.map(
(v, i) => {
return {
referencedColumnName: v.referencedColumnName,
name: junctionEntity.relations[1]
.joinColumnOptions![i].name
};
}
)
};
if (junctionEntity.database) {
firstRelation.joinTableOptions.database =
junctionEntity.database;
}
if (junctionEntity.schema) {
firstRelation.joinTableOptions.schema = junctionEntity.schema;
}
firstRelation.relationOptions = undefined;
secondRelation.relationOptions = undefined;
firstRelation.joinColumnOptions = undefined;
secondRelation.joinColumnOptions = undefined;
retVal = retVal.filter(ent => {
return ent.tscName !== junctionEntity.tscName;
});
});
return retval;
return retVal;
}
public async GetDataFromServer(
connectionOptons: IConnectionOptions
): Promise<EntityInfo[]> {
let dbModel = [] as EntityInfo[];
await this.ConnectToServer(connectionOptons);
connectionOptions: IConnectionOptions,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
let dbModel = [] as Entity[];
await this.ConnectToServer(connectionOptions);
const sqlEscapedSchema = AbstractDriver.escapeCommaSeparatedList(
connectionOptons.schemaName
connectionOptions.schemaName
);
dbModel = await this.GetAllTables(
sqlEscapedSchema,
connectionOptons.databaseName
connectionOptions.databaseName,
connectionOptions.skipTables
);
await this.GetCoulmnsFromEntity(
dbModel,
sqlEscapedSchema,
connectionOptons.databaseName
connectionOptions.databaseName
);
await this.GetIndexesFromEntity(
dbModel,
sqlEscapedSchema,
connectionOptons.databaseName
connectionOptions.databaseName
);
AbstractDriver.FindPrimaryColumnsFromIndexes(dbModel);
dbModel = await this.GetRelations(
dbModel,
sqlEscapedSchema,
connectionOptons.databaseName
connectionOptions.databaseName,
generationOptions
);
await this.DisconnectFromServer();
dbModel = AbstractDriver.FindManyToManyRelations(dbModel);
AbstractDriver.FindPrimaryColumnsFromIndexes(dbModel);
return dbModel;
}
@ -209,225 +216,227 @@ export default abstract class AbstractDriver {
public async GetAllTables(
schema: string,
dbNames: string
): Promise<EntityInfo[]> {
const response = await this.GetAllTablesQuery(schema, dbNames);
const ret: EntityInfo[] = [] as EntityInfo[];
dbNames: string,
tableNames: string[]
): Promise<Entity[]> {
const response = await this.GetAllTablesQuery(
schema,
dbNames,
tableNames
);
const ret: Entity[] = [] as Entity[];
response.forEach(val => {
const ent: EntityInfo = new EntityInfo();
ent.tsEntityName = val.TABLE_NAME;
ent.sqlEntityName = val.TABLE_NAME;
ent.Schema = val.TABLE_SCHEMA;
ent.Columns = [] as ColumnInfo[];
ent.Indexes = [] as IndexInfo[];
ent.Database = dbNames.includes(",") ? val.DB_NAME : "";
ret.push(ent);
ret.push({
columns: [],
indices: [],
relations: [],
relationIds: [],
sqlName: val.TABLE_NAME,
tscName: val.TABLE_NAME,
database: dbNames.includes(",") ? val.DB_NAME : "",
schema: val.TABLE_SCHEMA,
fileImports: []
});
});
return ret;
}
public static GetRelationsFromRelationTempInfo(
relationsTemp: RelationTempInfo[],
entities: EntityInfo[]
relationsTemp: RelationInternal[],
entities: Entity[],
generationOptions: IGenerationOptions
) {
relationsTemp.forEach(relationTmp => {
if (relationTmp.ownerColumnsNames.length > 1) {
const relatedTable = entities.find(
entity => entity.tsEntityName === relationTmp.ownerTable
)!;
if (
relatedTable.Columns.length !==
relationTmp.ownerColumnsNames.length * 2
) {
TomgUtils.LogError(
`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} wasn't generated correctly - complex relationships aren't supported yet.`,
false
);
return;
}
const secondRelation = relationsTemp.find(
relation =>
relation.ownerTable === relatedTable.tsEntityName &&
relation.referencedTable !== relationTmp.referencedTable
)!;
if (!secondRelation) {
TomgUtils.LogError(
`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} wasn't generated correctly - complex relationships aren't supported yet.`,
false
);
return;
}
}
const ownerEntity = entities.find(
entitity => entitity.tsEntityName === relationTmp.ownerTable
entity => entity.tscName === relationTmp.ownerTable.tscName
);
if (!ownerEntity) {
TomgUtils.LogError(
`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity model ${relationTmp.ownerTable}.`
`Relation between tables ${relationTmp.ownerTable.sqlName} and ${relationTmp.relatedTable.sqlName} didn't found entity model ${relationTmp.ownerTable.sqlName}.`
);
return;
}
const referencedEntity = entities.find(
entitity =>
entitity.tsEntityName === relationTmp.referencedTable
entity => entity.tscName === relationTmp.relatedTable.tscName
);
if (!referencedEntity) {
TomgUtils.LogError(
`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity model ${relationTmp.referencedTable}.`
`Relation between tables ${relationTmp.ownerTable.sqlName} and ${relationTmp.relatedTable.sqlName} didn't found entity model ${relationTmp.relatedTable.sqlName}.`
);
return;
}
const ownerColumns: Column[] = [];
const relatedColumns: Column[] = [];
for (
let relationColumnIndex = 0;
relationColumnIndex < relationTmp.ownerColumnsNames.length;
relationColumnIndex < relationTmp.ownerColumns.length;
relationColumnIndex++
) {
const ownerColumn = ownerEntity.Columns.find(
const ownerColumn = ownerEntity.columns.find(
column =>
column.tsName ===
relationTmp.ownerColumnsNames[relationColumnIndex]
column.tscName ===
relationTmp.ownerColumns[relationColumnIndex]
);
if (!ownerColumn) {
TomgUtils.LogError(
`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity column ${relationTmp.ownerTable}.${ownerColumn}.`
`Relation between tables ${relationTmp.ownerTable.sqlName} and ${relationTmp.relatedTable.sqlName} didn't found entity column ${relationTmp.ownerTable.sqlName}.${ownerColumn}.`
);
return;
}
const relatedColumn = referencedEntity.Columns.find(
const relatedColumn = referencedEntity.columns.find(
column =>
column.tsName ===
relationTmp.referencedColumnsNames[relationColumnIndex]
column.tscName ===
relationTmp.relatedColumns[relationColumnIndex]
);
if (!relatedColumn) {
TomgUtils.LogError(
`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity column ${relationTmp.referencedTable}.${relatedColumn}.`
`Relation between tables ${relationTmp.ownerTable.sqlName} and ${relationTmp.relatedTable.sqlName} didn't found entity column ${relationTmp.relatedTable.sqlName}.${relatedColumn}.`
);
return;
}
let isOneToMany: boolean;
isOneToMany = false;
const index = ownerEntity.Indexes.find(
ind =>
ind.isUnique &&
ind.columns.length === 1 &&
ind.columns[0].name === ownerColumn!.tsName
ownerColumns.push(ownerColumn);
relatedColumns.push(relatedColumn);
}
let isOneToMany: boolean;
isOneToMany = false;
const index = ownerEntity.indices.find(
ind =>
ind.options.unique &&
ind.columns.length === ownerColumns.length &&
ownerColumns.every(ownerColumn =>
ind.columns.some(col => col === ownerColumn.tscName)
)
);
isOneToMany = !index;
ownerColumns.forEach(column => {
column.isUsedInRelationAsOwner = true;
});
relatedColumns.forEach(column => {
column.isUsedInRelationAsReferenced = true;
});
let fieldName = "";
if (ownerColumns.length === 1) {
fieldName = TomgUtils.findNameForNewField(
ownerColumns[0].tscName,
ownerEntity
);
isOneToMany = !index;
} else {
fieldName = TomgUtils.findNameForNewField(
relationTmp.relatedTable.tscName,
ownerEntity
);
}
const ownerRelation = new RelationInfo();
ownerRelation.actionOnDelete = relationTmp.actionOnDelete;
ownerRelation.actionOnUpdate = relationTmp.actionOnUpdate;
ownerRelation.isOwner = true;
ownerRelation.relatedColumn = relatedColumn.tsName.toLowerCase();
ownerRelation.relatedTable = relationTmp.referencedTable;
ownerRelation.ownerTable = relationTmp.ownerTable;
ownerRelation.relationType = isOneToMany
? "ManyToOne"
: "OneToOne";
const relationOptions: RelationOptions = {
onDelete: relationTmp.onDelete,
onUpdate: relationTmp.onUpdate
};
let columnName = ownerEntity.tsEntityName;
if (
referencedEntity.Columns.some(v => v.tsName === columnName)
) {
columnName += "_";
for (let i = 2; i <= referencedEntity.Columns.length; i++) {
columnName =
columnName.substring(
0,
columnName.length - i.toString().length
) + i.toString();
if (
referencedEntity.Columns.every(
v => v.tsName !== columnName
)
) {
break;
}
const ownerRelation: Relation = {
fieldName,
relatedField: TomgUtils.findNameForNewField(
relationTmp.ownerTable.tscName,
relationTmp.relatedTable
),
joinColumnOptions: relationTmp.ownerColumns.map((v, idx) => {
const retVal: Required<JoinColumnOptions> = {
name: v,
referencedColumnName: relationTmp.relatedColumns[idx]
};
return retVal;
}),
relatedTable: relationTmp.relatedTable.tscName,
relationType: isOneToMany ? "ManyToOne" : "OneToOne"
};
if (JSON.stringify(relationOptions) !== "{}") {
ownerRelation.relationOptions = relationOptions;
}
const relatedRelation: Relation = {
fieldName: ownerRelation.relatedField,
relatedField: ownerRelation.fieldName,
relatedTable: relationTmp.ownerTable.tscName,
relationType: isOneToMany ? "OneToMany" : "OneToOne"
};
ownerEntity.relations.push(ownerRelation);
relationTmp.relatedTable.relations.push(relatedRelation);
if (generationOptions.relationIds && ownerColumns.length === 1) {
let relationIdFieldName = "";
relationIdFieldName = TomgUtils.findNameForNewField(
ownerColumns[0].tscName,
ownerEntity
);
let fieldType = "";
if (isOneToMany) {
fieldType = `${ownerColumns[0].tscType}[]`;
} else {
fieldType = ownerColumns[0].tscType;
if (ownerColumns[0].options.nullable) {
fieldType += " | null";
}
}
ownerRelation.ownerColumn = columnName;
ownerColumn.relations.push(ownerRelation);
if (isOneToMany) {
const col = new ColumnInfo();
col.tsName = columnName;
const referencedRelation = new RelationInfo();
col.relations.push(referencedRelation);
referencedRelation.actionOnDelete =
relationTmp.actionOnDelete;
referencedRelation.actionOnUpdate =
relationTmp.actionOnUpdate;
referencedRelation.isOwner = false;
referencedRelation.relatedColumn = ownerColumn.tsName;
referencedRelation.relatedTable = relationTmp.ownerTable;
referencedRelation.ownerTable = relationTmp.referencedTable;
referencedRelation.ownerColumn = relatedColumn.tsName;
referencedRelation.relationType = "OneToMany";
referencedEntity.Columns.push(col);
} else {
const col = new ColumnInfo();
col.tsName = columnName;
const referencedRelation = new RelationInfo();
col.relations.push(referencedRelation);
referencedRelation.actionOnDelete =
relationTmp.actionOnDelete;
referencedRelation.actionOnUpdate =
relationTmp.actionOnUpdate;
referencedRelation.isOwner = false;
referencedRelation.relatedColumn = ownerColumn.tsName;
referencedRelation.relatedTable = relationTmp.ownerTable;
referencedRelation.ownerTable = relationTmp.referencedTable;
referencedRelation.ownerColumn = relatedColumn.tsName;
referencedRelation.relationType = "OneToOne";
referencedEntity.Columns.push(col);
}
ownerEntity.relationIds.push({
fieldName: relationIdFieldName,
fieldType,
relationField: ownerRelation.fieldName
});
// TODO: RelationId on ManyToMany
}
});
return entities;
}
public abstract async GetCoulmnsFromEntity(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]>;
): Promise<Entity[]>;
public abstract async GetIndexesFromEntity(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]>;
): Promise<Entity[]>;
public abstract async GetRelations(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]>;
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]>;
public static FindPrimaryColumnsFromIndexes(dbModel: EntityInfo[]) {
public static FindPrimaryColumnsFromIndexes(dbModel: Entity[]) {
dbModel.forEach(entity => {
const primaryIndex = entity.Indexes.find(v => v.isPrimaryKey);
entity.Columns.filter(
col =>
primaryIndex &&
primaryIndex.columns.some(
cIndex => cIndex.name === col.tsName
)
).forEach(col => {
// eslint-disable-next-line no-param-reassign
col.options.primary = true;
});
const primaryIndex = entity.indices.find(v => v.primary);
entity.columns
.filter(
col =>
primaryIndex &&
primaryIndex.columns.some(
cIndex => cIndex === col.tscName
)
)
.forEach(col => {
// eslint-disable-next-line no-param-reassign
col.primary = true;
if (
primaryIndex!.columns.length === 1 &&
col.options.unique
) {
delete col.options.unique;
}
});
if (
!entity.Columns.some(v => {
return !!v.options.primary;
!entity.columns.some(v => {
return !!v.primary;
})
) {
TomgUtils.LogError(
`Table ${entity.tsEntityName} has no PK.`,
false
);
TomgUtils.LogError(`Table ${entity.tscName} has no PK.`, false);
}
});
}

View File

@ -4,12 +4,12 @@ import * as TypeormDriver from "typeorm/driver/sqlserver/SqlServerDriver";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import * as TomgUtils from "../Utils";
import AbstractDriver from "./AbstractDriver";
import EntityInfo from "../models/EntityInfo";
import ColumnInfo from "../models/ColumnInfo";
import IndexInfo from "../models/IndexInfo";
import IndexColumnInfo from "../models/IndexColumnInfo";
import RelationTempInfo from "../models/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 MssqlDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.SqlServerDriver({
@ -24,26 +24,36 @@ export default class MssqlDriver extends AbstractDriver {
private Connection: MSSQL.ConnectionPool;
public GetAllTablesQuery = async (schema: string, dbNames: string) => {
public GetAllTablesQuery = async (
schema: string,
dbNames: string,
tableNames: string[]
) => {
const request = new MSSQL.Request(this.Connection);
const tableCondition =
tableNames.length > 0
? ` AND NOT TABLE_NAME IN ('${tableNames.join("','")}')`
: "";
const response: {
TABLE_SCHEMA: string;
TABLE_NAME: string;
DB_NAME: string;
}[] = (await request.query(
`SELECT TABLE_SCHEMA,TABLE_NAME, table_catalog as "DB_NAME" FROM INFORMATION_SCHEMA.TABLES
}[] = (
await request.query(
`SELECT TABLE_SCHEMA,TABLE_NAME, table_catalog as "DB_NAME" FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG in (${MssqlDriver.escapeCommaSeparatedList(
dbNames
)})`
)).recordset;
dbNames
)}) ${tableCondition}`
)
).recordset;
return response;
};
public async GetCoulmnsFromEntity(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]> {
): Promise<Entity[]> {
const request = new MSSQL.Request(this.Connection);
const response: {
TABLE_NAME: string;
@ -56,140 +66,145 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
NUMERIC_SCALE: number;
IsIdentity: number;
IsUnique: 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,
(SELECT count(*)
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
inner join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cu
on cu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
where
tc.CONSTRAINT_TYPE = 'UNIQUE'
and tc.TABLE_NAME = c.TABLE_NAME
and cu.COLUMN_NAME = c.COLUMN_NAME
and tc.TABLE_SCHEMA=c.TABLE_SCHEMA) IsUnique
FROM INFORMATION_SCHEMA.COLUMNS c
where TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG in (${MssqlDriver.escapeCommaSeparatedList(
dbNames
)})
order by ordinal_position`)).recordset;
}[] = (
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,
(SELECT count(*)
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
inner join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cu
on cu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
where
tc.CONSTRAINT_TYPE = 'UNIQUE'
and tc.TABLE_NAME = c.TABLE_NAME
and cu.COLUMN_NAME = c.COLUMN_NAME
and tc.TABLE_SCHEMA=c.TABLE_SCHEMA) IsUnique
FROM INFORMATION_SCHEMA.COLUMNS c
where TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG in (${MssqlDriver.escapeCommaSeparatedList(
dbNames
)})
order by ordinal_position`)
).recordset;
entities.forEach(ent => {
response
.filter(filterVal => {
return filterVal.TABLE_NAME === ent.tsEntityName;
return filterVal.TABLE_NAME === ent.tscName;
})
.forEach(resp => {
const colInfo: ColumnInfo = new ColumnInfo();
colInfo.tsName = resp.COLUMN_NAME;
colInfo.options.name = resp.COLUMN_NAME;
colInfo.options.nullable = resp.IS_NULLABLE === "YES";
colInfo.options.generated = resp.IsIdentity === 1;
colInfo.options.unique = resp.IsUnique === 1;
colInfo.options.default = MssqlDriver.ReturnDefaultValueFunction(
const tscName = resp.COLUMN_NAME;
const options: Column["options"] = {
name: resp.COLUMN_NAME
};
if (resp.IS_NULLABLE === "YES") options.nullable = true;
if (resp.IsUnique === 1) options.unique = true;
const generated = resp.IsIdentity === 1 ? true : undefined;
const defaultValue = MssqlDriver.ReturnDefaultValueFunction(
resp.COLUMN_DEFAULT
);
colInfo.options.type = resp.DATA_TYPE as any;
const columnType = resp.DATA_TYPE;
let tscType = "";
switch (resp.DATA_TYPE) {
case "bigint":
colInfo.tsType = "string";
tscType = "string";
break;
case "bit":
colInfo.tsType = "boolean";
tscType = "boolean";
break;
case "decimal":
colInfo.tsType = "number";
tscType = "number";
break;
case "int":
colInfo.tsType = "number";
tscType = "number";
break;
case "money":
colInfo.tsType = "number";
tscType = "number";
break;
case "numeric":
colInfo.tsType = "number";
tscType = "number";
break;
case "smallint":
colInfo.tsType = "number";
tscType = "number";
break;
case "smallmoney":
colInfo.tsType = "number";
tscType = "number";
break;
case "tinyint":
colInfo.tsType = "number";
tscType = "number";
break;
case "float":
colInfo.tsType = "number";
tscType = "number";
break;
case "real":
colInfo.tsType = "number";
tscType = "number";
break;
case "date":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "datetime2":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "datetime":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "datetimeoffset":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "smalldatetime":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "time":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "char":
colInfo.tsType = "string";
tscType = "string";
break;
case "text":
colInfo.tsType = "string";
tscType = "string";
break;
case "varchar":
colInfo.tsType = "string";
tscType = "string";
break;
case "nchar":
colInfo.tsType = "string";
tscType = "string";
break;
case "ntext":
colInfo.tsType = "string";
tscType = "string";
break;
case "nvarchar":
colInfo.tsType = "string";
tscType = "string";
break;
case "binary":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "image":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "varbinary":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "hierarchyid":
colInfo.tsType = "string";
tscType = "string";
break;
case "sql_variant":
colInfo.tsType = "string";
tscType = "string";
break;
case "timestamp":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "uniqueidentifier":
colInfo.tsType = "string";
tscType = "string";
break;
case "xml":
colInfo.tsType = "string";
tscType = "string";
break;
case "geometry":
colInfo.tsType = "string";
tscType = "string";
break;
case "geography":
colInfo.tsType = "string";
tscType = "string";
break;
default:
tscType = "NonNullable<unknown>";
TomgUtils.LogError(
`Unknown column type: ${resp.DATA_TYPE} table name: ${resp.TABLE_NAME} column name: ${resp.COLUMN_NAME}`
);
@ -198,36 +213,42 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
if (
this.ColumnTypesWithPrecision.some(
v => v === colInfo.options.type
v => v === columnType
)
) {
colInfo.options.precision = resp.NUMERIC_PRECISION;
colInfo.options.scale = resp.NUMERIC_SCALE;
if (resp.NUMERIC_PRECISION !== null) {
options.precision = resp.NUMERIC_PRECISION;
}
if (resp.NUMERIC_SCALE !== null) {
options.scale = resp.NUMERIC_SCALE;
}
}
if (
this.ColumnTypesWithLength.some(
v => v === colInfo.options.type
)
this.ColumnTypesWithLength.some(v => v === columnType)
) {
colInfo.options.length =
options.length =
resp.CHARACTER_MAXIMUM_LENGTH > 0
? resp.CHARACTER_MAXIMUM_LENGTH
: undefined;
}
if (colInfo.options.type) {
ent.Columns.push(colInfo);
}
ent.columns.push({
generated,
type: columnType,
default: defaultValue,
options,
tscName,
tscType
});
});
});
return entities;
}
public async GetIndexesFromEntity(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]> {
): Promise<Entity[]> {
const request = new MSSQL.Request(this.Connection);
const response: {
TableName: string;
@ -245,63 +266,64 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
ColumnName: string;
is_unique: boolean;
is_primary_key: boolean;
}[] = (await request.query(`SELECT
TableName = t.name,
IndexName = ind.name,
ColumnName = col.name,
ind.is_unique,
ind.is_primary_key
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 in (${schema})
ORDER BY
t.name, ind.name, ind.index_id, ic.key_ordinal;`)).recordset;
}[] = (
await request.query(`SELECT
TableName = t.name,
IndexName = ind.name,
ColumnName = col.name,
ind.is_unique,
ind.is_primary_key
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 in (${schema})
ORDER BY
t.name, ind.name, ind.index_id, ic.key_ordinal;`)
).recordset;
response.push(...resp);
})
);
entities.forEach(ent => {
response
.filter(filterVal => filterVal.TableName === ent.tsEntityName)
.forEach(resp => {
let indexInfo: IndexInfo = {} as IndexInfo;
const indexColumnInfo: IndexColumnInfo = {} as IndexColumnInfo;
if (
ent.Indexes.filter(filterVal => {
return filterVal.name === resp.IndexName;
}).length > 0
) {
[indexInfo] = ent.Indexes.filter(filterVal => {
return filterVal.name === resp.IndexName;
});
} else {
indexInfo.columns = [] as IndexColumnInfo[];
indexInfo.name = resp.IndexName;
indexInfo.isUnique = resp.is_unique;
indexInfo.isPrimaryKey = resp.is_primary_key;
ent.Indexes.push(indexInfo);
}
indexColumnInfo.name = resp.ColumnName;
indexInfo.columns.push(indexColumnInfo);
const entityIndices = response.filter(
filterVal => filterVal.TableName === ent.tscName
);
const indexNames = new Set(entityIndices.map(v => v.IndexName));
indexNames.forEach(indexName => {
const records = entityIndices.filter(
v => v.IndexName === indexName
);
const indexInfo: Index = {
columns: [],
options: {},
name: records[0].IndexName
};
if (records[0].is_primary_key) indexInfo.primary = true;
if (records[0].is_unique) indexInfo.options.unique = true;
records.forEach(record => {
indexInfo.columns.push(record.ColumnName);
});
ent.indices.push(indexInfo);
});
});
return entities;
}
public async GetRelations(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]> {
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
const request = new MSSQL.Request(this.Connection);
const response: {
TableWithForeignKey: string;
@ -325,78 +347,93 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
onDelete: "RESTRICT" | "CASCADE" | "SET_NULL" | "NO_ACTION";
onUpdate: "RESTRICT" | "CASCADE" | "SET_NULL" | "NO_ACTION";
objectId: 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 as objectId
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 in (${schema})
order by
TableWithForeignKey, FK_PartNo`)).recordset;
}[] = (
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 as objectId
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 in (${schema})
order by
TableWithForeignKey, FK_PartNo`)
).recordset;
response.push(...resp);
})
);
const relationsTemp: RelationTempInfo[] = [] as RelationTempInfo[];
response.forEach(resp => {
let rels = relationsTemp.find(
val => val.objectId === resp.objectId
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.objectId));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.objectId === relationId);
const ownerTable = entities.find(
v => v.sqlName === rows[0].TableWithForeignKey
);
if (rels === undefined) {
rels = {} as RelationTempInfo;
rels.ownerColumnsNames = [];
rels.referencedColumnsNames = [];
switch (resp.onDelete) {
case "NO_ACTION":
rels.actionOnDelete = null;
break;
case "SET_NULL":
rels.actionOnDelete = "SET NULL";
break;
default:
rels.actionOnDelete = resp.onDelete;
break;
}
switch (resp.onUpdate) {
case "NO_ACTION":
rels.actionOnUpdate = null;
break;
case "SET_NULL":
rels.actionOnUpdate = "SET NULL";
break;
default:
rels.actionOnUpdate = resp.onUpdate;
break;
}
rels.objectId = resp.objectId;
rels.ownerTable = resp.TableWithForeignKey;
rels.referencedTable = resp.TableReferenced;
relationsTemp.push(rels);
const relatedTable = entities.find(
v => v.sqlName === rows[0].TableReferenced
);
if (!ownerTable || !relatedTable) {
TomgUtils.LogError(
`Relation between tables ${rows[0].TableWithForeignKey} and ${rows[0].TableReferenced} wasn't found in entity model.`,
true
);
return;
}
rels.ownerColumnsNames.push(resp.ForeignKeyColumn);
rels.referencedColumnsNames.push(resp.ForeignKeyColumnReferenced);
const internal: RelationInternal = {
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
};
switch (rows[0].onDelete) {
case "NO_ACTION":
break;
case "SET_NULL":
internal.onDelete = "SET NULL";
break;
default:
internal.onDelete = rows[0].onDelete;
break;
}
switch (rows[0].onUpdate) {
case "NO_ACTION":
break;
case "SET_NULL":
internal.onUpdate = "SET NULL";
break;
default:
internal.onUpdate = rows[0].onUpdate;
break;
}
rows.forEach(row => {
internal.ownerColumns.push(row.ForeignKeyColumn);
internal.relatedColumns.push(row.ForeignKeyColumnReferenced);
});
relationsTemp.push(internal);
});
const retVal = MssqlDriver.GetRelationsFromRelationTempInfo(
relationsTemp,
entities
entities,
generationOptions
);
return retVal;
}
@ -417,7 +454,7 @@ order by
},
password: connectionOptons.password,
port: connectionOptons.port,
requestTimeout: connectionOptons.timeout,
requestTimeout: 60 * 60 * 1000,
server: connectionOptons.host,
user: connectionOptons.user
};
@ -465,10 +502,10 @@ order by
private static ReturnDefaultValueFunction(
defVal: string | null
): string | null {
): string | undefined {
let defaultValue = defVal;
if (!defaultValue) {
return null;
return undefined;
}
if (defaultValue.startsWith("(") && defaultValue.endsWith(")")) {
defaultValue = defaultValue.slice(1, -1);

View File

@ -4,12 +4,12 @@ import * as TypeormDriver from "typeorm/driver/mysql/MysqlDriver";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import * as TomgUtils from "../Utils";
import AbstractDriver from "./AbstractDriver";
import EntityInfo from "../models/EntityInfo";
import ColumnInfo from "../models/ColumnInfo";
import IndexInfo from "../models/IndexInfo";
import IndexColumnInfo from "../models/IndexColumnInfo";
import RelationTempInfo from "../models/RelationTempInfo";
import IConnectionOptions from "../IConnectionOptions";
import { Entity } from "../models/Entity";
import { Column } from "../models/Column";
import { Index } from "../models/Index";
import { RelationInternal } from "../models/RelationInternal";
import IGenerationOptions from "../IGenerationOptions";
export default class MysqlDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.MysqlDriver({
@ -26,7 +26,15 @@ export default class MysqlDriver extends AbstractDriver {
private Connection: MYSQL.Connection;
public GetAllTablesQuery = async (schema: string, dbNames: string) => {
public GetAllTablesQuery = async (
schema: string,
dbNames: string,
tableNames: string[]
) => {
const tableCondition =
tableNames.length > 0
? ` AND NOT TABLE_NAME IN ('${tableNames.join("','")}')`
: "";
const response = this.ExecQuery<{
TABLE_SCHEMA: string;
TABLE_NAME: string;
@ -36,15 +44,15 @@ export default class MysqlDriver extends AbstractDriver {
WHERE table_type='BASE TABLE'
AND table_schema IN (${MysqlDriver.escapeCommaSeparatedList(
dbNames
)})`);
)}) ${tableCondition}`);
return response;
};
public async GetCoulmnsFromEntity(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]> {
): Promise<Entity[]> {
const response = await this.ExecQuery<{
TABLE_NAME: string;
COLUMN_NAME: string;
@ -52,8 +60,8 @@ export default class MysqlDriver extends AbstractDriver {
IS_NULLABLE: string;
DATA_TYPE: string;
CHARACTER_MAXIMUM_LENGTH: number;
NUMERIC_PRECISION: number;
NUMERIC_SCALE: number;
NUMERIC_PRECISION: number | null;
NUMERIC_SCALE: number | null;
IsIdentity: number;
COLUMN_TYPE: string;
COLUMN_KEY: string;
@ -66,152 +74,170 @@ export default class MysqlDriver extends AbstractDriver {
order by ordinal_position`);
entities.forEach(ent => {
response
.filter(filterVal => filterVal.TABLE_NAME === ent.tsEntityName)
.filter(filterVal => filterVal.TABLE_NAME === ent.tscName)
.forEach(resp => {
const colInfo: ColumnInfo = new ColumnInfo();
colInfo.tsName = resp.COLUMN_NAME;
colInfo.options.name = resp.COLUMN_NAME;
colInfo.options.nullable = resp.IS_NULLABLE === "YES";
colInfo.options.generated = resp.IsIdentity === 1;
colInfo.options.unique = resp.COLUMN_KEY === "UNI";
colInfo.options.default = MysqlDriver.ReturnDefaultValueFunction(
resp.COLUMN_DEFAULT
);
colInfo.options.type = resp.DATA_TYPE as any;
colInfo.options.unsigned = resp.COLUMN_TYPE.endsWith(
" unsigned"
const tscName = resp.COLUMN_NAME;
let tscType = "";
const options: Column["options"] = {
name: resp.COLUMN_NAME
};
const generated = resp.IsIdentity === 1 ? true : undefined;
const defaultValue = MysqlDriver.ReturnDefaultValueFunction(
resp.COLUMN_DEFAULT,
resp.DATA_TYPE
);
let columnType = resp.DATA_TYPE;
if (resp.IS_NULLABLE === "YES") options.nullable = true;
if (resp.COLUMN_KEY === "UNI") options.unique = true;
if (resp.COLUMN_TYPE.endsWith(" unsigned"))
options.unsigned = true;
switch (resp.DATA_TYPE) {
case "int":
colInfo.tsType = "number";
tscType = "number";
break;
case "bit":
if (resp.COLUMN_TYPE === "bit(1)") {
colInfo.options.width = 1;
colInfo.tsType = "boolean";
tscType = "boolean";
} else {
colInfo.tsType = "number";
tscType = "number";
}
break;
case "tinyint":
if (resp.COLUMN_TYPE === "tinyint(1)") {
colInfo.options.width = 1;
colInfo.tsType = "boolean";
options.width = 1;
tscType = "boolean";
} else {
colInfo.tsType = "number";
tscType = "number";
}
break;
case "smallint":
colInfo.tsType = "number";
tscType = "number";
break;
case "mediumint":
colInfo.tsType = "number";
tscType = "number";
break;
case "bigint":
colInfo.tsType = "string";
tscType = "string";
break;
case "float":
colInfo.tsType = "number";
tscType = "number";
break;
case "double":
colInfo.tsType = "number";
tscType = "number";
break;
case "decimal":
colInfo.tsType = "string";
tscType = "string";
break;
case "date":
colInfo.tsType = "string";
tscType = "string";
break;
case "datetime":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "timestamp":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "time":
colInfo.tsType = "string";
tscType = "string";
break;
case "year":
colInfo.tsType = "number";
tscType = "number";
break;
case "char":
colInfo.tsType = "string";
tscType = "string";
break;
case "varchar":
colInfo.tsType = "string";
tscType = "string";
break;
case "blob":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "text":
colInfo.tsType = "string";
tscType = "string";
break;
case "tinyblob":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "tinytext":
colInfo.tsType = "string";
tscType = "string";
break;
case "mediumblob":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "mediumtext":
colInfo.tsType = "string";
tscType = "string";
break;
case "longblob":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "longtext":
colInfo.tsType = "string";
tscType = "string";
break;
case "enum":
colInfo.tsType = resp.COLUMN_TYPE.substring(
tscType = resp.COLUMN_TYPE.substring(
5,
resp.COLUMN_TYPE.length - 1
)
.replace(/'/gi, '"')
.replace(/","/gi, '" | "');
colInfo.options.enum = resp.COLUMN_TYPE.substring(
options.enum = resp.COLUMN_TYPE.substring(
5,
resp.COLUMN_TYPE.length - 1
).replace(/'/gi, '"');
)
.replace(/'/gi, "")
.split(",");
break;
case "set":
tscType = `(${resp.COLUMN_TYPE.substring(
4,
resp.COLUMN_TYPE.length - 1
)
.replace(/'/gi, '"')
.replace(/","/gi, '" | "')})[]`;
options.enum = resp.COLUMN_TYPE.substring(
4,
resp.COLUMN_TYPE.length - 1
)
.replace(/'/gi, "")
.split(",");
break;
case "json":
colInfo.tsType = "object";
tscType = "object";
break;
case "binary":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "varbinary":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "geometry":
colInfo.tsType = "string";
tscType = "string";
break;
case "point":
colInfo.tsType = "string";
tscType = "string";
break;
case "linestring":
colInfo.tsType = "string";
tscType = "string";
break;
case "polygon":
colInfo.tsType = "string";
tscType = "string";
break;
case "multipoint":
colInfo.tsType = "string";
tscType = "string";
break;
case "multilinestring":
colInfo.tsType = "string";
tscType = "string";
break;
case "multipolygon":
colInfo.tsType = "string";
tscType = "string";
break;
case "geometrycollection":
case "geomcollection":
colInfo.options.type = "geometrycollection";
colInfo.tsType = "string";
columnType = "geometrycollection";
tscType = "string";
break;
default:
tscType = "NonNullable<unknown>";
TomgUtils.LogError(
`Unknown column type: ${resp.DATA_TYPE} table name: ${resp.TABLE_NAME} column name: ${resp.COLUMN_NAME}`
);
@ -219,48 +245,53 @@ export default class MysqlDriver extends AbstractDriver {
}
if (
this.ColumnTypesWithPrecision.some(
v => v === colInfo.options.type
v => v === columnType
)
) {
colInfo.options.precision = resp.NUMERIC_PRECISION;
colInfo.options.scale = resp.NUMERIC_SCALE;
if (resp.NUMERIC_PRECISION !== null) {
options.precision = resp.NUMERIC_PRECISION;
}
if (resp.NUMERIC_SCALE !== null) {
options.scale = resp.NUMERIC_SCALE;
}
}
if (
this.ColumnTypesWithLength.some(
v => v === colInfo.options.type
)
this.ColumnTypesWithLength.some(v => v === columnType)
) {
colInfo.options.length =
options.length =
resp.CHARACTER_MAXIMUM_LENGTH > 0
? resp.CHARACTER_MAXIMUM_LENGTH
: undefined;
}
if (
this.ColumnTypesWithWidth.some(
v =>
v === colInfo.options.type &&
colInfo.tsType !== "boolean"
v => v === columnType && tscType !== "boolean"
)
) {
colInfo.options.width =
options.width =
resp.CHARACTER_MAXIMUM_LENGTH > 0
? resp.CHARACTER_MAXIMUM_LENGTH
: undefined;
}
if (colInfo.options.type) {
ent.Columns.push(colInfo);
}
ent.columns.push({
generated,
type: columnType,
default: defaultValue,
options,
tscName,
tscType
});
});
});
return entities;
}
public async GetIndexesFromEntity(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]> {
): Promise<Entity[]> {
const response = await this.ExecQuery<{
TableName: string;
IndexName: string;
@ -274,39 +305,39 @@ export default class MysqlDriver extends AbstractDriver {
dbNames
)})`);
entities.forEach(ent => {
response
.filter(filterVal => filterVal.TableName === ent.tsEntityName)
.forEach(resp => {
let indexInfo: IndexInfo = {} as IndexInfo;
const indexColumnInfo: IndexColumnInfo = {} as IndexColumnInfo;
if (
ent.Indexes.filter(
filterVal => filterVal.name === resp.IndexName
).length > 0
) {
indexInfo = ent.Indexes.find(
filterVal => filterVal.name === resp.IndexName
)!;
} else {
indexInfo.columns = [] as IndexColumnInfo[];
indexInfo.name = resp.IndexName;
indexInfo.isUnique = resp.is_unique === 1;
indexInfo.isPrimaryKey = resp.is_primary_key === 1;
ent.Indexes.push(indexInfo);
}
indexColumnInfo.name = resp.ColumnName;
indexInfo.columns.push(indexColumnInfo);
const entityIndices = response.filter(
filterVal => filterVal.TableName === ent.tscName
);
const indexNames = new Set(entityIndices.map(v => v.IndexName));
indexNames.forEach(indexName => {
const records = entityIndices.filter(
v => v.IndexName === indexName
);
const indexInfo: Index = {
name: indexName,
columns: [],
options: {}
};
if (records[0].is_primary_key === 1) indexInfo.primary = true;
if (records[0].is_unique === 1) indexInfo.options.unique = true;
records.forEach(record => {
indexInfo.columns.push(record.ColumnName);
});
ent.indices.push(indexInfo);
});
});
return entities;
}
public async GetRelations(
entities: EntityInfo[],
entities: Entity[],
schema: string,
dbNames: string
): Promise<EntityInfo[]> {
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
const response = await this.ExecQuery<{
TableWithForeignKey: string;
FK_PartNo: number;
@ -334,30 +365,48 @@ export default class MysqlDriver extends AbstractDriver {
TABLE_SCHEMA IN (${MysqlDriver.escapeCommaSeparatedList(dbNames)})
AND CU.REFERENCED_TABLE_NAME IS NOT NULL;
`);
const relationsTemp: RelationTempInfo[] = [] as RelationTempInfo[];
response.forEach(resp => {
let rels = relationsTemp.find(
val => val.objectId === resp.object_id
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.object_id));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.object_id === relationId);
const ownerTable = entities.find(
v => v.sqlName === rows[0].TableWithForeignKey
);
if (rels === undefined) {
rels = {} as RelationTempInfo;
rels.ownerColumnsNames = [];
rels.referencedColumnsNames = [];
rels.actionOnDelete =
resp.onDelete === "NO_ACTION" ? null : resp.onDelete;
rels.actionOnUpdate =
resp.onUpdate === "NO_ACTION" ? null : resp.onUpdate;
rels.objectId = resp.object_id;
rels.ownerTable = resp.TableWithForeignKey;
rels.referencedTable = resp.TableReferenced;
relationsTemp.push(rels);
const relatedTable = entities.find(
v => v.sqlName === rows[0].TableReferenced
);
if (!ownerTable || !relatedTable) {
TomgUtils.LogError(
`Relation between tables ${rows[0].TableWithForeignKey} and ${rows[0].TableReferenced} wasn't found in entity model.`,
true
);
return;
}
rels.ownerColumnsNames.push(resp.ForeignKeyColumn);
rels.referencedColumnsNames.push(resp.ForeignKeyColumnReferenced);
const internal: RelationInternal = {
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
};
if (rows[0].onDelete !== "NO_ACTION") {
internal.onDelete = rows[0].onDelete;
}
if (rows[0].onUpdate !== "NO_ACTION") {
internal.onUpdate = rows[0].onUpdate;
}
rows.forEach(row => {
internal.ownerColumns.push(row.ForeignKeyColumn);
internal.relatedColumns.push(row.ForeignKeyColumnReferenced);
});
relationsTemp.push(internal);
});
const retVal = MysqlDriver.GetRelationsFromRelationTempInfo(
relationsTemp,
entities
entities,
generationOptions
);
return retVal;
}
@ -394,7 +443,7 @@ export default class MysqlDriver extends AbstractDriver {
ssl: {
rejectUnauthorized: false
},
timeout: connectionOptons.timeout,
timeout: 60 * 60 * 1000,
user: connectionOptons.user
};
} else {
@ -403,7 +452,7 @@ export default class MysqlDriver extends AbstractDriver {
host: connectionOptons.host,
password: connectionOptons.password,
port: connectionOptons.port,
timeout: connectionOptons.timeout,
timeout: 60 * 60 * 1000,
user: connectionOptons.user
};
}
@ -429,21 +478,19 @@ export default class MysqlDriver extends AbstractDriver {
}
public async CreateDB(dbName: string) {
await this.ExecQuery<any>(`CREATE DATABASE ${dbName}; `);
await this.ExecQuery(`CREATE DATABASE ${dbName}; `);
}
public async UseDB(dbName: string) {
await this.ExecQuery<any>(`USE ${dbName}; `);
await this.ExecQuery(`USE ${dbName}; `);
}
public async DropDB(dbName: string) {
await this.ExecQuery<any>(`DROP DATABASE ${dbName}; `);
await this.ExecQuery(`DROP DATABASE ${dbName}; `);
}
public async CheckIfDBExists(dbName: string): Promise<boolean> {
const resp = await this.ExecQuery<any>(
`SHOW DATABASES LIKE '${dbName}' `
);
const resp = await this.ExecQuery(`SHOW DATABASES LIKE '${dbName}' `);
return resp.length > 0;
}
@ -453,7 +500,7 @@ export default class MysqlDriver extends AbstractDriver {
const stream = query.stream({});
const promise = new Promise<boolean>((resolve, reject) => {
stream.on("data", chunk => {
ret.push((chunk as any) as T);
ret.push((chunk as unknown) as T);
});
stream.on("error", err => reject(err));
stream.on("end", () => resolve(true));
@ -463,11 +510,12 @@ export default class MysqlDriver extends AbstractDriver {
}
private static ReturnDefaultValueFunction(
defVal: string | null
): string | null {
defVal: string | undefined,
dataType: string
): string | undefined {
let defaultValue = defVal;
if (!defaultValue || defaultValue === "NULL") {
return null;
return undefined;
}
if (defaultValue.toLowerCase() === "current_timestamp()") {
defaultValue = "CURRENT_TIMESTAMP";
@ -478,6 +526,9 @@ export default class MysqlDriver extends AbstractDriver {
) {
return `() => "${defaultValue}"`;
}
if (dataType === "set") {
return `() => ['${defaultValue.split(",").join("','")}']`;
}
return `() => "'${defaultValue}'"`;
}
}

View File

@ -2,12 +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 "../models/EntityInfo";
import ColumnInfo from "../models/ColumnInfo";
import IndexInfo from "../models/IndexInfo";
import IndexColumnInfo from "../models/IndexColumnInfo";
import RelationTempInfo from "../models/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({
@ -36,20 +36,28 @@ export default class OracleDriver extends AbstractDriver {
}
}
public GetAllTablesQuery = async () => {
public GetAllTablesQuery = async (
schema: string,
dbNames: string,
tableNames: string[]
) => {
const tableCondition =
tableNames.length > 0
? ` AND NOT TABLE_NAME IN ('${tableNames.join("','")}')`
: "";
const response: {
TABLE_SCHEMA: string;
TABLE_NAME: string;
DB_NAME: string;
}[] = (await this.Connection.execute(
` SELECT NULL AS TABLE_SCHEMA, TABLE_NAME, NULL AS DB_NAME FROM all_tables WHERE owner = (select user from dual)`
)).rows!;
}[] = (
await this.Connection.execute(
`SELECT NULL AS TABLE_SCHEMA, TABLE_NAME, NULL AS DB_NAME FROM all_tables WHERE owner = (select user from dual) ${tableCondition}`
)
).rows!;
return response;
};
public async GetCoulmnsFromEntity(
entities: EntityInfo[]
): Promise<EntityInfo[]> {
public async GetCoulmnsFromEntity(entities: Entity[]): Promise<Entity[]> {
const response: {
TABLE_NAME: string;
COLUMN_NAME: string;
@ -59,123 +67,127 @@ export default class OracleDriver extends AbstractDriver {
DATA_LENGTH: number;
DATA_PRECISION: number;
DATA_SCALE: number;
IDENTITY_COLUMN: string;
IDENTITY_COLUMN: string; // doesn't exist in old oracle versions (#195)
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
}[] = (
await this.Connection
.execute(`SELECT utc.*, (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!;
FROM USER_TAB_COLUMNS utc`)
).rows!;
entities.forEach(ent => {
response
.filter(filterVal => filterVal.TABLE_NAME === ent.tsEntityName)
.filter(filterVal => filterVal.TABLE_NAME === ent.tscName)
.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 =
const tscName = resp.COLUMN_NAME;
const options: Column["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('"')
? null
? undefined
: 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;
const columnType = DATA_TYPE.toLowerCase();
let tscType = "";
switch (DATA_TYPE.toLowerCase()) {
case "char":
colInfo.tsType = "string";
tscType = "string";
break;
case "nchar":
colInfo.tsType = "string";
tscType = "string";
break;
case "nvarchar2":
colInfo.tsType = "string";
tscType = "string";
break;
case "varchar2":
colInfo.tsType = "string";
tscType = "string";
break;
case "long":
colInfo.tsType = "string";
tscType = "string";
break;
case "raw":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "long raw":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "number":
colInfo.tsType = "number";
tscType = "number";
break;
case "numeric":
colInfo.tsType = "number";
tscType = "number";
break;
case "float":
colInfo.tsType = "number";
tscType = "number";
break;
case "dec":
colInfo.tsType = "number";
tscType = "number";
break;
case "decimal":
colInfo.tsType = "number";
tscType = "number";
break;
case "integer":
colInfo.tsType = "number";
tscType = "number";
break;
case "int":
colInfo.tsType = "number";
tscType = "number";
break;
case "smallint":
colInfo.tsType = "number";
tscType = "number";
break;
case "real":
colInfo.tsType = "number";
tscType = "number";
break;
case "double precision":
colInfo.tsType = "number";
tscType = "number";
break;
case "date":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "timestamp":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "timestamp with time zone":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "timestamp with local time zone":
colInfo.tsType = "Date";
tscType = "Date";
break;
case "interval year to month":
colInfo.tsType = "string";
tscType = "string";
break;
case "interval day to second":
colInfo.tsType = "string";
tscType = "string";
break;
case "bfile":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "blob":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "clob":
colInfo.tsType = "string";
tscType = "string";
break;
case "nclob":
colInfo.tsType = "string";
tscType = "string";
break;
case "rowid":
colInfo.tsType = "number";
tscType = "number";
break;
case "urowid":
colInfo.tsType = "number";
tscType = "number";
break;
default:
tscType = "NonNullable<unknown>";
TomgUtils.LogError(
`Unknown column type:${DATA_TYPE}`
);
@ -183,75 +195,85 @@ export default class OracleDriver extends AbstractDriver {
}
if (
this.ColumnTypesWithPrecision.some(
v => v === colInfo.options.type
v => v === columnType
)
) {
colInfo.options.precision = resp.DATA_PRECISION;
colInfo.options.scale = resp.DATA_SCALE;
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 === colInfo.options.type
)
this.ColumnTypesWithLength.some(v => v === columnType)
) {
colInfo.options.length =
options.length =
resp.DATA_LENGTH > 0 ? resp.DATA_LENGTH : undefined;
}
if (colInfo.options.type) {
ent.Columns.push(colInfo);
}
ent.columns.push({
generated,
type: columnType,
default: defaultValue,
options,
tscName,
tscType
});
});
});
return entities;
}
public async GetIndexesFromEntity(
entities: EntityInfo[]
): Promise<EntityInfo[]> {
public async GetIndexesFromEntity(entities: Entity[]): Promise<Entity[]> {
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
}[] = (
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!;
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);
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;
}
public async GetRelations(entities: EntityInfo[]): Promise<EntityInfo[]> {
public async GetRelations(
entities: Entity[],
schema: string,
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
const response: {
OWNER_TABLE_NAME: string;
OWNER_POSITION: string;
@ -260,8 +282,9 @@ export default class OracleDriver extends AbstractDriver {
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,
}[] = (
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
@ -269,32 +292,48 @@ export default class OracleDriver extends AbstractDriver {
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!;
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
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
);
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);
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;
}
rels.ownerColumnsNames.push(resp.OWNER_COLUMN_NAME);
rels.referencedColumnsNames.push(resp.CHILD_COLUMN_NAME);
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
entities,
generationOptions
);
return retVal;
}
@ -305,29 +344,28 @@ export default class OracleDriver extends AbstractDriver {
}
}
public async ConnectToServer(connectionOptons: IConnectionOptions) {
public async ConnectToServer(connectionOptions: IConnectionOptions) {
let config: any;
if (connectionOptons.user === String(process.env.ORACLE_UsernameSys)) {
if (connectionOptions.user === String(process.env.ORACLE_UsernameSys)) {
config /* Oracle.IConnectionAttributes */ = {
connectString: `${connectionOptons.host}:${connectionOptons.port}/${connectionOptons.databaseName}`,
externalAuth: connectionOptons.ssl,
password: connectionOptons.password,
connectString: `${connectionOptions.host}:${connectionOptions.port}/${connectionOptions.databaseName}`,
externalAuth: connectionOptions.ssl,
password: connectionOptions.password,
privilege: this.Oracle.SYSDBA,
user: connectionOptons.user
user: connectionOptions.user
};
} else {
config /* Oracle.IConnectionAttributes */ = {
connectString: `${connectionOptons.host}:${connectionOptons.port}/${connectionOptons.databaseName}`,
externalAuth: connectionOptons.ssl,
password: connectionOptons.password,
user: connectionOptons.user
connectString: `${connectionOptions.host}:${connectionOptions.port}/${connectionOptions.databaseName}`,
externalAuth: connectionOptions.ssl,
password: connectionOptions.password,
user: connectionOptions.user
};
}
const that = this;
const promise = new Promise<boolean>((resolve, reject) => {
this.Oracle.getConnection(config, (err, connection) => {
if (!err) {
that.Connection = connection;
this.Connection = connection;
resolve(true);
} else {
TomgUtils.LogError(
@ -370,10 +408,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);

View File

@ -4,12 +4,12 @@ import * as TypeormDriver from "typeorm/driver/postgres/PostgresDriver";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import * as TomgUtils from "../Utils";
import AbstractDriver from "./AbstractDriver";
import EntityInfo from "../models/EntityInfo";
import ColumnInfo from "../models/ColumnInfo";
import IndexInfo from "../models/IndexInfo";
import IndexColumnInfo from "../models/IndexColumnInfo";
import RelationTempInfo from "../models/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 PostgresDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.PostgresDriver({
@ -24,22 +24,31 @@ export default class PostgresDriver extends AbstractDriver {
private Connection: PG.Client;
public GetAllTablesQuery = async (schema: string) => {
public GetAllTablesQuery = async (
schema: string,
dbNames: string,
tableNames: string[]
) => {
const tableCondition =
tableNames.length > 0
? ` AND NOT table_name IN ('${tableNames.join("','")}')`
: "";
const response: {
TABLE_SCHEMA: string;
TABLE_NAME: string;
DB_NAME: string;
}[] = (await this.Connection.query(
`SELECT table_schema as "TABLE_SCHEMA",table_name as "TABLE_NAME", table_catalog as "DB_NAME" FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE='BASE TABLE' AND table_schema in (${schema}) AND table_name<>'spatial_ref_sys'`
)).rows;
}[] = (
await this.Connection.query(
`SELECT table_schema as "TABLE_SCHEMA",table_name as "TABLE_NAME", table_catalog as "DB_NAME" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND table_schema in (${schema}) ${tableCondition}`
)
).rows;
return response;
};
public async GetCoulmnsFromEntity(
entities: EntityInfo[],
entities: Entity[],
schema: string
): Promise<EntityInfo[]> {
): Promise<Entity[]> {
const response: {
table_name: string;
column_name: string;
@ -53,41 +62,46 @@ export default class PostgresDriver extends AbstractDriver {
isidentity: string;
isunique: string;
enumvalues: string | null;
}[] = (await this.Connection
.query(`SELECT table_name,column_name,udt_name,column_default,is_nullable,
data_type,character_maximum_length,numeric_precision,numeric_scale,
case when column_default LIKE 'nextval%' then 'YES' else 'NO' end isidentity,
(SELECT count(*)
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
inner join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cu
on cu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
where
tc.CONSTRAINT_TYPE = 'UNIQUE'
and tc.TABLE_NAME = c.TABLE_NAME
and cu.COLUMN_NAME = c.COLUMN_NAME
and tc.TABLE_SCHEMA=c.TABLE_SCHEMA) IsUnique,
(SELECT
string_agg(enumlabel, ',')
FROM "pg_enum" "e"
INNER JOIN "pg_type" "t" ON "t"."oid" = "e"."enumtypid"
INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace"
WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
) enumValues
FROM INFORMATION_SCHEMA.COLUMNS c
where table_schema in (${schema})
order by ordinal_position`)).rows;
}[] = (
await this.Connection
.query(`SELECT table_name,column_name,udt_name,column_default,is_nullable,
data_type,character_maximum_length,numeric_precision,numeric_scale,
case when column_default LIKE 'nextval%' then 'YES' else 'NO' end isidentity,
(SELECT count(*)
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
inner join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cu
on cu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
where
tc.CONSTRAINT_TYPE = 'UNIQUE'
and tc.TABLE_NAME = c.TABLE_NAME
and cu.COLUMN_NAME = c.COLUMN_NAME
and tc.TABLE_SCHEMA=c.TABLE_SCHEMA) IsUnique,
(SELECT
string_agg(enumlabel, ',')
FROM "pg_enum" "e"
INNER JOIN "pg_type" "t" ON "t"."oid" = "e"."enumtypid"
INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace"
WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
) enumValues
FROM INFORMATION_SCHEMA.COLUMNS c
where table_schema in (${schema})
order by ordinal_position`)
).rows;
entities.forEach(ent => {
response
.filter(filterVal => filterVal.table_name === ent.tsEntityName)
.filter(filterVal => filterVal.table_name === ent.tscName)
.forEach(resp => {
const colInfo: ColumnInfo = new ColumnInfo();
colInfo.tsName = resp.column_name;
colInfo.options.name = resp.column_name;
colInfo.options.nullable = resp.is_nullable === "YES";
colInfo.options.generated = resp.isidentity === "YES";
colInfo.options.unique = resp.isunique === "1";
colInfo.options.default = colInfo.options.generated
? null
const tscName = resp.column_name;
const options: Column["options"] = {
name: resp.column_name
};
if (resp.is_nullable === "YES") options.nullable = true;
if (resp.isunique === "1") options.unique = true;
const generated =
resp.isidentity === "YES" ? true : undefined;
const defaultValue = generated
? undefined
: PostgresDriver.ReturnDefaultValueFunction(
resp.column_default
);
@ -97,63 +111,69 @@ WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
resp.udt_name,
resp.enumvalues
);
if (!columnTypes.sqlType || !columnTypes.tsType) {
if (columnTypes.tsType === "NonNullable<unknown>") {
if (
resp.data_type === "USER-DEFINED" ||
resp.data_type === "ARRAY"
) {
TomgUtils.LogError(
`Unknown ${resp.data_type} column type: ${resp.udt_name} table name: ${resp.table_name} column name: ${resp.column_name}`
`Unknown ${resp.data_type} column type: ${resp.udt_name} table name: ${resp.table_name} column name: ${resp.column_name}`
);
} else {
TomgUtils.LogError(
`Unknown column type: ${resp.data_type} table name: ${resp.table_name} column name: ${resp.column_name}`
`Unknown column type: ${resp.data_type} table name: ${resp.table_name} column name: ${resp.column_name}`
);
}
return;
}
colInfo.options.type = columnTypes.sqlType as any;
colInfo.tsType = columnTypes.tsType;
colInfo.options.array = columnTypes.isArray;
colInfo.options.enum = columnTypes.enumValues;
if (colInfo.options.array) {
colInfo.tsType = colInfo.tsType
const columnType = columnTypes.sqlType;
let tscType = columnTypes.tsType;
if (columnTypes.isArray) options.array = true;
if (columnTypes.enumValues.length > 0)
options.enum = columnTypes.enumValues;
if (options.array) {
tscType = tscType
.split("|")
.map(x => `${x.replace("|", "").trim()}[]`)
.join(" | ") as any;
.join(" | ");
}
if (
this.ColumnTypesWithPrecision.some(
v => v === colInfo.options.type
v => v === columnType
)
) {
colInfo.options.precision = resp.numeric_precision;
colInfo.options.scale = resp.numeric_scale;
if (resp.numeric_precision !== null) {
options.precision = resp.numeric_precision;
}
if (resp.numeric_scale !== null) {
options.scale = resp.numeric_scale;
}
}
if (
this.ColumnTypesWithLength.some(
v => v === colInfo.options.type
)
this.ColumnTypesWithLength.some(v => v === columnType)
) {
colInfo.options.length =
options.length =
resp.character_maximum_length > 0
? resp.character_maximum_length
: undefined;
}
if (
this.ColumnTypesWithWidth.some(
v => v === colInfo.options.type
)
) {
colInfo.options.width =
if (this.ColumnTypesWithWidth.some(v => v === columnType)) {
options.width =
resp.character_maximum_length > 0
? resp.character_maximum_length
: undefined;
}
if (colInfo.options.type && colInfo.tsType) {
ent.Columns.push(colInfo);
}
ent.columns.push({
generated,
type: columnType,
default: defaultValue,
options,
tscName,
tscType
});
});
});
return entities;
@ -165,17 +185,16 @@ WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
enumValues: string | null
) {
let ret: {
tsType?: ColumnInfo["tsType"];
sqlType: string | null;
tsType: Column["tscType"];
sqlType: string;
isArray: boolean;
enumValues: string[];
} = {
tsType: undefined,
sqlType: null,
tsType: "",
sqlType: dataType,
isArray: false,
enumValues: []
};
ret.sqlType = dataType;
switch (dataType) {
case "int2":
ret.tsType = "number";
@ -381,35 +400,30 @@ WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
.split(",")
.join('" | "')}"` as never) as string;
ret.sqlType = "enum";
ret.enumValues = (`"${enumValues
.split(",")
.join('","')}"` as never) as string[];
} else {
ret.tsType = undefined;
ret.sqlType = null;
ret.enumValues = enumValues.split(",");
}
break;
}
break;
default:
ret.tsType = undefined;
ret.sqlType = null;
ret.tsType = "NonNullable<unknown>";
break;
}
return ret;
}
public async GetIndexesFromEntity(
entities: EntityInfo[],
entities: Entity[],
schema: string
): Promise<EntityInfo[]> {
): Promise<Entity[]> {
const response: {
tablename: string;
indexname: string;
columnname: string;
is_unique: number;
is_primary_key: number;
}[] = (await this.Connection.query(`SELECT
}[] = (
await this.Connection.query(`SELECT
c.relname AS tablename,
i.relname as indexname,
f.attname AS columnname,
@ -432,43 +446,40 @@ WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
AND n.nspname in (${schema})
AND f.attnum > 0
AND i.oid<>0
ORDER BY c.relname,f.attname;`)).rows;
ORDER BY c.relname,f.attname;`)
).rows;
entities.forEach(ent => {
response
.filter(filterVal => filterVal.tablename === ent.tsEntityName)
.forEach(resp => {
let indexInfo: IndexInfo = {} as IndexInfo;
const indexColumnInfo: IndexColumnInfo = {} as IndexColumnInfo;
if (
ent.Indexes.filter(
filterVal => filterVal.name === resp.indexname
).length > 0
) {
indexInfo = ent.Indexes.find(
filterVal => filterVal.name === resp.indexname
)!;
} else {
indexInfo.columns = [] as IndexColumnInfo[];
indexInfo.name = resp.indexname;
indexInfo.isUnique = resp.is_unique === 1;
indexInfo.isPrimaryKey = resp.is_primary_key === 1;
ent.Indexes.push(indexInfo);
}
indexColumnInfo.name = resp.columnname;
if (resp.is_primary_key === 0) {
indexInfo.isPrimaryKey = false;
}
indexInfo.columns.push(indexColumnInfo);
const entityIndices = response.filter(
filterVal => filterVal.tablename === ent.tscName
);
const indexNames = new Set(entityIndices.map(v => v.indexname));
indexNames.forEach(indexName => {
const records = entityIndices.filter(
v => v.indexname === indexName
);
const indexInfo: Index = {
columns: [],
options: {},
name: records[0].indexname
};
if (records[0].is_primary_key === 1) indexInfo.primary = true;
if (records[0].is_unique === 1) indexInfo.options.unique = true;
records.forEach(record => {
indexInfo.columns.push(record.columnname);
});
ent.indices.push(indexInfo);
});
});
return entities;
}
public async GetRelations(
entities: EntityInfo[],
schema: string
): Promise<EntityInfo[]> {
entities: Entity[],
schema: string,
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
const response: {
tablewithforeignkey: string;
fk_partno: number;
@ -479,7 +490,8 @@ WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
onupdate: "RESTRICT" | "CASCADE" | "SET NULL" | "NO ACTION";
object_id: string;
// Distinct because of note in https://www.postgresql.org/docs/9.1/information-schema.html
}[] = (await this.Connection.query(`SELECT DISTINCT
}[] = (
await this.Connection.query(`SELECT DISTINCT
con.relname AS tablewithforeignkey,
att.attnum as fk_partno,
att2.attname AS foreignkeycolumn,
@ -518,31 +530,50 @@ WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
AND att2.attrelid = con.conrelid
AND att2.attnum = con.parent
AND rc.constraint_name= con.conname AND constraint_catalog=current_database() AND rc.constraint_schema=nspname
`)).rows;
const relationsTemp: RelationTempInfo[] = [] as RelationTempInfo[];
response.forEach(resp => {
let rels = relationsTemp.find(
val => val.objectId === resp.object_id
`)
).rows;
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.object_id));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.object_id === relationId);
const ownerTable = entities.find(
v => v.sqlName === rows[0].tablewithforeignkey
);
if (rels === undefined) {
rels = {} as RelationTempInfo;
rels.ownerColumnsNames = [];
rels.referencedColumnsNames = [];
rels.actionOnDelete =
resp.ondelete === "NO ACTION" ? null : resp.ondelete;
rels.actionOnUpdate =
resp.onupdate === "NO ACTION" ? null : resp.onupdate;
rels.objectId = resp.object_id;
rels.ownerTable = resp.tablewithforeignkey;
rels.referencedTable = resp.tablereferenced;
relationsTemp.push(rels);
const relatedTable = entities.find(
v => v.sqlName === rows[0].tablereferenced
);
if (!ownerTable || !relatedTable) {
TomgUtils.LogError(
`Relation between tables ${rows[0].tablewithforeignkey} and ${rows[0].tablereferenced} wasn't found in entity model.`,
true
);
return;
}
rels.ownerColumnsNames.push(resp.foreignkeycolumn);
rels.referencedColumnsNames.push(resp.foreignkeycolumnreferenced);
const internal: RelationInternal = {
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
};
if (rows[0].ondelete !== "NO ACTION") {
internal.onDelete = rows[0].ondelete;
}
if (rows[0].onupdate !== "NO ACTION") {
internal.onUpdate = rows[0].onupdate;
}
rows.forEach(row => {
internal.ownerColumns.push(row.foreignkeycolumn);
internal.relatedColumns.push(row.foreignkeycolumnreferenced);
});
relationsTemp.push(internal);
});
const retVal = PostgresDriver.GetRelationsFromRelationTempInfo(
relationsTemp,
entities
entities,
generationOptions
);
return retVal;
}
@ -575,7 +606,7 @@ WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
port: connectionOptons.port,
ssl: connectionOptons.ssl,
// eslint-disable-next-line @typescript-eslint/camelcase
statement_timeout: connectionOptons.timeout,
statement_timeout: 60 * 60 * 1000,
user: connectionOptons.user
});
@ -618,10 +649,10 @@ WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name
private static ReturnDefaultValueFunction(
defVal: string | null
): string | null {
): string | undefined {
let defaultValue = defVal;
if (!defaultValue) {
return null;
return undefined;
}
defaultValue = defaultValue.replace(/'::[\w ]*/, "'");
if (defaultValue.startsWith(`'`)) {

View File

@ -4,12 +4,12 @@ import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import * as sqliteLib from "sqlite3";
import * as TomgUtils from "../Utils";
import AbstractDriver from "./AbstractDriver";
import EntityInfo from "../models/EntityInfo";
import ColumnInfo from "../models/ColumnInfo";
import IndexInfo from "../models/IndexInfo";
import IndexColumnInfo from "../models/IndexColumnInfo";
import RelationTempInfo from "../models/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 SqliteDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.SqliteDriver({
@ -24,33 +24,43 @@ export default class SqliteDriver extends AbstractDriver {
public sqlite = sqliteLib.verbose();
public db: any;
public db: sqliteLib.Database;
public tablesWithGeneratedPrimaryKey: string[] = new Array<string>();
public GetAllTablesQuery: any;
public async GetAllTables(): Promise<EntityInfo[]> {
const ret: EntityInfo[] = [] as EntityInfo[];
public async GetAllTables(
schema: string,
dbNames: string,
tableNames: string[]
): Promise<Entity[]> {
const ret: Entity[] = [] as Entity[];
const tableCondition =
tableNames.length > 0
? ` AND NOT tbl_name IN ('${tableNames.join("','")}')`
: "";
const rows = await this.ExecQuery<{ tbl_name: string; sql: string }>(
`SELECT tbl_name, sql FROM "sqlite_master" WHERE "type" = 'table' AND name NOT LIKE 'sqlite_%'`
`SELECT tbl_name, sql FROM "sqlite_master" WHERE "type" = 'table' AND name NOT LIKE 'sqlite_%' ${tableCondition}`
);
rows.forEach(val => {
const ent: EntityInfo = new EntityInfo();
ent.tsEntityName = val.tbl_name;
ent.Columns = [] as ColumnInfo[];
ent.Indexes = [] as IndexInfo[];
if (val.sql.includes("AUTOINCREMENT")) {
this.tablesWithGeneratedPrimaryKey.push(ent.tsEntityName);
this.tablesWithGeneratedPrimaryKey.push(val.tbl_name);
}
ret.push(ent);
ret.push({
columns: [],
indices: [],
relations: [],
relationIds: [],
sqlName: val.tbl_name,
tscName: val.tbl_name,
fileImports: []
});
});
return ret;
}
public async GetCoulmnsFromEntity(
entities: EntityInfo[]
): Promise<EntityInfo[]> {
public async GetCoulmnsFromEntity(entities: Entity[]): Promise<Entity[]> {
await Promise.all(
entities.map(async ent => {
const response = await this.ExecQuery<{
@ -60,155 +70,172 @@ export default class SqliteDriver extends AbstractDriver {
notnull: number;
dflt_value: string;
pk: number;
}>(`PRAGMA table_info('${ent.tsEntityName}');`);
}>(`PRAGMA table_info('${ent.tscName}');`);
response.forEach(resp => {
const colInfo: ColumnInfo = new ColumnInfo();
colInfo.tsName = resp.name;
colInfo.options.name = resp.name;
colInfo.options.nullable = resp.notnull === 0;
colInfo.options.primary = resp.pk > 0;
colInfo.options.default = SqliteDriver.ReturnDefaultValueFunction(
const tscName = resp.name;
let tscType = "";
const options: Column["options"] = { name: resp.name };
if (resp.notnull === 0) options.nullable = true;
const isPrimary = resp.pk > 0 ? true : undefined;
const defaultValue = SqliteDriver.ReturnDefaultValueFunction(
resp.dflt_value
);
colInfo.options.type = resp.type
const columnType = resp.type
.replace(/\([0-9 ,]+\)/g, "")
.toLowerCase()
.trim() as any;
colInfo.options.generated =
colInfo.options.primary &&
this.tablesWithGeneratedPrimaryKey.includes(
ent.tsEntityName
);
switch (colInfo.options.type) {
.trim();
const generated =
isPrimary &&
this.tablesWithGeneratedPrimaryKey.includes(ent.tscName)
? true
: undefined;
switch (columnType) {
case "int":
colInfo.tsType = "number";
tscType = "number";
break;
case "integer":
colInfo.tsType = "number";
tscType = "number";
break;
case "int2":
colInfo.tsType = "number";
tscType = "number";
break;
case "int8":
colInfo.tsType = "number";
tscType = "number";
break;
case "tinyint":
colInfo.tsType = "number";
tscType = "number";
break;
case "smallint":
colInfo.tsType = "number";
tscType = "number";
break;
case "mediumint":
colInfo.tsType = "number";
tscType = "number";
break;
case "bigint":
colInfo.tsType = "string";
tscType = "string";
break;
case "unsigned big int":
colInfo.tsType = "string";
tscType = "string";
break;
case "character":
colInfo.tsType = "string";
tscType = "string";
break;
case "varchar":
colInfo.tsType = "string";
tscType = "string";
break;
case "varying character":
colInfo.tsType = "string";
tscType = "string";
break;
case "nchar":
colInfo.tsType = "string";
tscType = "string";
break;
case "native character":
colInfo.tsType = "string";
tscType = "string";
break;
case "nvarchar":
colInfo.tsType = "string";
tscType = "string";
break;
case "text":
colInfo.tsType = "string";
tscType = "string";
break;
case "blob":
colInfo.tsType = "Buffer";
tscType = "Buffer";
break;
case "clob":
colInfo.tsType = "string";
tscType = "string";
break;
case "real":
colInfo.tsType = "number";
tscType = "number";
break;
case "double":
colInfo.tsType = "number";
tscType = "number";
break;
case "double precision":
colInfo.tsType = "number";
tscType = "number";
break;
case "float":
colInfo.tsType = "number";
tscType = "number";
break;
case "numeric":
colInfo.tsType = "number";
tscType = "number";
break;
case "decimal":
colInfo.tsType = "number";
tscType = "number";
break;
case "boolean":
colInfo.tsType = "boolean";
tscType = "boolean";
break;
case "date":
colInfo.tsType = "string";
tscType = "string";
break;
case "datetime":
colInfo.tsType = "Date";
tscType = "Date";
break;
default:
tscType = "NonNullable<unknown>";
TomgUtils.LogError(
`Unknown column type: ${colInfo.options.type} table name: ${ent.tsEntityName} column name: ${resp.name}`
`Unknown column type: ${columnType} table name: ${ent.tscName} column name: ${resp.name}`
);
break;
}
const options = resp.type.match(/\([0-9 ,]+\)/g);
const sqlOptions = resp.type.match(/\([0-9 ,]+\)/g);
if (
this.ColumnTypesWithPrecision.some(
v => v === colInfo.options.type
v => v === columnType
) &&
options
sqlOptions
) {
colInfo.options.precision = options[0]
.substring(1, options[0].length - 1)
.split(",")[0] as any;
colInfo.options.scale = options[0]
.substring(1, options[0].length - 1)
.split(",")[1] as any;
options.precision = Number.parseInt(
sqlOptions[0]
.substring(1, sqlOptions[0].length - 1)
.split(",")[0],
10
);
options.scale = Number.parseInt(
sqlOptions[0]
.substring(1, sqlOptions[0].length - 1)
.split(",")[1],
10
);
}
if (
this.ColumnTypesWithLength.some(
v => v === colInfo.options.type
v => v === columnType
) &&
options
sqlOptions
) {
colInfo.options.length = options[0].substring(
1,
options[0].length - 1
) as any;
options.length = Number.parseInt(
sqlOptions[0].substring(
1,
sqlOptions[0].length - 1
),
10
);
}
if (
this.ColumnTypesWithWidth.some(
v =>
v === colInfo.options.type &&
colInfo.tsType !== "boolean"
v => v === columnType && tscType !== "boolean"
) &&
options
sqlOptions
) {
colInfo.options.width = options[0].substring(
1,
options[0].length - 1
) as any;
options.width = Number.parseInt(
sqlOptions[0].substring(
1,
sqlOptions[0].length - 1
),
10
);
}
if (colInfo.options.type) {
ent.Columns.push(colInfo);
}
ent.columns.push({
generated,
primary: isPrimary,
type: columnType,
default: defaultValue,
options,
tscName,
tscType
});
});
})
);
@ -216,9 +243,7 @@ export default class SqliteDriver extends AbstractDriver {
return entities;
}
public async GetIndexesFromEntity(
entities: EntityInfo[]
): Promise<EntityInfo[]> {
public async GetIndexesFromEntity(entities: Entity[]): Promise<Entity[]> {
await Promise.all(
entities.map(async ent => {
const response = await this.ExecQuery<{
@ -227,7 +252,7 @@ export default class SqliteDriver extends AbstractDriver {
unique: number;
origin: string;
partial: number;
}>(`PRAGMA index_list('${ent.tsEntityName}');`);
}>(`PRAGMA index_list('${ent.tscName}');`);
await Promise.all(
response.map(async resp => {
const indexColumnsResponse = await this.ExecQuery<{
@ -235,37 +260,29 @@ export default class SqliteDriver extends AbstractDriver {
cid: number;
name: string;
}>(`PRAGMA index_info('${resp.name}');`);
indexColumnsResponse.forEach(element => {
let indexInfo: IndexInfo = {} as IndexInfo;
const indexColumnInfo: IndexColumnInfo = {} as IndexColumnInfo;
if (
ent.Indexes.filter(filterVal => {
return filterVal.name === resp.name;
}).length > 0
) {
indexInfo = ent.Indexes.find(
filterVal => filterVal.name === resp.name
)!;
} else {
indexInfo.columns = [] as IndexColumnInfo[];
indexInfo.name = resp.name;
indexInfo.isUnique = resp.unique === 1;
ent.Indexes.push(indexInfo);
}
indexColumnInfo.name = element.name;
if (
indexColumnsResponse.length === 1 &&
indexInfo.isUnique
) {
ent.Columns.filter(
v => v.tsName === indexColumnInfo.name
).forEach(v => {
const indexInfo: Index = {
name: resp.name,
columns: [],
options: {}
};
if (resp.unique === 1) indexInfo.options.unique = true;
indexColumnsResponse.forEach(record => {
indexInfo.columns.push(record.name);
});
if (
indexColumnsResponse.length === 1 &&
indexInfo.options.unique
) {
ent.columns
.filter(v => v.tscName === indexInfo.columns[0])
.forEach(v => {
// eslint-disable-next-line no-param-reassign
v.options.unique = true;
});
}
indexInfo.columns.push(indexColumnInfo);
});
}
ent.indices.push(indexInfo);
})
);
})
@ -274,7 +291,12 @@ export default class SqliteDriver extends AbstractDriver {
return entities;
}
public async GetRelations(entities: EntityInfo[]): Promise<EntityInfo[]> {
public async GetRelations(
entities: Entity[],
schema: string,
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
let retVal = entities;
await Promise.all(
retVal.map(async entity => {
@ -295,25 +317,49 @@ export default class SqliteDriver extends AbstractDriver {
| "SET NULL"
| "NO ACTION";
match: string;
}>(`PRAGMA foreign_key_list('${entity.tsEntityName}');`);
const relationsTemp: RelationTempInfo[] = [] as RelationTempInfo[];
response.forEach(resp => {
const rels = {} as RelationTempInfo;
rels.ownerColumnsNames = [];
rels.referencedColumnsNames = [];
rels.actionOnDelete =
resp.on_delete === "NO ACTION" ? null : resp.on_delete;
rels.actionOnUpdate =
resp.on_update === "NO ACTION" ? null : resp.on_update;
rels.ownerTable = entity.tsEntityName;
rels.referencedTable = resp.table;
relationsTemp.push(rels);
rels.ownerColumnsNames.push(resp.from);
rels.referencedColumnsNames.push(resp.to);
}>(`PRAGMA foreign_key_list('${entity.tscName}');`);
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.id));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.id === relationId);
const ownerTable = entities.find(
v => v.sqlName === entity.tscName
);
const relatedTable = entities.find(
v => v.sqlName === rows[0].table
);
if (!ownerTable || !relatedTable) {
TomgUtils.LogError(
`Relation between tables ${entity.tscName} and ${rows[0].table} wasn't found in entity model.`,
true
);
return;
}
const internal: RelationInternal = {
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
};
if (rows[0].on_delete !== "NO ACTION") {
internal.onDelete = rows[0].on_delete;
}
if (rows[0].on_update !== "NO ACTION") {
internal.onUpdate = rows[0].on_update;
}
rows.forEach(row => {
internal.ownerColumns.push(row.from);
internal.relatedColumns.push(row.to);
});
relationsTemp.push(internal);
});
retVal = SqliteDriver.GetRelationsFromRelationTempInfo(
relationsTemp,
retVal
retVal,
generationOptions
);
})
);
@ -362,7 +408,7 @@ export default class SqliteDriver extends AbstractDriver {
}
public async ExecQuery<T>(sql: string): Promise<T[]> {
let ret: any;
let ret: T[] = [];
const promise = new Promise<boolean>((resolve, reject) => {
this.db.serialize(() => {
this.db.all(sql, [], (err, row) => {
@ -386,9 +432,9 @@ export default class SqliteDriver extends AbstractDriver {
private static ReturnDefaultValueFunction(
defVal: string | null
): string | null {
): string | undefined {
if (!defVal) {
return null;
return undefined;
}
if (defVal.startsWith(`'`)) {
return `() => "${defVal}"`;

View File

@ -1,46 +0,0 @@
import {BaseEntity,Column,Entity,Index,JoinColumn,JoinTable,ManyToMany,ManyToOne,OneToMany,OneToOne,PrimaryColumn,PrimaryGeneratedColumn,RelationId} from "typeorm";
{{relationImports}}{{#each UniqueImports}}import {{curly true}}{{toEntityName this}}{{curly false}} from "./{{toFileName this}}";
{{/each}}
@Entity("{{sqlEntityName}}"{{#Schema}} ,{schema:"{{.}}"{{#if ../Database}}, database:"{{../Database}}"{{/if}} } {{/Schema}})
{{#Indexes}}{{^isPrimaryKey}}@Index("{{name}}",[{{#columns}}"{{toPropertyName name}}",{{/columns}}]{{#isUnique}},{unique:true}{{/isUnique}})
{{/isPrimaryKey}}{{/Indexes}}export class {{toEntityName tsEntityName}}{{#IsActiveRecord}} extends BaseEntity{{/IsActiveRecord}} {
{{#Columns}}
{{^relations}}{{#options}}{{#generated}} @PrimaryGeneratedColumn({
type:"{{type}}", {{/generated}}{{^generated}} @Column("{{type}}",{ {{#nullable}}
nullable:true,{{/nullable}}{{^nullable}}
nullable:false,{{/nullable}}{{#primary}}
primary:{{primary}},{{/primary}}{{#unique}}
unique: true,{{/unique}}{{/generated}}{{#length}}
length:{{.}},{{/length}}{{#width}}
width:{{.}},{{/width}}{{#unsigned}}
unsigned: true,{{/unsigned}}{{#default}}
default: {{.}},{{/default}}{{#precision}}
precision:{{.}},{{/precision}}{{#scale}}
scale:{{.}},{{/scale}}{{#enum}}
enum:[{{.}}],{{/enum}}{{#array}}
array:{{array}},{{/array}}
name:"{{name}}"
}){{/options}}
{{printPropertyVisibility}}{{toPropertyName tsName}}{{strictMode}}:{{tsType}}{{#options/nullable}} | null{{/options/nullable}};
{{/relations}}{{#relations}}
@{{relationType}}(()=>{{toEntityName relatedTable}}, ({{toPropertyName relatedTable}}: {{toEntityName relatedTable}})=>{{toPropertyName relatedTable}}.{{#if isOwner}}{{toPropertyName ownerColumn}},{ {{#../options/primary}}primary:true,{{/../options/primary}}{{^../options/nullable}} nullable:false,{{/../options/nullable}}{{#actionOnDelete}}onDelete: '{{.}}',{{/actionOnDelete}}{{#actionOnUpdate}}onUpdate: '{{.}}'{{/actionOnUpdate}} }{{else}}{{toPropertyName relatedColumn}}{{#if (or actionOnDelete actionOnUpdate ) }},{ {{#actionOnDelete}}onDelete: '{{.}}' ,{{/actionOnDelete}}{{#actionOnUpdate}}onUpdate: '{{.}}'{{/actionOnUpdate}} }{{/if}}{{/if}}){{#isOwner}}
{{#if isManyToMany}}@JoinTable({ name:'{{ ../options/name}}'{{#if joinColumn}},joinColumn:{name:'{{joinColumn}}'}{{/if}}{{#if inverseJoinColumn}},inverseJoinColumn:{name:'{{inverseJoinColumn}}'}{{/if}} }){{else}}@JoinColumn({ name:'{{ ../options/name}}'}){{/if}}{{/isOwner}}
{{#if (or isOneToMany isManyToMany)}}{{printPropertyVisibility}}{{toPropertyName ../tsName}}{{strictMode}}:{{toLazy (concat (toEntityName relatedTable) "[]")}};
{{else}}{{printPropertyVisibility}}{{toPropertyName ../tsName}}{{strictMode}}:{{toLazy (concat (toEntityName relatedTable) ' | null')}};
{{/if}}
{{#if relationIdField }}
@RelationId(({{toPropertyName ../../tsEntityName}}: {{toEntityName ../../tsEntityName}}) => {{toPropertyName ../../tsEntityName}}.{{toPropertyName ../tsName}})
{{printPropertyVisibility}}{{toPropertyName ../tsName}}Id{{strictMode}}: {{#if isOneToOne}}{{toLazy ../tsType}}{{else}}{{toLazy (concat ../tsType "[]")}}{{/if}};{{/if}}{{/relations}}
{{/Columns}}
{{#if GenerateConstructor}}
{{printPropertyVisibility}}constructor(init?: Partial<{{toEntityName tsEntityName}}>) {
{{#IsActiveRecord}}super();
{{/IsActiveRecord}}Object.assign(this, init);
}
{{/if}}
}

File diff suppressed because it is too large Load Diff

18
src/library.ts Normal file
View File

@ -0,0 +1,18 @@
import * as Engine from "./Engine";
import * as IConnectionOptions from "./IConnectionOptions";
import * as IGenerationOptions from "./IGenerationOptions";
import * as NamingStrategy from "./NamingStrategy";
import * as Utils from "./Utils";
export { Column } from "./models/Column";
export { Entity } from "./models/Entity";
export { Index } from "./models/Index";
export { Relation } from "./models/Relation";
export { RelationId } from "./models/RelationId";
export {
Engine,
IConnectionOptions,
IGenerationOptions,
NamingStrategy,
Utils
};

25
src/models/Column.ts Normal file
View File

@ -0,0 +1,25 @@
import { ColumnType } from "typeorm";
export type Column = {
tscType: string;
tscName: string;
type: ColumnType | string; // TODO: remove ?
isUsedInRelationAsOwner?: true; // TODO: move to separate object/calulate when us
isUsedInRelationAsReferenced?: true; // TODO: move to separate object/calulate when us
primary?: boolean;
generated?: true | "increment" | "uuid";
default?: string; // ?
options: {
name: string;
length?: number;
width?: number;
nullable?: boolean;
unique?: boolean; // ?
precision?: number;
scale?: number;
unsigned?: boolean;
enum?: string[];
array?: boolean; // ?
};
};

View File

@ -1,22 +0,0 @@
import { ColumnOptions } from "typeorm";
import RelationInfo from "./RelationInfo";
export default class ColumnInfo {
public options: ColumnOptions = {};
public tsName: string = "";
public tsType:
| "number"
| "string"
| "boolean"
| "Date"
| "Buffer"
| "object"
| "string | object"
| "string | string[]"
| "any"
| string;
public relations: RelationInfo[] = [];
}

21
src/models/Entity.ts Normal file
View File

@ -0,0 +1,21 @@
import { Column } from "./Column";
import { Relation } from "./Relation";
import { Index } from "./Index";
import { RelationId } from "./RelationId";
export type Entity = {
sqlName: string;
tscName: string;
database?: string;
schema?: string;
columns: Column[];
relationIds: RelationId[];
relations: Relation[];
indices: Index[];
// TODO: move to sub-object or use handlebars helpers(?)
fileImports: string[];
activeRecord?: true;
generateConstructor?: true;
};

View File

@ -1,38 +0,0 @@
import ColumnInfo from "./ColumnInfo";
import IndexInfo from "./IndexInfo";
export default class EntityInfo {
public tsEntityName: string;
public sqlEntityName: string;
public Columns: ColumnInfo[];
public Imports: string[];
public UniqueImports: string[];
public Indexes: IndexInfo[];
public Schema?: string;
public GenerateConstructor: boolean;
public IsActiveRecord: boolean;
public Database?: string;
public relationImports() {
const imports: string[] = [];
this.Columns.forEach(column => {
column.relations.forEach(relation => {
if (this.tsEntityName !== relation.relatedTable) {
imports.push(relation.relatedTable);
}
});
});
this.UniqueImports = imports.filter(
(elem, index, self) => index === self.indexOf(elem)
);
}
}

8
src/models/Index.ts Normal file
View File

@ -0,0 +1,8 @@
export type Index = {
name: string;
columns: string[];
options: {
unique?: boolean;
};
primary?: boolean;
};

View File

@ -1,3 +0,0 @@
export default interface IndexColumnInfo {
name: string;
}

View File

@ -1,8 +0,0 @@
import IndexColumnInfo from "./IndexColumnInfo";
export default interface IndexInfo {
name: string;
columns: IndexColumnInfo[];
isUnique: boolean;
isPrimaryKey: boolean;
}

12
src/models/Relation.ts Normal file
View File

@ -0,0 +1,12 @@
import { JoinColumnOptions, RelationOptions } from "typeorm";
import { JoinTableMultipleColumnsOptions } from "typeorm/decorator/options/JoinTableMultipleColumnsOptions";
export type Relation = {
relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany";
relatedTable: string;
relatedField: string;
fieldName: string;
relationOptions?: RelationOptions;
joinColumnOptions?: Required<JoinColumnOptions>[];
joinTableOptions?: JoinTableMultipleColumnsOptions;
};

5
src/models/RelationId.ts Normal file
View File

@ -0,0 +1,5 @@
export type RelationId = {
fieldName: string;
fieldType: string;
relationField: string;
};

View File

@ -1,50 +0,0 @@
export default class RelationInfo {
public isOwner: boolean;
public relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany";
public relatedTable: string;
public relatedColumn: string;
public ownerTable: string;
public ownerColumn: string;
public joinColumn?: string;
public inverseJoinColumn?: string;
public actionOnDelete:
| "RESTRICT"
| "CASCADE"
| "SET NULL"
| "DEFAULT"
| "NO ACTION"
| null;
public actionOnUpdate:
| "RESTRICT"
| "CASCADE"
| "SET NULL"
| "DEFAULT"
| null;
public relationIdField: boolean = false;
public get isOneToMany(): boolean {
return this.relationType === "OneToMany";
}
public get isManyToMany(): boolean {
return this.relationType === "ManyToMany";
}
public get isOneToOne(): boolean {
return this.relationType === "OneToOne";
}
public get isManyToOne(): boolean {
return this.relationType === "ManyToOne";
}
}

View File

@ -0,0 +1,12 @@
import { OnDeleteType } from "typeorm/metadata/types/OnDeleteType";
import { OnUpdateType } from "typeorm/metadata/types/OnUpdateType";
import { Entity } from "./Entity";
export type RelationInternal = {
ownerTable: Entity;
relatedTable: Entity;
ownerColumns: string[];
relatedColumns: string[];
onDelete?: OnDeleteType;
onUpdate?: OnUpdateType;
};

View File

@ -1,15 +0,0 @@
export default interface RelationTempInfo {
ownerTable: string;
ownerColumnsNames: string[];
referencedTable: string;
referencedColumnsNames: string[];
actionOnDelete:
| "RESTRICT"
| "CASCADE"
| "SET NULL"
| "DEFAULT"
| "NO ACTION"
| null;
actionOnUpdate: "RESTRICT" | "CASCADE" | "SET NULL" | "DEFAULT" | null;
objectId: number | string;
}

47
src/templates/entity.mst Normal file
View File

@ -0,0 +1,47 @@
{{#*inline "Index"}}
@Index("{{name}}",[{{#columns}}"{{toPropertyName .}}",{{/columns~}}],{ {{json options}} })
{{/inline}}
{{#*inline "Import"}}
import {{localImport (toEntityName .)}} from './{{toFileName .}}'
{{/inline}}
{{#*inline "Column"}}
{{#generated}}@PrimaryGeneratedColumn({ type:"{{type}}", {{/generated}}{{^generated}}@Column("{{type}}",{ {{#primary}}primary:{{primary}},{{/primary}}{{/generated}}{{json options}}{{#default}},default: {{.}},{{/default}} })
{{printPropertyVisibility}}{{toPropertyName tscName}}{{strictMode}}:{{tscType}}{{#if options.nullable}} | null{{/if}};
{{/inline}}
{{#*inline "JoinColumnOptions"}}
{ name: "{{name}}", referencedColumnName: "{{toPropertyName referencedColumnName}}" },
{{/inline}}
{{#*inline "Relation"}}
@{{relationType}}(()=>{{toEntityName relatedTable}},{{toPropertyName relatedTable}}=>{{toPropertyName relatedTable}}.{{toPropertyName relatedField}}{{#if relationOptions}},{ {{json relationOptions}} }{{/if}})
{{#if joinColumnOptions}}@JoinColumn([{{#joinColumnOptions}}{{> JoinColumnOptions}}{{/joinColumnOptions}}]){{/if}}
{{#joinTableOptions}}@JoinTable({ name:"{{name}}", joinColumns:[{{#joinColumns}}{{> JoinColumnOptions}}{{/joinColumns}}],inverseJoinColumns:[{{#inverseJoinColumns}}{{> JoinColumnOptions}}{{/inverseJoinColumns}}],{{#database}}database:"{{.}}",{{/database}}{{#schema}}schema:"{{.}}"{{/schema}} }){{/joinTableOptions}}
{{printPropertyVisibility}}{{toPropertyName fieldName}}{{strictMode}}:{{toRelation (toEntityName relatedTable) relationType}};
{{/inline}}
{{#*inline "RelationId"}}
@RelationId(({{toPropertyName entityName}}:{{toEntityName entityName}})=>{{toPropertyName entityName}}.{{toPropertyName relationField}})
{{printPropertyVisibility}}{{toPropertyName fieldName}}{{strictMode}}:{{fieldType}};
{{/inline}}
{{#*inline "Constructor"}}
{{printPropertyVisibility}}constructor(init?: Partial<{{toEntityName entityName}}>) {
{{#activeRecord}}super();
{{/activeRecord}}Object.assign(this, init);
}
{{/inline}}
{{#*inline "Entity"}}
{{#indices}}{{> Index}}{{/indices~}}
@Entity("{{sqlName}}"{{#schema}} ,{schema:"{{.}}"{{#if ../database}}, database:"{{../database}}"{{/if}} } {{/schema}})
export {{defaultExport}} class {{toEntityName tscName}}{{#activeRecord}} extends BaseEntity{{/activeRecord}} {
{{#columns}}{{> Column}}{{/columns~}}
{{#relations}}{{> Relation}}{{/relations~}}
{{#relationIds}}{{> RelationId entityName=../tscName}}{{/relationIds~}}
{{#if generateConstructor}}{{>Constructor entityName=tscName}}{{/if~}}
}
{{/inline}}
import {BaseEntity,Column,Entity,Index,JoinColumn,JoinTable,ManyToMany,ManyToOne,OneToMany,OneToOne,PrimaryColumn,PrimaryGeneratedColumn,RelationId} from "typeorm";
{{#fileImports}}{{> Import}}{{/fileImports}}
{{> Entity}}

5
src/templates/index.mst Normal file
View File

@ -0,0 +1,5 @@
{{#entities~}}
import {{localImport (toEntityName tscName)}} from './{{toFileName tscName}}'
{{/entities}}
export { {{#entities}}{{toEntityName tscName}},{{/entities~}} }

View File

@ -0,0 +1,16 @@
[
{
"name": "default",
"type": "{{databaseType}}",
"host": "{{host}}",
"port": {{port}},
"username": "{{user}}",
"password": "{{password}}",
"database": "{{databaseName}}",{{#schemaName}}
"schema": "{{.}}",{{/schemaName}}
"synchronize": false,
"entities": [
"entities/*.js"
]
}
]

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
}
}

30
test/configs/.eslintrc.js Normal file
View File

@ -0,0 +1,30 @@
module.exports = {
env: {
node: true,
},
extends: [
"airbnb-base",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/@typescript-eslint"
],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "test/configs/tsconfig.json"
},
plugins: ["@typescript-eslint"],
rules: { // TODO: remove some rule overrides after disabling eslint on model customization tests(?)
"import/extensions": ["off"],
"import/prefer-default-export": ["off"],
"@typescript-eslint/no-explicit-any": ["off"],
"@typescript-eslint/camelcase": ["off"],
"@typescript-eslint/class-name-casing": ["off"]
},
settings: {
"import/resolver": {
node: {
extensions: [".ts"]
}
}
}
};

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"newLine": "LF",
"typeRoots": [
"../../node_modules/@types"
],
"resolveJsonModule": true,
},
"include": [
"../../output"
]
}

View File

@ -2,10 +2,7 @@ import { expect } from "chai";
import * as MSSQL from "mssql";
import * as Sinon from "sinon";
import MssqlDriver from "../../src/drivers/MssqlDriver";
import EntityInfo from "../../src/models/EntityInfo";
import ColumnInfo from "../../src/models/ColumnInfo";
import IndexInfo from "../../src/models/IndexInfo";
import RelationInfo from "../../src/models/RelationInfo";
import { Entity } from "../../src/models/Entity";
interface FakeResponse extends MSSQL.IResult<any> {
recordsets: MSSQL.IRecordSet<any>[];
@ -26,7 +23,7 @@ class FakeRecordset extends Array<any> implements MSSQL.IRecordSet<any> {
}
}
describe("MssqlDriver", function() {
describe("MssqlDriver", () => {
let driver: MssqlDriver;
const sandbox = Sinon.sandbox.create();
@ -50,15 +47,19 @@ describe("MssqlDriver", function() {
return response;
}
});
const result = await driver.GetAllTables("schema", "db");
const expectedResult = [] as EntityInfo[];
const y = new EntityInfo();
y.tsEntityName = "name";
y.sqlEntityName = "name";
y.Schema = "schema";
y.Columns = [] as ColumnInfo[];
y.Indexes = [] as IndexInfo[];
y.Database = "";
const result = await driver.GetAllTables("schema", "db", []);
const expectedResult = [] as Entity[];
const y: Entity = {
columns: [],
indices: [],
relationIds: [],
relations: [],
sqlName: "name",
tscName: "name",
schema: "schema",
database: "",
fileImports: []
};
expectedResult.push(y);
expect(result).to.be.deep.equal(expectedResult);
});
@ -73,7 +74,7 @@ describe("MssqlDriver", function() {
COLUMN_DEFAULT: "'a'",
COLUMN_NAME: "name",
DATA_TYPE: "int",
IS_NULLABLE: "YES",
IS_NULLABLE: "NO",
NUMERIC_PRECISION: 0,
NUMERIC_SCALE: 0,
IsIdentity: 1
@ -82,27 +83,31 @@ describe("MssqlDriver", function() {
}
});
const entities = [] as EntityInfo[];
const y = new EntityInfo();
y.tsEntityName = "name";
y.Columns = [] as ColumnInfo[];
y.Indexes = [] as IndexInfo[];
y.Database = "";
const entities = [] as Entity[];
const y: Entity = {
columns: [],
indices: [],
relationIds: [],
relations: [],
sqlName: "name",
tscName: "name",
schema: "schema",
database: "",
fileImports: []
};
entities.push(y);
const expected: EntityInfo[] = JSON.parse(JSON.stringify(entities));
expected[0].Columns.push({
const expected: Entity[] = JSON.parse(JSON.stringify(entities));
expected[0].columns.push({
options: {
default: `() => "'a'"`,
nullable: true,
generated: true,
name: "name",
unique: false,
type: "int"
name: "name"
},
tsName: "name",
tsType: "number",
relations: [] as RelationInfo[]
type: "int",
generated: true,
default: `() => "'a'"`,
tscName: "name",
tscType: "number"
});
const result = await driver.GetCoulmnsFromEntity(
entities,
"schema",

View File

@ -116,4 +116,10 @@ export class Post {
@Column("geometrycollection")
geometrycollection: string;
@Column("set", {
enum: ["A", "B", "C"],
default: ["A", "B"]
})
roles: ("A" | "B" | "C")[]
}

View File

@ -2,7 +2,6 @@ import { Entity, PrimaryColumn, Column } from "typeorm";
@Entity("Post")
export class Post {
@PrimaryColumn()
id: number;
@ -91,7 +90,7 @@ export class Post {
hierarchyid: string;
@Column("sql_variant")
sql_variant: string;
sqlVariant: string;
@Column("timestamp")
timestamp: Date;
@ -107,5 +106,4 @@ export class Post {
@Column("geography")
geography: string;
}

View File

@ -118,4 +118,10 @@ export class Post {
@Column("geometrycollection")
geometrycollection: string;
@Column("set", {
enum: ["A", "B", "C"],
default: ["A", "B"]
})
roles: ("A" | "B" | "C")[]
}

View File

@ -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;
}

View File

@ -81,7 +81,7 @@ export class Post {
varbit: string;
@Column("bit varying")
bit_varying: string;
bitVarying: string;
@Column("timetz")
timetz: string;
@ -93,10 +93,10 @@ export class Post {
timestamp: Date;
@Column("timestamp without time zone")
timestamp_without_time_zone: Date;
timestampWithoutTimeZone: Date;
@Column("timestamp with time zone")
timestamp_with_time_zone: Date;
timestampWithTimeZone: Date;
@Column("date")
date: string;
@ -104,10 +104,10 @@ export class Post {
@Column("time")
time: string;
@Column("time without time zone")
time_without_time_zone: string;
timeWithoutTimeZone: string;
@Column("time with time zone")
time_with_time_zone: string;
timeWithTimeZone: string;
@Column("interval")
interval: any;

View File

@ -2,7 +2,6 @@ import { Entity, PrimaryColumn, Column } from "typeorm";
@Entity("PostArrays")
export class PostArrays {
@PrimaryColumn()
id: number;
@ -82,7 +81,7 @@ export class PostArrays {
varbit: string[];
@Column("bit varying", { array: true })
bit_varying: string[];
bitVarying: string[];
@Column("timetz", { array: true })
timetz: string[];
@ -97,7 +96,7 @@ export class PostArrays {
// timestamp_without_time_zone: Date[];
@Column("timestamp with time zone", { array: true })
timestamp_with_time_zone: Date[];
timestampWithTimeZone: Date[];
@Column("date", { array: true })
date: string[];
@ -105,10 +104,10 @@ export class PostArrays {
@Column("time", { array: true })
time: string[];
@Column("time without time zone", { array: true })
time_without_time_zone: string[];
timeWithoutTimeZone: string[];
@Column("time with time zone", { array: true })
time_with_time_zone: string[];
timeWithTimeZone: string[];
@Column("interval", { array: true })
interval: any[];
@ -187,5 +186,4 @@ export class PostArrays {
@Column("daterange", { array: true })
daterange: string[];
}

View File

@ -2,7 +2,6 @@ import { Entity, PrimaryColumn, Column } from "typeorm";
@Entity("Post")
export class Post {
@PrimaryColumn()
id: number;
@ -34,7 +33,7 @@ export class Post {
bigint: string;
@Column("unsigned big int")
unsigned_big_int: string;
unsignedBigInt: string;
@Column("character")
character: string;
@ -43,13 +42,13 @@ export class Post {
varchar: string;
@Column("varying character")
varying_character: string;
varyingCharacter: string;
@Column("nchar")
nchar: string;
@Column("native character")
native_character: string;
nativeCharacter: string;
@Column("nvarchar")
nvarchar: string;
@ -88,5 +87,4 @@ export class Post {
@Column("datetime")
datetime: Date;
}

View File

@ -1,8 +1,13 @@
import { Column, Entity, PrimaryGeneratedColumn, Index, Generated } from "typeorm";
import {
Column,
Entity,
PrimaryGeneratedColumn,
Index,
Generated
} from "typeorm";
@Entity("Post")
export class Post {
@PrimaryGeneratedColumn()
id: number;
@ -13,8 +18,8 @@ export class Post {
text: string;
@Column("int", {
nullable: false
// Columns are non-nullable by default
// nullable: false
})
likesCount: number;
}

View File

@ -1,8 +1,14 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, JoinColumn, Index } from "typeorm";
import {
PrimaryGeneratedColumn,
Column,
Entity,
OneToOne,
JoinColumn,
Index
} from "typeorm";
@Entity("EverythingEntity")
export class EverythingEntity {
//TODO: change to check column types per database engine
@PrimaryGeneratedColumn()
id: number;
@ -68,5 +74,4 @@ export class EverythingEntity {
// @UpdateDateColumn()
// updatedDate: Date;
}

View File

@ -1,9 +1,14 @@
import { Column, Entity, Index, PrimaryGeneratedColumn, OneToMany } from "typeorm"
import {Post} from "./Post";
import {
Column,
Entity,
Index,
PrimaryGeneratedColumn,
OneToMany
} from "typeorm";
import { Post } from "./Post";
@Entity("Author")
export class Author {
@PrimaryGeneratedColumn()
id: number;
@ -12,6 +17,7 @@ export class Author {
@OneToMany(type => Post, post => post.author, {
// cascade: true
lazy: true
})
posts: Promise<Post[]>;
@ -21,5 +27,4 @@ export class Author {
// asPromise() {
// return Promise.resolve(this);
// }
}

View File

@ -1,16 +1,23 @@
import { Column, Entity, Index, PrimaryGeneratedColumn, VersionColumn, ManyToMany } from "typeorm"
import {Post} from "./Post";
import {
Column,
Entity,
Index,
PrimaryGeneratedColumn,
VersionColumn,
ManyToMany
} from "typeorm";
import { Post } from "./Post";
@Entity("Category")
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(type => Post, post => post.categorys)
@ManyToMany(type => Post, post => post.categories, {
lazy: true
})
posts: Promise<Post[]>;
}

View File

@ -1,10 +1,17 @@
import { Column, Entity, Index, PrimaryGeneratedColumn, ManyToOne, ManyToMany, JoinTable } from "typeorm"
import {
Column,
Entity,
Index,
PrimaryGeneratedColumn,
ManyToOne,
ManyToMany,
JoinTable
} from "typeorm";
import { Author } from "./Author";
import { Category } from "./Category";
@Entity("Post")
export class Post {
@PrimaryGeneratedColumn()
id: number;
@ -15,16 +22,17 @@ export class Post {
text: string;
@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<Author | null>;
@ManyToMany(type => Category, category => category.posts, {
lazy: true
// cascade: true
})
@JoinTable()
categorys: Promise<Category[]>;
categories: Promise<Category[]>;
}

View File

@ -1,4 +1,12 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, JoinColumn } from "typeorm";
import {
PrimaryGeneratedColumn,
Column,
Entity,
OneToOne,
OneToMany,
ManyToOne,
JoinColumn
} from "typeorm";
import { PostDetails } from "./PostDetails";
import { PostCategory } from "./PostCategory";
import { PostAuthor } from "./PostAuthor";
@ -8,7 +16,6 @@ import { PostMetadata } from "./PostMetadata";
@Entity("Post")
export class Post {
@PrimaryGeneratedColumn()
id: number;
@ -20,41 +27,40 @@ export class Post {
// post has relation with category, however inverse relation is not set (category does not have relation with post set)
@ManyToOne(type => PostCategory, {
cascade: true,
onDelete: 'CASCADE'
// cascade: true,
onDelete: "CASCADE"
})
category: PostCategory;
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
// relation it will be inserted automatically to the db when you save this Post entity
@ManyToOne(type => PostDetails, details => details.posts, {
cascade: true,
// cascade: true,
})
details: PostDetails;
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@ManyToOne(type => PostImage, image => image.posts, {
cascade: true,
// cascade: true,
})
image: PostImage;
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@ManyToOne(type => PostMetadata, metadata => metadata.posts, {
cascade: true,
// cascade: true,
})
metadata: PostMetadata | null;
// post has relation with details. full cascades here
@ManyToOne(type => PostInformation, information => information.posts, {
cascade: true,
onDelete: 'CASCADE'
// cascade: true,
onDelete: "CASCADE"
})
information: PostInformation;
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
@ManyToOne(type => PostAuthor, author => author.posts)
author: PostAuthor;
}

View File

@ -1,14 +1,23 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {PostDetail} from "./PostDetail";
import {PostCategory} from "./PostCategory";
import {PostAuthor} from "./PostAuthor";
import {PostInformation} from "./PostInformation";
import {PostImage} from "./PostImage";
import {PostMetadata} from "./PostMetadata";
import {
PrimaryGeneratedColumn,
Column,
Entity,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { PostDetail } from "./PostDetail";
import { PostCategory } from "./PostCategory";
import { PostAuthor } from "./PostAuthor";
import { PostInformation } from "./PostInformation";
import { PostImage } from "./PostImage";
import { PostMetadata } from "./PostMetadata";
@Entity("Post")
export class Post {
@PrimaryGeneratedColumn()
id: number;
@ -20,15 +29,15 @@ export class Post {
// post has relation with category, however inverse relation is not set (category does not have relation with post set)
@ManyToMany(type => PostCategory, {
cascade: true
// cascade: true
})
@JoinTable()
postCategorys: PostCategory[];
postCategories: PostCategory[];
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
// relation it will be inserted automatically to the db when you save this Post entity
@ManyToMany(type => PostDetail, details => details.posts, {
cascade: true
// cascade: true
})
@JoinTable()
postDetails: PostDetail[];
@ -36,7 +45,7 @@ export class Post {
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@ManyToMany(type => PostImage, image => image.posts, {
cascade: true
// cascade: true
})
@JoinTable()
postImages: PostImage[];
@ -45,11 +54,11 @@ export class Post {
// it will be inserted automatically to the db when you save this Post entity
@ManyToMany(type => PostMetadata, metadata => metadata.posts)
@JoinTable()
postMetadatas: PostMetadata[];
postMetadata: PostMetadata[];
// post has relation with details. full cascades here
@ManyToMany(type => PostInformation, information => information.posts, {
cascade: true
// cascade: true
})
@JoinTable()
postInformations: PostInformation[];
@ -58,5 +67,4 @@ export class Post {
@ManyToMany(type => PostAuthor, author => author.posts)
@JoinTable()
postAuthors: PostAuthor[];
}

View File

@ -1,16 +1,24 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {Post} from "./Post";
import {
PrimaryGeneratedColumn,
Column,
Entity,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { Post } from "./Post";
@Entity("PostMetadata")
export class PostMetadata {
@PrimaryGeneratedColumn()
id: number;
@Column()
description: string;
@ManyToMany(type => Post, post => post.postMetadatas)
@ManyToMany(type => Post, post => post.postMetadata)
posts: Post[];
}

View File

@ -0,0 +1,29 @@
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { Section } from "./Section";
@Entity("Post")
export class Post {
@Column("int", {
primary: true,
name: "Id"
})
id: number;
@OneToOne(type => Section, section => section.post)
@JoinColumn([
{ name: "work", referencedColumnName: "work" },
{ name: "section", referencedColumnName: "section" }
])
section: Section;
}

View File

@ -0,0 +1,32 @@
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("Section")
export class Section {
@Column("int", {
primary: true,
name: "work"
})
work: number;
@Column("int", {
primary: true,
name: "section"
})
section: number;
@OneToOne(type => Post, Post => Post.id)
post: Post;
}

View File

@ -1,26 +1,36 @@
import { Index, Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { PostAuthor } from "./PostAuthor";
import { PostCategory } from "./PostCategory";
@Index("travel_travelplanextra_travel_plan_id_extra_id_f825ca51_uniq",["postAuthor","postCategory",],{unique:true})
@Index(
"travel_travelplanextra_travel_plan_id_extra_id_f825ca51_uniq",
["postAuthor", "postCategory"],
{ unique: true }
)
@Entity("Post")
export class Post {
@Column("int", {
nullable: false,
primary: true,
name: "Id"
name: "id"
})
Id: number;
id: number;
@ManyToOne(type => PostAuthor, PostAuthor => PostAuthor.Id)
@ManyToOne(type => PostAuthor, PostAuthor => PostAuthor.id)
@JoinColumn()
postAuthor: PostAuthor;
@ManyToOne(type => PostCategory, PostCategory => PostCategory.Id)
@ManyToOne(type => PostCategory, PostCategory => PostCategory.id)
@JoinColumn()
postCategory: PostCategory;
}

View File

@ -1,20 +1,26 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("PostAuthor")
export class PostAuthor {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToMany(type => Post, Post => Post.Id)
posts:Post[];
@OneToMany(type => Post, Post => Post.id)
posts: Post[];
}

View File

@ -1,20 +1,26 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("PostCategory")
export class PostCategory {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToMany(type => Post, Post => Post.Id)
posts:Post[];
@OneToMany(type => Post, Post => Post.id)
posts: Post[];
}

View File

@ -0,0 +1,19 @@
import { PrimaryGeneratedColumn, Entity, ManyToMany, JoinTable } from "typeorm";
import { ClientCategory } from "./ClientCategory";
@Entity("Client")
export class Client {
@PrimaryGeneratedColumn()
id: number;
@ManyToMany(
() => ClientCategory,
(clientCategory: ClientCategory) => clientCategory.clients
)
@JoinTable({
name: "client_categories",
joinColumn: { name: "client_id" },
inverseJoinColumn: { name: "category_id" }
})
clientCategories: ClientCategory[];
}

View File

@ -0,0 +1,11 @@
import { Entity, PrimaryGeneratedColumn, ManyToMany } from "typeorm";
import { Client } from "./Client";
@Entity("ClientCategory")
export class ClientCategory {
@PrimaryGeneratedColumn()
id: number;
@ManyToMany(() => Client, (client: Client) => client.clientCategories)
clients: Client[];
}

View File

@ -0,0 +1,15 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, JoinColumn, Index } from "typeorm";
@Entity("Post")
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column("varchar", { nullable: true })
title: string | null;
@Column()
text: string;
}

View File

@ -1,28 +1,30 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, JoinColumn} from "typeorm";
import {User} from "./User";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
JoinColumn
} from "typeorm";
import { User } from "./User";
@Entity("Post",{schema:"sch1"})
@Entity("Post", { schema: "sch1" })
export class Post {
@Column("integer", {
primary: true,
name: "id"
})
id: number;
@Column("integer",{
nullable:false,
primary:true,
name:"id"
})
id:number;
@ManyToOne(type=>User, userId=>userId.posts)
@JoinColumn({ name:"userId"})
userId:User;
@Column("text",{
nullable:true,
name:"body"
})
body:string;
@ManyToOne(type => User, user => user.posts)
@JoinColumn({ name: "userId" })
user: User;
@Column("text", {
nullable: true,
name: "body"
})
body: string;
}

View File

@ -1,27 +1,29 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, JoinColumn} from "typeorm";
import {Post} from "./Post";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
JoinColumn
} from "typeorm";
import { Post } from "./Post";
@Entity("User",{schema:"sch2"})
@Entity("User", { schema: "sch2" })
export class User {
@Column("integer", {
primary: true,
name: "id"
})
id: number;
@Column("integer",{
nullable:false,
primary:true,
name:"id"
})
id:number;
@Column("text",{
nullable:true,
name:"name"
})
name:string;
@OneToMany(type=>Post, posts=>posts.userId)
posts:Post[];
@Column("text", {
nullable: true,
name: "name"
})
name: string;
@OneToMany(type => Post, posts => posts.user)
posts: Post[];
}

View File

@ -1,20 +1,25 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, JoinColumn} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
JoinColumn
} from "typeorm";
@Entity("Post")
export class Post {
@Column("integer", {
primary: true,
name: "id"
})
id: number;
@Column("integer",{
nullable:false,
primary:true,
name:"id"
})
id:number;
@Column({ unique: true })
body: string;
@Column({unique:true})
body:string;
@Column()
body2:string;
body2: string;
}

View File

@ -0,0 +1,46 @@
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { Users } from "./Users";
import { Quests } from "./Quests";
@Entity("feedextrainfo")
@Index("feedExtraInfo_FeedOwnerId_idx", ["feedOwnerId"], { unique: true })
@Index("feedExtraInfo_ReaderId_idx", ["readerId"], { unique: true })
@Index("feedExtraInfo_QuestId_idx", ["questId"], { unique: true })
export class Feedextrainfo {
@PrimaryColumn({ name: "FeedOwnerId" })
feedOwnerId: number;
@PrimaryColumn({ name: "QuestId" })
questId: number;
@PrimaryColumn({ name: "ReaderId" })
readerId: number;
@OneToOne(type => Users, FeedOwnerId => FeedOwnerId.feedextrainfo)
@JoinColumn({ name: "FeedOwnerId" })
feedOwner: Users;
@OneToOne(type => Quests, QuestId => QuestId.feedextrainfo)
@JoinColumn({ name: "QuestId" })
quest: Quests;
@OneToOne(type => Users, ReaderId => ReaderId.feedextrainfo2)
@JoinColumn({ name: "ReaderId" })
reader: Users;
@Column("int", {
name: "MostUpdatedFeedEntryIdUserRead"
})
mostUpdatedFeedEntryIdUserRead: number;
}

View File

@ -0,0 +1,25 @@
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { Feedextrainfo } from "./Feedextrainfo";
@Entity("quests")
export class Quests {
@Column("int", {
primary: true,
name: "QuestId"
})
questId: number;
@OneToOne(type => Feedextrainfo, feedextrainfo => feedextrainfo.quest)
feedextrainfo: Feedextrainfo;
}

View File

@ -0,0 +1,28 @@
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { Feedextrainfo } from "./Feedextrainfo";
@Entity("users")
export class Users {
@Column("int", {
primary: true,
name: "UserId"
})
userId: number;
@OneToOne(type => Feedextrainfo, feedextrainfo => feedextrainfo.feedOwner)
feedextrainfo: Feedextrainfo;
@OneToOne(type => Feedextrainfo, feedextrainfo2 => feedextrainfo2.reader)
feedextrainfo2: Feedextrainfo;
}

View File

@ -1,36 +0,0 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm";
import {users} from "./users";
import {quests} from "./quests";
@Entity("feedextrainfo")
@Index("feedExtraInfo_FeedOwnerId_idx",["feedOwnerId",],{unique:true})
@Index("feedExtraInfo_ReaderId_idx",["readerId",],{unique:true})
@Index("feedExtraInfo_QuestId_idx",["questId",],{unique:true})
export class feedextrainfo {
@OneToOne(type=>users, FeedOwnerId=>FeedOwnerId.feedextrainfo,{primary:true, nullable:false, })
@JoinColumn({ name:'FeedOwnerId'})
feedOwnerId:users;
@OneToOne(type=>quests, QuestId=>QuestId.feedextrainfo,{primary:true, nullable:false, })
@JoinColumn({ name:'QuestId'})
questId:quests;
@OneToOne(type=>users, ReaderId=>ReaderId.feedextrainfo2,{primary:true, nullable:false, })
@JoinColumn({ name:'ReaderId'})
readerId:users;
@Column("int",{
nullable:false,
name:"MostUpdatedFeedEntryIdUserRead"
})
MostUpdatedFeedEntryIdUserRead:number;
}

View File

@ -1,20 +0,0 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm";
import {feedextrainfo} from "./feedextrainfo";
@Entity("quests")
export class quests {
@Column("int",{
nullable:false,
primary:true,
name:"QuestId"
})
QuestId:number;
@OneToOne(type=>feedextrainfo, feedextrainfo=>feedextrainfo.questId)
feedextrainfo:feedextrainfo;
}

View File

@ -1,25 +0,0 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm";
import {feedextrainfo} from "./feedextrainfo";
@Entity("users")
export class users {
@Column("int",{
nullable:false,
primary:true,
name:"UserId"
})
UserId:number;
@OneToOne(type=>feedextrainfo, feedextrainfo=>feedextrainfo.feedOwnerId)
feedextrainfo:feedextrainfo;
@OneToOne(type=>feedextrainfo, feedextrainfo2=>feedextrainfo2.readerId)
feedextrainfo2:feedextrainfo;
}

View File

@ -1,24 +1,29 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { PostAuthor } from "./PostAuthor";
import { PostReader } from "./PostReader";
@Entity("Post")
export class Post {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToOne(type => PostAuthor, PostAuthor => PostAuthor.Id)
@OneToOne(type => PostAuthor, PostAuthor => PostAuthor.id)
postAuthor: PostAuthor;
@OneToMany(type => PostReader, PostReader => PostReader.Id)
@OneToMany(type => PostReader, PostReader => PostReader.id)
postReaders: PostReader[];
}

View File

@ -1,22 +1,29 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("PostAuthor")
export class PostAuthor {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToOne(type => Post, Post => Post.Id)
@OneToOne(type => Post, Post => Post.id)
@JoinColumn()
post:Post;
post: Post;
@RelationId((postAuthor: PostAuthor) => postAuthor.post)
postId: number;

View File

@ -1,20 +1,29 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("PostReader")
export class PostReader {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@ManyToOne(type => Post, Post => Post.Id)
@ManyToOne(type => Post, Post => Post.id)
@JoinColumn()
post:Post;
post: Post;
@RelationId((postReader: PostReader) => postReader.post)
postId: number[];

View File

@ -1,41 +1,47 @@
import { Index, Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable
} from "typeorm";
import { PostReader } from "./PostReader";
import { PostAuthor } from "./PostAuthor";
import { PostCategory } from "./PostCategory";
import { PostDetails } from "./PostDetails";
@Entity("Post")
export class Post {
@Column("int", {
nullable: false,
// nullable: false,
primary: true,
name: "Id"
name: "id"
})
Id: number;
id: number;
@OneToOne(type => PostAuthor, PostAuthor => PostAuthor.Id,
{
// onDelete: "CASCADE",
// onUpdate: "CASCADE"
})
@OneToOne(type => PostAuthor, PostAuthor => PostAuthor.id, {
// onDelete: "CASCADE",
// onUpdate: "CASCADE"
})
postAuthor: PostAuthor;
@OneToOne(type => PostReader, PostReader => PostReader.Id)
@OneToOne(type => PostReader, PostReader => PostReader.id)
postReader: PostReader;
@OneToOne(type => PostCategory, PostCategory => PostCategory.Id,
{
// onDelete: "RESTRICT",
// onUpdate: "RESTRICT"
})
@OneToOne(type => PostCategory, PostCategory => PostCategory.id, {
// onDelete: "RESTRICT",
// onUpdate: "RESTRICT"
})
postCategory: PostCategory;
@OneToOne(type => PostDetails, PostDetails => PostDetails.Id,
{
// onDelete: "SET NULL",
// onUpdate: "SET NULL"
})
@OneToOne(type => PostDetails, PostDetails => PostDetails.id, {
// onDelete: "SET NULL",
// onUpdate: "SET NULL"
})
postDetails: PostDetails;
}

View File

@ -1,24 +1,30 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("PostAuthor")
export class PostAuthor {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToOne(type => Post, Post => Post.Id,{
onDelete: "CASCADE",
@OneToOne(type => Post, Post => Post.id, {
// onDelete: "CASCADE"
// onUpdate: "CASCADE"
})
@JoinColumn()
post:Post;
post: Post;
}

View File

@ -1,25 +1,30 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("PostCategory")
export class PostCategory {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToOne(type => Post, Post => Post.Id,
{
// onDelete: "RESTRICT",
// onUpdate: "RESTRICT"
})
@OneToOne(type => Post, Post => Post.id, {
// onDelete: "RESTRICT",
// onUpdate: "RESTRICT"
})
@JoinColumn()
post:Post;
post: Post;
}

View File

@ -1,23 +1,30 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("PostDetails")
export class PostDetails {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToOne(type => Post, Post => Post.Id,
{
onDelete: "SET NULL",
// onUpdate: "SET NULL"
})
@OneToOne(type => Post, Post => Post.id, {
// onDelete: "SET NULL"
// onUpdate: "SET NULL"
})
@JoinColumn()
post:Post;
post: Post;
}

View File

@ -1,19 +1,27 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import {
Index,
Entity,
PrimaryColumn,
Column,
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
RelationId
} from "typeorm";
import { Post } from "./Post";
@Entity("PostReader")
export class PostReader {
@Column("int", {
primary: true,
name: "id"
})
id: number;
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToOne(type => Post, Post => Post.Id)
@OneToOne(type => Post, Post => Post.id)
@JoinColumn()
post:Post;
post: Post;
}

View File

@ -1,48 +1,39 @@
import "reflect-metadata";
import { expect } from "chai";
import * as chai from "chai";
import * as ts from "typescript";
import * as fs from "fs-extra";
import * as path from "path";
import * as chaiSubset from "chai-subset";
import * as flatMap from "array.prototype.flatmap";
import yn from "yn";
import { CLIEngine } from "eslint";
import EntityFileToJson from "../utils/EntityFileToJson";
import {
createDriver,
dataCollectionPhase,
modelCustomizationPhase,
modelGenerationPhase
} from "../../src/Engine";
import { createDriver, dataCollectionPhase } from "../../src/Engine";
import * as GTU from "../utils/GeneralTestUtils";
import EntityInfo from "../../src/models/EntityInfo";
import { Entity } from "../../src/models/Entity";
import IConnectionOptions from "../../src/IConnectionOptions";
import fs = require("fs-extra");
import path = require("path");
import chaiSubset = require("chai-subset");
import chai = require("chai");
import yn = require("yn");
import modelCustomizationPhase from "../../src/ModelCustomization";
import modelGenerationPhase from "../../src/ModelGeneration";
require("dotenv").config();
flatMap.shim();
chai.use(chaiSubset);
const { expect } = chai;
it("Column default values", async function() {
it("Column default values", async () => {
const testPartialPath = "test/integration/defaultValues";
this.timeout(60000);
this.slow(10000); // compiling created models takes time
await runTestsFromPath(testPartialPath, true);
});
it("Platform specyfic types", async function() {
this.timeout(60000);
this.slow(10000); // compiling created models takes time
it("Platform specific types", async () => {
const testPartialPath = "test/integration/entityTypes";
await runTestsFromPath(testPartialPath, true);
});
describe("GitHub issues", async function() {
this.timeout(60000);
this.slow(10000); // compiling created models takes time
describe("GitHub issues", async () => {
const testPartialPath = "test/integration/github-issues";
await runTestsFromPath(testPartialPath, false);
});
describe("TypeOrm examples", async function() {
this.timeout(60000);
this.slow(10000); // compiling created models takes time
describe("TypeOrm examples", async () => {
const testPartialPath = "test/integration/examples";
await runTestsFromPath(testPartialPath, false);
});
@ -77,8 +68,8 @@ function runTestForMultipleDrivers(
dbDrivers: string[],
testPartialPath: string
) {
it(testName, async function() {
const driversToRun = selectDriversForSpecyficTest();
it(testName, async () => {
const driversToRun = selectDriversForSpecificTest();
const modelGenerationPromises = driversToRun.map(async dbDriver => {
const {
generationOptions,
@ -87,25 +78,26 @@ function runTestForMultipleDrivers(
resultsPath,
filesOrgPathTS
} = await prepareTestRuns(testPartialPath, testName, dbDriver);
let dbModel: EntityInfo[] = [];
let dbModel: Entity[] = [];
switch (testName) {
case "144":
dbModel = await dataCollectionPhase(
driver,
Object.assign(connectionOptions, {
databaseName: "db1,db2"
})
}),
generationOptions
);
break;
default:
dbModel = await dataCollectionPhase(
driver,
connectionOptions
connectionOptions,
generationOptions
);
break;
}
dbModel = modelCustomizationPhase(
dbModel,
generationOptions,
@ -127,7 +119,7 @@ function runTestForMultipleDrivers(
compileGeneratedModel(path.resolve(process.cwd(), `output`), dbDrivers);
});
function selectDriversForSpecyficTest() {
function selectDriversForSpecificTest() {
switch (testName) {
case "39":
return dbDrivers.filter(
@ -161,7 +153,11 @@ async function runTest(
resultsPath,
filesOrgPathTS
} = await prepareTestRuns(testPartialPath, dbDriver, dbDriver);
let dbModel = await dataCollectionPhase(driver, connectionOptions);
let dbModel = await dataCollectionPhase(
driver,
connectionOptions,
generationOptions
);
dbModel = modelCustomizationPhase(
dbModel,
generationOptions,
@ -190,23 +186,74 @@ function compareGeneratedFiles(filesOrgPathTS: string, filesGenPath: string) {
const filesGen = fs
.readdirSync(filesGenPath)
.filter(val => val.toString().endsWith(".ts"));
expect(filesOrg, "Errors detected in model comparision").to.be.deep.equal(
expect(filesOrg, "Errors detected in model comparison").to.be.deep.equal(
filesGen
);
filesOrg.forEach(file => {
const jsonEntityOrg = EntityFileToJson.convert(
fs.readFileSync(path.resolve(filesOrgPathTS, file))
);
const jsonEntityGen = EntityFileToJson.convert(
const generatedEntities = filesOrg.map(file =>
EntityFileToJson.convert(
fs.readFileSync(path.resolve(filesGenPath, file))
);
expect(jsonEntityGen, `Error in file ${file}`).to.containSubset(
jsonEntityOrg
);
});
)
);
const originalEntities = filesGen.map(file =>
EntityFileToJson.convert(
fs.readFileSync(path.resolve(filesOrgPathTS, file))
)
);
generatedEntities
.flatMap(entity =>
entity.columns
.filter(
column =>
column.relationType === "ManyToMany" &&
column.joinOptions.length > 0
)
.map(v => {
return {
ownerColumn: v,
ownerEntity: entity
};
})
)
.forEach(({ ownerColumn, ownerEntity }) => {
const childColumn = generatedEntities
.find(
childEntity =>
childEntity.entityName.toLowerCase() ===
ownerColumn.columnTypes[0]
.substring(0, ownerColumn.columnTypes[0].length - 2)
.toLowerCase()
)!
.columns.find(
column =>
column.columnTypes[0].toLowerCase() ===
`${ownerEntity.entityName}[]`.toLowerCase()
)!;
childColumn.joinOptions = ownerColumn.joinOptions.map(options => {
return {
...options,
joinColumns: options.inverseJoinColumns,
inverseJoinColumns: options.joinColumns
};
});
});
// TODO: set relation options on ManyToMany to both side of relation
generatedEntities
.map((ent, i) => [ent, originalEntities[i], filesOrg[i]])
.forEach(([generated, original, file]) => {
expect(generated, `Error in file ${file}`).to.containSubset(
original
);
});
}
function compileGeneratedModel(filesGenPath: string, drivers: string[]) {
// TODO: Move(?)
// eslint-disable-next-line import/prefer-default-export
export function compileGeneratedModel(
filesGenPath: string,
drivers: string[],
lintGeneratedFiles = true
) {
const currentDirectoryFiles: string[] = [];
drivers.forEach(driver => {
const entitiesPath = path.resolve(filesGenPath, driver, "entities");
@ -223,7 +270,7 @@ function compileGeneratedModel(filesGenPath: string, drivers: string[]) {
);
}
});
const compileErrors = GTU.compileTsFiles(currentDirectoryFiles, {
const compiledWithoutErrors = GTU.compileTsFiles(currentDirectoryFiles, {
experimentalDecorators: true,
sourceMap: false,
emitDecoratorMetadata: true,
@ -231,8 +278,24 @@ function compileGeneratedModel(filesGenPath: string, drivers: string[]) {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS
});
expect(compileErrors, "Errors detected while compiling generated model").to
.be.false;
expect(
compiledWithoutErrors,
"Errors detected while compiling generated model"
).to.equal(true);
if (lintGeneratedFiles) {
const cli = new CLIEngine({ configFile: "test/configs/.eslintrc.js" });
const lintReport = cli.executeOnFiles(currentDirectoryFiles);
lintReport.results.forEach(result =>
result.messages.forEach(message => {
console.error(
`${result.filePath}:${message.line} - ${message.message}`
);
})
);
expect(lintReport.errorCount).to.equal(0);
expect(lintReport.warningCount).to.equal(0);
}
}
async function prepareTestRuns(
@ -276,7 +339,8 @@ async function prepareTestRuns(
password: String(process.env.MYSQL_Password),
databaseType: "mysql",
schemaName: "ignored",
ssl: yn(process.env.MYSQL_SSL)
ssl: yn(process.env.MYSQL_SSL, { default: false }),
skipTables: []
};
break;
case "mariadb":
@ -288,7 +352,8 @@ async function prepareTestRuns(
password: String(process.env.MARIADB_Password),
databaseType: "mariadb",
schemaName: "ignored",
ssl: yn(process.env.MARIADB_SSL)
ssl: yn(process.env.MARIADB_SSL, { default: false }),
skipTables: []
};
break;

3
test/mocha.opts Normal file
View File

@ -0,0 +1,3 @@
--timeout 60000
--slow 20000
-R spec

View File

@ -0,0 +1,741 @@
import * as path from "path";
import * as fs from "fs-extra";
import * as chai from "chai";
import * as chaiSubset from "chai-subset";
import { Entity } from "../../src/models/Entity";
import modelCustomizationPhase from "../../src/ModelCustomization";
import { getDefaultGenerationOptions } from "../../src/IGenerationOptions";
import modelGenerationPhase from "../../src/ModelGeneration";
import { getDefaultConnectionOptions } from "../../src/IConnectionOptions";
import { compileGeneratedModel } from "../integration/runTestsFromPath.test";
chai.use(chaiSubset);
const { expect } = chai;
// TODO: test for connectionOptions.specyficTables
describe("Model customization phase", async () => {
const generateSampleData: () => Entity[] = () => [
{
columns: [
{
generated: true,
type: "integer",
options: { name: "id" },
tscName: "id",
tscType: "number",
primary: true
},
{
type: "character varying",
options: { name: "name" },
tscName: "name",
tscType: "string"
}
],
indices: [
{
columns: ["id"],
options: { unique: true },
name: "PK_6571d08cfb2f1ab06c3aab425a6",
primary: true
}
],
relations: [
{
fieldName: "Post",
relatedField: "authorId",
relatedTable: "Post",
relationType: "OneToMany"
}
],
relationIds: [],
sqlName: "PostAuthor",
tscName: "PostAuthor",
database: "",
schema: "public",
fileImports: []
},
{
columns: [
{
generated: true,
type: "integer",
options: { name: "id" },
tscName: "id",
tscType: "number",
primary: true
},
{
type: "character varying",
options: { name: "title" },
tscName: "title",
tscType: "string"
},
{
type: "character varying",
options: { name: "text" },
tscName: "text",
tscType: "string"
}
],
indices: [
{
columns: ["authorId"],
options: { unique: true },
name: "REL_cef8d6e8edb69c82e5f10bb402"
},
{
columns: ["id"],
options: { unique: true },
name: "PK_c4d3b3dcd73db0b0129ea829f9f",
primary: true
}
],
relations: [
{
fieldName: "authorId",
relatedField: "Post",
joinColumnOptions: [
{ name: "authorId", referencedColumnName: "id" }
],
relatedTable: "PostAuthor",
relationType: "ManyToOne"
}
],
relationIds: [],
sqlName: "Post",
tscName: "Post",
database: "",
schema: "public",
fileImports: []
}
];
const resultsPath = path.resolve(process.cwd(), `output`);
const generateGenerationOptions = () => {
const generationOptions = getDefaultGenerationOptions();
generationOptions.resultsPath = resultsPath;
return generationOptions;
};
const clearGenerationDir = () => {
fs.ensureDirSync(resultsPath);
fs.emptyDirSync(resultsPath);
};
describe("case-file", () => {
it("PascalCase", () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.convertCaseFile = "pascal";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const files = fs.readdirSync(filesGenPath).sort();
expect(files[0]).to.equal("Post.ts");
expect(files[1]).to.equal("PostAuthor.ts");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("camelCase", () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.convertCaseFile = "camel";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const files = fs.readdirSync(filesGenPath).sort();
expect(files[0]).to.equal("post.ts");
expect(files[1]).to.equal("postAuthor.ts");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
});
describe("case-entity", () => {
it("PascalCase", () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.convertCaseEntity = "pascal";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.contain("class Post {");
expect(postAuthorContent).to.contain("class PostAuthor {");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("camelCase", () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.convertCaseEntity = "camel";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.contain("class post {");
expect(postAuthorContent).to.contain("class postAuthor {");
compileGeneratedModel(generationOptions.resultsPath, [""], false);
});
});
describe("case-property", async () => {
it("PascalCase", () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.convertCaseProperty = "pascal";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.contain("Title: string;");
expect(postAuthorContent).to.contain("Posts: Post[];");
compileGeneratedModel(generationOptions.resultsPath, [""], false);
});
it("camelCase", () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.convertCaseProperty = "camel";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.contain("title: string;");
expect(postAuthorContent).to.contain("posts: Post[];");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
});
describe("property-visibility", () => {
it("public", () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.propertyVisibility = "public";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(" public title: string");
expect(postAuthorContent).to.have.string(" public posts: Post[];");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("none", () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.propertyVisibility = "none";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(" title: string");
expect(postAuthorContent).to.have.string(" posts: Post[];");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
});
it("lazy", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.lazy = true;
generationOptions.pluralizeNames = false;
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string("lazy: true");
expect(postContent).to.have.string("Promise<PostAuthor>;");
expect(postAuthorContent).to.have.string("lazy: true");
expect(postAuthorContent).to.have.string("Promise<Post[]>");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("activeRecord", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.activeRecord = true;
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(
`export class Post extends BaseEntity `
);
expect(postAuthorContent).to.have.string(
`export class PostAuthor extends BaseEntity `
);
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("skipSchema", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.skipSchema = true;
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(`@Entity("Post")`);
expect(postAuthorContent).to.have.string(`@Entity("PostAuthor")`);
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("generateConstructor", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.generateConstructor = true;
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(`constructor(init?: Partial<Post>)`);
expect(postContent).to.have.string(`Object.assign(this, init);`);
expect(postAuthorContent).to.have.string(
`constructor(init?: Partial<PostAuthor>)`
);
expect(postAuthorContent).to.have.string(`Object.assign(this, init);`);
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("generateConstructor with activeRecord", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.generateConstructor = true;
generationOptions.activeRecord = true;
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(`constructor(init?: Partial<Post>)`);
expect(postContent).to.have.string(`super();`);
expect(postContent).to.have.string(`Object.assign(this, init);`);
expect(postAuthorContent).to.have.string(
`constructor(init?: Partial<PostAuthor>)`
);
expect(postAuthorContent).to.have.string(`super();`);
expect(postAuthorContent).to.have.string(`Object.assign(this, init);`);
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
describe("strictMode", () => {
it("!", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.strictMode = "!";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(`id!: number;`);
expect(postContent).to.have.string(`title!: string;`);
expect(postContent).to.have.string(`text!: string;`);
expect(postContent).to.have.string(`author!: PostAuthor;`);
expect(postAuthorContent).to.have.string(`id!: number;`);
expect(postAuthorContent).to.have.string(`name!: string;`);
expect(postAuthorContent).to.have.string(`posts!: Post[];`);
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("?", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.strictMode = "?";
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(`id?: number;`);
expect(postContent).to.have.string(`title?: string;`);
expect(postContent).to.have.string(`text?: string;`);
expect(postContent).to.have.string(`author?: PostAuthor;`);
expect(postAuthorContent).to.have.string(`id?: number;`);
expect(postAuthorContent).to.have.string(`name?: string;`);
expect(postAuthorContent).to.have.string(`posts?: Post[];`);
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
});
it("naming strategy", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();
generationOptions.convertCaseEntity = "none";
generationOptions.convertCaseFile = "none";
generationOptions.convertCaseProperty = "none";
generationOptions.customNamingStrategyPath =
"test/modelCustomization/testNamingStrategy.ts";
// TODO: relationId
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post_B.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor_B.ts"))
.toString();
expect(postContent).to.have.string(`@Entity("Post"`);
expect(postContent).to.have.string(`class Post_B {`);
expect(postContent).to.have.string(`id_C: number;`);
expect(postContent).to.have.string(`author_A: PostAuthor_B`);
expect(postContent).to.have.string(
`import { PostAuthor_B } from "./PostAuthor_B";`
);
expect(postAuthorContent).to.have.string(`@Entity("PostAuthor"`);
expect(postAuthorContent).to.have.string(`class PostAuthor_B`);
expect(postAuthorContent).to.have.string(`id_C: number;`);
expect(postAuthorContent).to.have.string(
`import { Post_B } from "./Post_B";`
);
compileGeneratedModel(generationOptions.resultsPath, [""], false);
});
describe("pluralization", () => {
it("enabled", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
generationOptions.pluralizeNames = true;
clearGenerationDir();
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postAuthorContent).to.contain("posts: Post[];");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("disabled", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
generationOptions.pluralizeNames = false;
clearGenerationDir();
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postAuthorContent).to.contain("post: Post[];");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
});
describe("index file generation", () => {
it("named export", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
generationOptions.indexFile = true;
clearGenerationDir();
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const indexFileContent = fs
.readFileSync(path.resolve(filesGenPath, "Index.ts"))
.toString();
expect(indexFileContent).to.contain(
'import { PostAuthor } from "./PostAuthor'
);
expect(indexFileContent).to.contain('import { Post } from "./Post');
expect(indexFileContent).to.contain("export { PostAuthor, Post }");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("default export", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
generationOptions.indexFile = true;
generationOptions.exportType = "default";
clearGenerationDir();
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const indexFileContent = fs
.readFileSync(path.resolve(filesGenPath, "Index.ts"))
.toString();
expect(indexFileContent).to.contain(
'import PostAuthor from "./PostAuthor'
);
expect(indexFileContent).to.contain('import Post from "./Post');
expect(indexFileContent).to.contain("export { PostAuthor, Post }");
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("disabled", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
generationOptions.pluralizeNames = false;
clearGenerationDir();
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
expect(
fs.existsSync(path.resolve(filesGenPath, "Index.ts"))
).to.equal(false);
compileGeneratedModel(generationOptions.resultsPath, [""]);
});
});
});

View File

@ -0,0 +1,22 @@
import * as NamingStrategy from "../../src/NamingStrategy";
import { RelationId } from "../../src/models/RelationId";
import { Relation } from "../../src/models/Relation";
export function relationIdName(
relationId: RelationId,
relation: Relation
): string {
return `${NamingStrategy.relationIdName(relationId, relation)}`;
}
export function relationName(relation: Relation): string {
return `${NamingStrategy.relationName(relation)}_A`;
}
export function entityName(oldEntityName: string): string {
return `${NamingStrategy.entityName(oldEntityName)}_B`;
}
export function columnName(oldColumnName: string): string {
return `${NamingStrategy.columnName(oldColumnName)}_C`;
}

View File

@ -2,38 +2,35 @@
class EntityJson {
public entityName: string;
public entityOptions: any = {};
public entityOptions: { [key: string]: string | boolean } = {};
public columns: EntityColumn[] = [] as EntityColumn[];
public indicies: EntityIndex[] = [] as EntityIndex[];
public indices: EntityIndex[] = [] as EntityIndex[];
}
class EntityColumn {
public columnName: string;
public columnTypes: string[] = [];
public columnOptions: any = {};
public columnOptions: { [key: string]: string | boolean } = {};
public relationType:
| "OneToOne"
| "OneToMany"
| "ManyToOne"
| "ManyToMany"
| "None" = "None";
public joinOptions: { [key: string]: string | boolean }[] = [];
public isOwnerOfRelation: boolean = false;
public relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany";
public isOwnerOfRelation = false;
}
class EntityIndex {
public indexName: string;
public columnNames: string[] = [];
public isUnique: boolean = false;
public isUnique = false;
}
export default class EntityFileToJson {
public static getEntityOptions(trimmedLine: string, ent: EntityJson) {
public static getEntityOptions(trimmedLine: string, ent: EntityJson): void {
const decoratorParameters = trimmedLine.slice(
trimmedLine.indexOf("(") + 1,
trimmedLine.lastIndexOf(")")
@ -62,7 +59,7 @@ export default class EntityFileToJson {
public static getColumnOptionsAndType(
trimmedLine: string,
col: EntityColumn
) {
): void {
const decoratorParameters = trimmedLine.slice(
trimmedLine.indexOf("(") + 1,
trimmedLine.lastIndexOf(")")
@ -122,7 +119,10 @@ export default class EntityFileToJson {
}
}
public static getRelationOptions(trimmedLine: string, col: EntityColumn) {
public static getRelationOptions(
trimmedLine: string,
col: EntityColumn
): void {
const decoratorParameters = trimmedLine.slice(
trimmedLine.indexOf("(") + 1,
trimmedLine.lastIndexOf(")")
@ -151,7 +151,7 @@ export default class EntityFileToJson {
}
}
public static getIndexOptions(trimmedLine: string, ind: EntityIndex) {
public static getIndexOptions(trimmedLine: string, ind: EntityIndex): void {
const decoratorParameters = trimmedLine.slice(
trimmedLine.indexOf("(") + 1,
trimmedLine.lastIndexOf(")")
@ -166,7 +166,7 @@ export default class EntityFileToJson {
decoratorParameters.indexOf('"') + 1,
decoratorParameters
.substr(decoratorParameters.indexOf('"') + 1)
.indexOf('"')
.indexOf('"') + 1
);
}
if (containsTables) {
@ -241,6 +241,14 @@ export default class EntityFileToJson {
}
if (!isInClassBody) {
if (trimmedLine.startsWith("import")) {
if (
EntityFileToJson.isPartOfMultilineStatement(trimmedLine)
) {
isMultilineStatement = true;
priorPartOfMultilineStatement = trimmedLine;
} else {
isMultilineStatement = false;
}
return;
}
if (trimmedLine.startsWith("@Entity")) {
@ -275,7 +283,7 @@ export default class EntityFileToJson {
isMultilineStatement = false;
const ind = new EntityIndex();
EntityFileToJson.getIndexOptions(trimmedLine, ind);
retVal.indicies.push(ind);
retVal.indices.push(ind);
}
return;
}
@ -341,6 +349,7 @@ export default class EntityFileToJson {
retVal.columns.push(column);
column.relationType = "ManyToOne";
column.isOwnerOfRelation = true;
EntityFileToJson.getRelationOptions(trimmedLine, column);
}
return;
}
@ -353,6 +362,7 @@ export default class EntityFileToJson {
const column = new EntityColumn();
retVal.columns.push(column);
column.relationType = "OneToMany";
EntityFileToJson.getRelationOptions(trimmedLine, column);
}
return;
}
@ -365,6 +375,7 @@ export default class EntityFileToJson {
const column = new EntityColumn();
retVal.columns.push(column);
column.relationType = "ManyToMany";
EntityFileToJson.getRelationOptions(trimmedLine, column);
}
return;
}
@ -390,6 +401,23 @@ export default class EntityFileToJson {
retVal.columns[
retVal.columns.length - 1
].isOwnerOfRelation = true;
const decoratorParameters = trimmedLine
.substring(
trimmedLine.indexOf("(") + 1,
trimmedLine.indexOf(")")
)
.trim()
.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ');
if (decoratorParameters.length > 0) {
const column =
retVal.columns[retVal.columns.length - 1];
const options = JSON.parse(decoratorParameters);
if (Array.isArray(options)) {
column.joinOptions = options as any;
} else {
column.joinOptions = [options] as any;
}
}
}
return;
}
@ -399,9 +427,35 @@ export default class EntityFileToJson {
priorPartOfMultilineStatement = trimmedLine;
} else {
isMultilineStatement = false;
retVal.columns[
retVal.columns.length - 1
].isOwnerOfRelation = true;
const decoratorParameters = trimmedLine
.substring(
trimmedLine.indexOf("(") + 1,
trimmedLine.indexOf(")")
)
.trim()
.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ');
if (decoratorParameters.length > 0) {
const column =
retVal.columns[retVal.columns.length - 1];
const options = JSON.parse(decoratorParameters);
if (
options.inverseJoinColumn &&
!Array.isArray(options.inverseJoinColumn)
) {
options.inverseJoinColumns = [
options.inverseJoinColumn
];
delete options.inverseJoinColumn;
}
if (
options.joinColumn &&
!Array.isArray(options.joinColumn)
) {
options.joinColumns = [options.joinColumn];
delete options.joinColumn;
}
column.joinOptions = [options];
}
}
return;
}
@ -413,7 +467,7 @@ export default class EntityFileToJson {
isMultilineStatement = false;
const ind = new EntityIndex();
EntityFileToJson.getIndexOptions(trimmedLine, ind);
retVal.indicies.push(ind);
retVal.indices.push(ind);
}
return;
}
@ -439,7 +493,7 @@ export default class EntityFileToJson {
colTypes = colTypes.substring(8, colTypes.length - 1);
retVal.columns[
retVal.columns.length - 1
].columnOptions.isLazy = true;
].columnOptions.isTypeLazy = true;
}
retVal.columns[
retVal.columns.length - 1
@ -461,13 +515,11 @@ export default class EntityFileToJson {
);
}
if (
retVal.indicies.length > 0 &&
retVal.indicies[retVal.indicies.length - 1].columnNames
retVal.indices.length > 0 &&
retVal.indices[retVal.indices.length - 1].columnNames
.length === 0
) {
retVal.indicies[
retVal.indicies.length - 1
].columnNames.push(
retVal.indices[retVal.indices.length - 1].columnNames.push(
retVal.columns[retVal.columns.length - 1].columnName
);
}
@ -483,16 +535,7 @@ export default class EntityFileToJson {
console.log(`${trimmedLine}`);
});
retVal.columns = retVal.columns.map(col => {
if (col.columnName.endsWith("Id")) {
col.columnName = col.columnName.substr(
0,
col.columnName.length - 2
);
}
return col;
});
retVal.indicies = retVal.indicies.map(ind => {
retVal.indices = retVal.indices.map(ind => {
ind.columnNames = ind.columnNames.map(colName => {
if (colName.endsWith("Id")) {
colName = colName.substr(0, colName.length - 2);
@ -504,7 +547,7 @@ export default class EntityFileToJson {
return retVal;
}
public static isPartOfMultilineStatement(statement: string) {
public static isPartOfMultilineStatement(statement: string): boolean {
const matchStarting =
statement.split("(").length + statement.split("{").length;
const matchEnding =

View File

@ -1,7 +1,9 @@
import { ConnectionOptions, createConnection } from "typeorm";
import * as ts from "typescript";
import * as yn from "yn";
import IGenerationOptions from "../../src/IGenerationOptions";
import IGenerationOptions, {
getDefaultGenerationOptions
} from "../../src/IGenerationOptions";
import IConnectionOptions from "../../src/IConnectionOptions";
import MssqlDriver from "../../src/drivers/MssqlDriver";
import MariaDbDriver from "../../src/drivers/MariaDbDriver";
@ -12,21 +14,9 @@ import MysqlDriver from "../../src/drivers/MysqlDriver";
import path = require("path");
export function getGenerationOptions(resultsPath: string): IGenerationOptions {
return {
resultsPath,
noConfigs: false,
convertCaseEntity: "none",
convertCaseFile: "none",
convertCaseProperty: "none",
propertyVisibility: "none",
lazy: false,
generateConstructor: false,
customNamingStrategyPath: "",
relationIds: false,
skipSchema: false,
activeRecord: false,
strictMode: false
};
const retVal = getDefaultGenerationOptions();
retVal.resultsPath = resultsPath;
return retVal;
}
export async function createMSSQLModels(
@ -41,7 +31,8 @@ export async function createMSSQLModels(
password: String(process.env.MSSQL_Password),
databaseType: "mssql",
schemaName: "dbo,sch1,sch2",
ssl: yn(process.env.MSSQL_SSL)
ssl: yn(process.env.MSSQL_SSL, { default: false }),
skipTables: []
};
await driver.ConnectToServer(connectionOptions);
connectionOptions.databaseName = String(process.env.MSSQL_Database);
@ -92,7 +83,8 @@ export async function createPostgresModels(
password: String(process.env.POSTGRES_Password),
databaseType: "postgres",
schemaName: "public,sch1,sch2",
ssl: yn(process.env.POSTGRES_SSL)
ssl: yn(process.env.POSTGRES_SSL, { default: false }),
skipTables: ["spatial_ref_sys"]
};
await driver.ConnectToServer(connectionOptions);
connectionOptions.databaseName = String(process.env.POSTGRES_Database);
@ -142,7 +134,8 @@ export async function createSQLiteModels(
password: "",
databaseType: "sqlite",
schemaName: "",
ssl: false
ssl: false,
skipTables: []
};
const connOpt: ConnectionOptions = {
@ -176,7 +169,8 @@ export async function createMysqlModels(
password: String(process.env.MYSQL_Password),
databaseType: "mysql",
schemaName: "ignored",
ssl: yn(process.env.MYSQL_SSL)
ssl: yn(process.env.MYSQL_SSL, { default: false }),
skipTables: []
};
await driver.ConnectToServer(connectionOptions);
@ -218,7 +212,8 @@ export async function createMariaDBModels(
password: String(process.env.MARIADB_Password),
databaseType: "mariadb",
schemaName: "ignored",
ssl: yn(process.env.MARIADB_SSL)
ssl: yn(process.env.MARIADB_SSL, { default: false }),
skipTables: []
};
await driver.ConnectToServer(connectionOptions);
@ -262,7 +257,8 @@ export async function createOracleDBModels(
password: String(process.env.ORACLE_PasswordSys),
databaseType: "oracle",
schemaName: String(process.env.ORACLE_Username),
ssl: yn(process.env.ORACLE_SSL)
ssl: yn(process.env.ORACLE_SSL, { default: false }),
skipTables: []
};
await driver.ConnectToServer(connectionOptions);
connectionOptions.user = String(process.env.ORACLE_Username);
@ -301,7 +297,7 @@ export function compileTsFiles(
): boolean {
const program = ts.createProgram(fileNames, options);
const emitResult = program.emit();
let compileErrors = false;
let compiledWithoutErrors = true;
const preDiagnostics = ts.getPreEmitDiagnostics(program);
const allDiagnostics = [...preDiagnostics, ...emitResult.diagnostics];
@ -318,13 +314,13 @@ export function compileTsFiles(
`${diagnostic.file!.fileName} (${lineAndCharacter.line +
1},${lineAndCharacter.character + 1}): ${message}`
);
compileErrors = true;
compiledWithoutErrors = false;
});
return compileErrors;
return compiledWithoutErrors;
}
export function getEnabledDbDrivers() {
export function getEnabledDbDrivers(): string[] {
const dbDrivers: string[] = [];
if (process.env.SQLITE_Skip === "0") {
dbDrivers.push("sqlite");

View File

@ -1,26 +1,31 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"declaration": false,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"stripInternal": true,
"strictNullChecks": true,
"moduleResolution": "node",
"newLine": "LF",
"outDir": "dist",
"lib": [
"es2017"
],
"resolveJsonModule": true,
},
"include": [
"src",
"test"
]
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"noImplicitAny": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"declaration": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"stripInternal": true,
"strictNullChecks": true,
"moduleResolution": "node",
"newLine": "LF",
"outDir": "dist",
"lib": [
"es2019",
"es2019.array"
],
"typeRoots": [
"./node_modules/@types"
],
"resolveJsonModule": true,
},
"include": [
"src",
"test",
],
"exclude": ["**/node_modules", "node_modules"]
}