Merge branch 'next'
This commit is contained in:
commit
285e705c92
@ -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
|
||||
|
16
.eslintrc.js
16
.eslintrc.js
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@ -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=
|
||||
|
38
CHANGELOG.md
38
CHANGELOG.md
@ -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
|
||||
|
||||
|
@ -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
4532
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
93
package.json
93
package.json
@ -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": {
|
||||
|
@ -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;
|
||||
}
|
457
src/Engine.ts
457
src/Engine.ts
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
357
src/ModelCustomization.ts
Normal 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
269
src/ModelGeneration.ts
Normal 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"
|
||||
});
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
74
src/Utils.ts
74
src/Utils.ts
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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}'"`;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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(`'`)) {
|
||||
|
@ -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}"`;
|
||||
|
@ -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}}
|
||||
}
|
829
src/index.ts
829
src/index.ts
File diff suppressed because it is too large
Load Diff
18
src/library.ts
Normal file
18
src/library.ts
Normal 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
25
src/models/Column.ts
Normal 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; // ?
|
||||
};
|
||||
};
|
@ -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
21
src/models/Entity.ts
Normal 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;
|
||||
};
|
@ -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
8
src/models/Index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export type Index = {
|
||||
name: string;
|
||||
columns: string[];
|
||||
options: {
|
||||
unique?: boolean;
|
||||
};
|
||||
primary?: boolean;
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
export default interface IndexColumnInfo {
|
||||
name: string;
|
||||
}
|
@ -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
12
src/models/Relation.ts
Normal 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
5
src/models/RelationId.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type RelationId = {
|
||||
fieldName: string;
|
||||
fieldType: string;
|
||||
relationField: string;
|
||||
};
|
@ -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";
|
||||
}
|
||||
}
|
12
src/models/RelationInternal.ts
Normal file
12
src/models/RelationInternal.ts
Normal 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;
|
||||
};
|
@ -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
47
src/templates/entity.mst
Normal 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
5
src/templates/index.mst
Normal file
@ -0,0 +1,5 @@
|
||||
{{#entities~}}
|
||||
import {{localImport (toEntityName tscName)}} from './{{toFileName tscName}}'
|
||||
{{/entities}}
|
||||
|
||||
export { {{#entities}}{{toEntityName tscName}},{{/entities~}} }
|
16
src/templates/ormconfig.mst
Normal file
16
src/templates/ormconfig.mst
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
10
src/templates/tsconfig.mst
Normal file
10
src/templates/tsconfig.mst
Normal 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
30
test/configs/.eslintrc.js
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
17
test/configs/tsconfig.json
Normal file
17
test/configs/tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
@ -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",
|
||||
|
@ -116,4 +116,10 @@ export class Post {
|
||||
|
||||
@Column("geometrycollection")
|
||||
geometrycollection: string;
|
||||
|
||||
@Column("set", {
|
||||
enum: ["A", "B", "C"],
|
||||
default: ["A", "B"]
|
||||
})
|
||||
roles: ("A" | "B" | "C")[]
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -118,4 +118,10 @@ export class Post {
|
||||
|
||||
@Column("geometrycollection")
|
||||
geometrycollection: string;
|
||||
|
||||
@Column("set", {
|
||||
enum: ["A", "B", "C"],
|
||||
default: ["A", "B"]
|
||||
})
|
||||
roles: ("A" | "B" | "C")[]
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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[];
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
// }
|
||||
|
||||
}
|
||||
|
@ -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[]>;
|
||||
|
||||
}
|
||||
|
@ -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[]>;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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[];
|
||||
|
||||
}
|
||||
|
@ -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[];
|
||||
|
||||
}
|
||||
|
29
test/integration/github-issues/117/entity/Post.ts
Normal file
29
test/integration/github-issues/117/entity/Post.ts
Normal 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;
|
||||
}
|
32
test/integration/github-issues/117/entity/Section.ts
Normal file
32
test/integration/github-issues/117/entity/Section.ts
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -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[];
|
||||
}
|
||||
|
19
test/integration/github-issues/183/entity/Client.ts
Normal file
19
test/integration/github-issues/183/entity/Client.ts
Normal 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[];
|
||||
}
|
11
test/integration/github-issues/183/entity/ClientCategory.ts
Normal file
11
test/integration/github-issues/183/entity/ClientCategory.ts
Normal 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[];
|
||||
}
|
15
test/integration/github-issues/227/entity/Post.ts
Normal file
15
test/integration/github-issues/227/entity/Post.ts
Normal 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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
46
test/integration/github-issues/58/entity/Feedextrainfo.ts
Normal file
46
test/integration/github-issues/58/entity/Feedextrainfo.ts
Normal 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;
|
||||
}
|
25
test/integration/github-issues/58/entity/Quests.ts
Normal file
25
test/integration/github-issues/58/entity/Quests.ts
Normal 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;
|
||||
}
|
28
test/integration/github-issues/58/entity/Users.ts
Normal file
28
test/integration/github-issues/58/entity/Users.ts
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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[];
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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[];
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
3
test/mocha.opts
Normal file
@ -0,0 +1,3 @@
|
||||
--timeout 60000
|
||||
--slow 20000
|
||||
-R spec
|
741
test/modelCustomization/modelCustomization.test.ts
Normal file
741
test/modelCustomization/modelCustomization.test.ts
Normal 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, [""]);
|
||||
});
|
||||
});
|
||||
});
|
22
test/modelCustomization/testNamingStrategy.ts
Normal file
22
test/modelCustomization/testNamingStrategy.ts
Normal 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`;
|
||||
}
|
@ -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 =
|
||||
|
@ -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");
|
||||
|
@ -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"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user