Merge branch 'master' into pr/dustinsmith1024/256

This commit is contained in:
Kononnable 2020-03-28 13:26:43 +01:00
commit a908f7c2e0
23 changed files with 1538 additions and 1540 deletions

View File

@ -20,7 +20,7 @@ module.exports = {
"@typescript-eslint/prefer-interface": ["off"],
"import/no-extraneous-dependencies": [
"error",
{ devDependencies: ["**/*.test.ts"] }
{ devDependencies: ["test/**/*.ts"] }
],
"@typescript-eslint/no-floating-promises": ["error"],
"no-use-before-define": ["error", "nofunc"],

View File

@ -4,11 +4,13 @@ node_js:
- 12
- 10
cache: npm
sudo: required
branches:
except:
- no-engines
services:
- docker
env:
matrix:
jobs:
- POSTGRES_Skip=0 POSTGRES_Host=localhost POSTGRES_Port=5432 POSTGRES_Username=test
POSTGRES_Password=test POSTGRES_Database=test POSTGRES_SSL=0 MYSQL_Skip=0
MYSQL_Host=localhost MYSQL_Port=3306 MYSQL_Username=root MYSQL_Password=admin
@ -45,7 +47,5 @@ script:
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=
- docker-compose logs
- docker ps

View File

@ -1,5 +1,13 @@
# Changelog
## 0.4.1
* Fixed model generation on oracle database (#252)
* New option to define line ending character (#241)
* Fixed model generation for two table with same name, different schema on mssql (#246)
* Proper naming strategy typings (#249)
* Release version without installed db drivers
## 0.4.0
### BREAKING CHANGES

View File

@ -15,6 +15,8 @@ Supported db engines:
## Installation
### Versions
Typeorm-model-generator comes with preinstalled driver for each supported db(except for oracle). However if you want to use it as a dev-dependency you may want to install your db driver manually to reduce dependency footprint, reduce time spent in the CI. In such case you can use version without preinstalled db drivers - `npm i typeorm-model-generator@no-engines`.
### Global module
To install module globally simply type `npm i -g typeorm-model-generator` in your console.
### Npx way

2036
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "typeorm-model-generator",
"version": "0.4.0",
"version": "0.4.1",
"description": "Generates models for TypeORM from existing databases.",
"bin": "bin/typeorm-model-generator",
"main": "./dist/src/library.js",
@ -27,44 +27,42 @@
"homepage": "https://github.com/Kononnable/typeorm-model-generator#readme",
"dependencies": {
"change-case": "^4.1.1",
"fs-extra": "^8.1.0",
"handlebars": "^4.5.3",
"inquirer": "^7.0.3",
"mssql": "^6.0.1",
"mysql": "^2.17.1",
"pg": "^7.17.0",
"fs-extra": "^9.0.0",
"handlebars": "^4.7.3",
"inquirer": "^7.1.0",
"mssql": "^6.2.0",
"mysql": "^2.18.1",
"pg": "^7.18.2",
"pluralize": "^8.0.0",
"prettier": "^1.19.1",
"prettier": "^2.0.2",
"reflect-metadata": "^0.1.13",
"sqlite3": "^4.1.1",
"typeorm": "^0.2.22",
"typescript": "^3.7.4",
"yargs": "^15.1.0",
"yn": "^3.1.1"
"typeorm": "^0.2.24",
"yargs": "^15.3.1"
},
"devDependencies": {
"@types/array.prototype.flatmap": "^1.2.0",
"@types/chai": "^4.2.7",
"@types/array.prototype.flatmap": "^1.2.1",
"@types/chai": "^4.2.11",
"@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/eslint": "^6.1.8",
"@types/fs-extra": "^8.1.0",
"@types/handlebars": "^4.1.0",
"@types/inquirer": "^6.5.0",
"@types/mocha": "^5.2.7",
"@types/mocha": "^7.0.2",
"@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/mysql": "^2.15.9",
"@types/node": "^13.9.3",
"@types/oracledb": "^4.2.0",
"@types/pg": "^7.14.3",
"@types/pluralize": "0.0.29",
"@types/prettier": "^1.19.0",
"@types/sinon": "^7.5.1",
"@types/prettier": "^1.19.1",
"@types/sinon": "^7.5.2",
"@types/sqlite3": "^3.1.6",
"@types/yargs": "^13.0.4",
"@typescript-eslint/eslint-plugin": "^2.15.0",
"@typescript-eslint/parser": "^2.15.0",
"@typescript-eslint/typescript-estree": "^2.15.0",
"@types/yargs": "^15.0.4",
"@typescript-eslint/eslint-plugin": "^2.25.0",
"@typescript-eslint/parser": "^2.25.0",
"@typescript-eslint/typescript-estree": "^2.25.0",
"array.prototype.flatmap": "^1.2.3",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
@ -72,18 +70,20 @@
"codecov": "^3.6.1",
"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-import": "^2.19.1",
"husky": "^4.0.1",
"lint-staged": "^9.5.0",
"mocha": "^7.0.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-import": "^2.20.1",
"husky": "^4.2.3",
"lint-staged": "^10.0.9",
"mocha": "^7.1.1",
"ncp": "^2.0.0",
"nyc": "^15.0.0",
"rimraf": "^3.0.0",
"sinon": "^7.5.0",
"sinon-chai": "^3.4.0",
"ts-node": "^8.5.4"
"rimraf": "^3.0.2",
"sinon": "^9.0.1",
"sinon-chai": "^3.5.0",
"ts-node": "^8.8.1",
"typescript": "^3.8.3",
"yn": "^4.0.0"
},
"husky": {
"hooks": {

View File

@ -29,7 +29,7 @@ export function getDefaultConnectionOptions(): IConnectionOptions {
databaseType: undefined as any,
schemaName: "",
ssl: false,
skipTables: []
skipTables: [],
};
return connectionOptions;
}

View File

@ -27,7 +27,7 @@ export default interface IGenerationOptions {
export const eolConverter = {
LF: "\n",
CRLF: "\r\n"
CRLF: "\r\n",
};
export function getDefaultGenerationOptions(): IGenerationOptions {
@ -48,7 +48,7 @@ export function getDefaultGenerationOptions(): IGenerationOptions {
strictMode: "none",
skipSchema: false,
indexFile: false,
exportType: "named"
exportType: "named",
};
return generationOptions;
}

View File

@ -15,7 +15,7 @@ export default function modelCustomizationPhase(
columnName: NamingStrategy.columnName,
entityName: NamingStrategy.entityName,
relationIdName: NamingStrategy.relationIdName,
relationName: NamingStrategy.relationName
relationName: NamingStrategy.relationName,
};
if (
generationOptions.customNamingStrategyPath &&
@ -87,19 +87,19 @@ export default function modelCustomizationPhase(
function removeIndicesGeneratedByTypeorm(dbModel: Entity[]): Entity[] {
// TODO: Support typeorm CustomNamingStrategy
const namingStrategy = new DefaultNamingStrategy();
dbModel.forEach(entity => {
dbModel.forEach((entity) => {
entity.indices = entity.indices.filter(
v =>
(v) =>
!(
v.name.startsWith(`sqlite_autoindex_`) ||
(v.primary && v.name === "PRIMARY")
)
);
const primaryColumns = entity.columns
.filter(v => v.primary)
.map(v => v.tscName);
.filter((v) => v.primary)
.map((v) => v.tscName);
entity.indices = entity.indices.filter(
v =>
(v) =>
!(
v.primary &&
v.name ===
@ -110,9 +110,9 @@ function removeIndicesGeneratedByTypeorm(dbModel: Entity[]): Entity[] {
)
);
entity.relations
.filter(v => v.joinColumnOptions)
.forEach(rel => {
const columnNames = rel.joinColumnOptions!.map(v => v.name);
.filter((v) => v.joinColumnOptions)
.forEach((rel) => {
const columnNames = rel.joinColumnOptions!.map((v) => v.name);
const idxName = namingStrategy.relationConstraintName(
entity.tscName,
columnNames
@ -122,20 +122,20 @@ function removeIndicesGeneratedByTypeorm(dbModel: Entity[]): Entity[] {
columnNames
);
entity.indices = entity.indices.filter(
v => v.name !== idxName && v.name !== fkName
(v) => v.name !== idxName && v.name !== fkName
);
});
});
return dbModel;
}
function removeColumnsInRelation(dbModel: Entity[]): Entity[] {
dbModel.forEach(entity => {
dbModel.forEach((entity) => {
entity.columns = entity.columns.filter(
col =>
(col) =>
!col.isUsedInRelationAsOwner ||
col.isUsedInRelationAsReferenced ||
entity.indices.some(idx =>
idx.columns.some(v => v === col.tscName)
entity.indices.some((idx) =>
idx.columns.some((v) => v === col.tscName)
) ||
col.primary
);
@ -149,8 +149,8 @@ function removeColumnDefaultProperties(
if (!defaultValues) {
return dbModel;
}
dbModel.forEach(entity => {
entity.columns.forEach(column => {
dbModel.forEach((entity) => {
entity.columns.forEach((column) => {
const defVal = defaultValues[column.tscType];
if (defVal) {
if (
@ -185,11 +185,11 @@ function removeColumnDefaultProperties(
}
function findFileImports(dbModel: Entity[]) {
dbModel.forEach(entity => {
entity.relations.forEach(relation => {
dbModel.forEach((entity) => {
entity.relations.forEach((relation) => {
if (
relation.relatedTable !== entity.tscName &&
!entity.fileImports.some(v => v === relation.relatedTable)
!entity.fileImports.some((v) => v === relation.relatedTable)
) {
entity.fileImports.push(relation.relatedTable);
}
@ -203,8 +203,8 @@ function addImportsAndGenerationOptions(
generationOptions: IGenerationOptions
): Entity[] {
dbModel = findFileImports(dbModel);
dbModel.forEach(entity => {
entity.relations.forEach(relation => {
dbModel.forEach((entity) => {
entity.relations.forEach((relation) => {
if (generationOptions.lazy) {
if (!relation.relationOptions) {
relation.relationOptions = {};
@ -237,11 +237,11 @@ function applyNamingStrategy(
return retVal;
function changeRelationIdNames(model: Entity[]): Entity[] {
model.forEach(entity => {
entity.relationIds.forEach(relationId => {
model.forEach((entity) => {
entity.relationIds.forEach((relationId) => {
const oldName = relationId.fieldName;
const relation = entity.relations.find(
v => v.fieldName === relationId.relationField
(v) => v.fieldName === relationId.relationField
)!;
let newName = namingStrategy.relationIdName(
relationId,
@ -253,8 +253,8 @@ function applyNamingStrategy(
entity,
oldName
);
entity.indices.forEach(index => {
index.columns = index.columns.map(column2 =>
entity.indices.forEach((index) => {
index.columns = index.columns.map((column2) =>
column2 === oldName ? newName : column2
);
});
@ -266,8 +266,8 @@ function applyNamingStrategy(
}
function changeRelationNames(model: Entity[]): Entity[] {
model.forEach(entity => {
entity.relations.forEach(relation => {
model.forEach((entity) => {
entity.relations.forEach((relation) => {
const oldName = relation.fieldName;
let newName = namingStrategy.relationName(relation, entity);
newName = TomgUtils.findNameForNewField(
@ -277,15 +277,15 @@ function applyNamingStrategy(
);
const relatedEntity = model.find(
v => v.tscName === relation.relatedTable
(v) => v.tscName === relation.relatedTable
)!;
const relation2 = relatedEntity.relations.find(
v => v.fieldName === relation.relatedField
(v) => v.fieldName === relation.relatedField
)!;
entity.relationIds
.filter(v => v.relationField === oldName)
.forEach(v => {
.filter((v) => v.relationField === oldName)
.forEach((v) => {
v.relationField = newName;
});
@ -293,8 +293,8 @@ function applyNamingStrategy(
relation2.relatedField = newName;
if (relation.relationOptions) {
entity.indices.forEach(ind => {
ind.columns.map(column2 =>
entity.indices.forEach((ind) => {
ind.columns.map((column2) =>
column2 === oldName ? newName : column2
);
});
@ -305,8 +305,8 @@ function applyNamingStrategy(
}
function changeColumnNames(model: Entity[]): Entity[] {
model.forEach(entity => {
entity.columns.forEach(column => {
model.forEach((entity) => {
entity.columns.forEach((column) => {
const oldName = column.tscName;
let newName = namingStrategy.columnName(column.tscName, column);
newName = TomgUtils.findNameForNewField(
@ -314,8 +314,8 @@ function applyNamingStrategy(
entity,
oldName
);
entity.indices.forEach(index => {
index.columns = index.columns.map(column2 =>
entity.indices.forEach((index) => {
index.columns = index.columns.map((column2) =>
column2 === oldName ? newName : column2
);
});
@ -326,10 +326,10 @@ function applyNamingStrategy(
return model;
}
function changeEntityNames(entities: Entity[]): Entity[] {
entities.forEach(entity => {
entities.forEach((entity) => {
const newName = namingStrategy.entityName(entity.tscName, entity);
entities.forEach(entity2 => {
entity2.relations.forEach(relation => {
entities.forEach((entity2) => {
entity2.relations.forEach((relation) => {
if (relation.relatedTable === entity.tscName) {
relation.relatedTable = newName;
}

View File

@ -9,6 +9,11 @@ import IGenerationOptions, { eolConverter } from "./IGenerationOptions";
import { Entity } from "./models/Entity";
import { Relation } from "./models/Relation";
const prettierOptions: Prettier.Options = {
parser: "typescript",
endOfLine: "auto",
};
export default function modelGenerationPhase(
connectionOptions: IConnectionOptions,
generationOptions: IGenerationOptions,
@ -47,9 +52,9 @@ function generateModels(
);
const entityTemplate = fs.readFileSync(entityTemplatePath, "UTF-8");
const entityCompliedTemplate = Handlebars.compile(entityTemplate, {
noEscape: true
noEscape: true,
});
databaseModel.forEach(element => {
databaseModel.forEach((element) => {
let casedFileName = "";
switch (generationOptions.convertCaseFile) {
case "camel":
@ -80,12 +85,20 @@ function generateModels(
)
: rendered
);
const formatted = Prettier.format(withImportStatements, {
parser: "typescript"
});
let formatted = "";
try {
formatted = Prettier.format(withImportStatements, prettierOptions);
} catch (error) {
console.error(
"There were some problems with model generation for table: ",
element.sqlName
);
console.error(error);
formatted = withImportStatements;
}
fs.writeFileSync(resultFilePath, formatted, {
encoding: "UTF-8",
flag: "w"
flag: "w",
});
});
}
@ -98,12 +111,10 @@ function createIndexFile(
const templatePath = path.resolve(__dirname, "templates", "index.mst");
const template = fs.readFileSync(templatePath, "UTF-8");
const compliedTemplate = Handlebars.compile(template, {
noEscape: true
noEscape: true,
});
const rendered = compliedTemplate({ entities: databaseModel });
const formatted = Prettier.format(rendered, {
parser: "typescript"
});
const formatted = Prettier.format(rendered, prettierOptions);
let fileName = "index";
switch (generationOptions.convertCaseFile) {
case "camel":
@ -120,7 +131,7 @@ function createIndexFile(
const resultFilePath = path.resolve(entitiesPath, `${fileName}.ts`);
fs.writeFileSync(resultFilePath, formatted, {
encoding: "UTF-8",
flag: "w"
flag: "w",
});
}
@ -132,7 +143,7 @@ function removeUnusedImports(rendered: string) {
.split(",");
const restOfEntityDefinition = rendered.substring(closeBracketIndex);
const distinctImports = imports.filter(
v =>
(v) =>
restOfEntityDefinition.indexOf(`@${v}(`) !== -1 ||
(v === "BaseEntity" && restOfEntityDefinition.indexOf(v) !== -1)
);
@ -142,12 +153,12 @@ function removeUnusedImports(rendered: string) {
}
function createHandlebarsHelpers(generationOptions: IGenerationOptions): void {
Handlebars.registerHelper("json", context => {
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 => {
Handlebars.registerHelper("toEntityName", (str) => {
let retStr = "";
switch (generationOptions.convertCaseEntity) {
case "camel":
@ -164,7 +175,7 @@ function createHandlebarsHelpers(generationOptions: IGenerationOptions): void {
}
return retStr;
});
Handlebars.registerHelper("toFileName", str => {
Handlebars.registerHelper("toFileName", (str) => {
let retStr = "";
switch (generationOptions.convertCaseFile) {
case "camel":
@ -189,7 +200,7 @@ function createHandlebarsHelpers(generationOptions: IGenerationOptions): void {
? `${generationOptions.propertyVisibility} `
: ""
);
Handlebars.registerHelper("toPropertyName", str => {
Handlebars.registerHelper("toPropertyName", (str) => {
let retStr = "";
switch (generationOptions.convertCaseProperty) {
case "camel":
@ -240,7 +251,7 @@ function createHandlebarsHelpers(generationOptions: IGenerationOptions): void {
lt: (v1, v2) => v1 < v2,
lte: (v1, v2) => v1 <= v2,
ne: (v1, v2) => v1 !== v2,
or: (v1, v2) => v1 || v2
or: (v1, v2) => v1 || v2,
});
}
@ -248,14 +259,14 @@ 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
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"
flag: "w",
});
}
function createTypeOrmConfig(
@ -265,13 +276,13 @@ function createTypeOrmConfig(
const templatePath = path.resolve(__dirname, "templates", "ormconfig.mst");
const template = fs.readFileSync(templatePath, "UTF-8");
const compliedTemplate = Handlebars.compile(template, {
noEscape: true
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"
flag: "w",
});
}

View File

@ -33,17 +33,17 @@ export function findNameForNewField(
let fieldName = _fieldName;
const validNameCondition = () =>
(entity.columns.every(
v =>
(v) =>
changeCase.camelCase(v.tscName) !==
changeCase.camelCase(fieldName)
) &&
entity.relations.every(
v =>
(v) =>
changeCase.camelCase(v.fieldName) !==
changeCase.camelCase(fieldName)
) &&
entity.relationIds.every(
v =>
(v) =>
changeCase.camelCase(v.fieldName) !==
changeCase.camelCase(fieldName)
)) ||

View File

@ -1,7 +1,7 @@
import {
WithLengthColumnType,
WithPrecisionColumnType,
WithWidthColumnType
WithWidthColumnType,
} from "typeorm/driver/types/ColumnTypes";
import { JoinColumnOptions, RelationOptions } from "typeorm";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
@ -27,7 +27,7 @@ export default abstract class AbstractDriver {
"smallint",
"mediumint",
"int",
"bigint"
"bigint",
];
public ColumnTypesWithPrecision: WithPrecisionColumnType[] = [
@ -48,7 +48,7 @@ export default abstract class AbstractDriver {
"timestamp",
"timestamp without time zone",
"timestamp with time zone",
"timestamp with local time zone"
"timestamp with local time zone",
];
public ColumnTypesWithLength: WithLengthColumnType[] = [
@ -64,7 +64,7 @@ export default abstract class AbstractDriver {
"nvarchar2",
"raw",
"binary",
"varbinary"
"varbinary",
];
public abstract GetAllTablesQuery: (
@ -82,42 +82,43 @@ export default abstract class AbstractDriver {
public static FindManyToManyRelations(dbModel: Entity[]) {
let retVal = dbModel;
const manyToManyEntities = retVal.filter(
entity =>
(entity) =>
entity.relations.length === 2 &&
entity.relations.every(
v => v.joinColumnOptions && v.relationType !== "ManyToMany"
(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.filter((c) => c.primary).length &&
entity.columns
.map(v => v.tscName)
.map((v) => v.tscName)
.filter(
v =>
(v) =>
!entity.relations[0]
.joinColumnOptions!.map(x => x.name)
.some(jc => jc === v) &&
.joinColumnOptions!.map((x) => x.name)
.some((jc) => jc === v) &&
!entity.relations[1]
.joinColumnOptions!.map(x => x.name)
.some(jc => jc === v)
.joinColumnOptions!.map((x) => x.name)
.some((jc) => jc === v)
).length === 0
);
manyToManyEntities.forEach(junctionEntity => {
manyToManyEntities.forEach((junctionEntity) => {
const firstEntity = dbModel.find(
v => v.tscName === junctionEntity.relations[0].relatedTable
(v) => v.tscName === junctionEntity.relations[0].relatedTable
)!;
const secondEntity = dbModel.find(
v => v.tscName === junctionEntity.relations[1].relatedTable
(v) => v.tscName === junctionEntity.relations[1].relatedTable
)!;
const firstRelation = firstEntity.relations.find(
v => v.relatedTable === junctionEntity.tscName
(v) => v.relatedTable === junctionEntity.tscName
)!;
const secondRelation = secondEntity.relations.find(
v => v.relatedTable === junctionEntity.tscName
(v) => v.relatedTable === junctionEntity.tscName
)!;
firstRelation.relationType = "ManyToMany";
@ -143,7 +144,7 @@ export default abstract class AbstractDriver {
return {
referencedColumnName: v.referencedColumnName,
name: junctionEntity.relations[0]
.joinColumnOptions![i].name
.joinColumnOptions![i].name,
};
}
),
@ -152,10 +153,10 @@ export default abstract class AbstractDriver {
return {
referencedColumnName: v.referencedColumnName,
name: junctionEntity.relations[1]
.joinColumnOptions![i].name
.joinColumnOptions![i].name,
};
}
)
),
};
if (junctionEntity.database) {
firstRelation.joinTableOptions.database =
@ -169,7 +170,7 @@ export default abstract class AbstractDriver {
secondRelation.relationOptions = undefined;
firstRelation.joinColumnOptions = undefined;
secondRelation.joinColumnOptions = undefined;
retVal = retVal.filter(ent => {
retVal = retVal.filter((ent) => {
return ent.tscName !== junctionEntity.tscName;
});
});
@ -225,7 +226,7 @@ export default abstract class AbstractDriver {
tableNames
);
const ret: Entity[] = [] as Entity[];
response.forEach(val => {
response.forEach((val) => {
ret.push({
columns: [],
indices: [],
@ -235,7 +236,7 @@ export default abstract class AbstractDriver {
tscName: val.TABLE_NAME,
database: dbNames.includes(",") ? val.DB_NAME : "",
schema: val.TABLE_SCHEMA,
fileImports: []
fileImports: [],
});
});
return ret;
@ -246,9 +247,9 @@ export default abstract class AbstractDriver {
entities: Entity[],
generationOptions: IGenerationOptions
) {
relationsTemp.forEach(relationTmp => {
relationsTemp.forEach((relationTmp) => {
const ownerEntity = entities.find(
entity => entity.tscName === relationTmp.ownerTable.tscName
(entity) => entity.tscName === relationTmp.ownerTable.tscName
);
if (!ownerEntity) {
TomgUtils.LogError(
@ -257,7 +258,7 @@ export default abstract class AbstractDriver {
return;
}
const referencedEntity = entities.find(
entity => entity.tscName === relationTmp.relatedTable.tscName
(entity) => entity.tscName === relationTmp.relatedTable.tscName
);
if (!referencedEntity) {
TomgUtils.LogError(
@ -274,7 +275,7 @@ export default abstract class AbstractDriver {
relationColumnIndex++
) {
const ownerColumn = ownerEntity.columns.find(
column =>
(column) =>
column.tscName ===
relationTmp.ownerColumns[relationColumnIndex]
);
@ -285,7 +286,7 @@ export default abstract class AbstractDriver {
return;
}
const relatedColumn = referencedEntity.columns.find(
column =>
(column) =>
column.tscName ===
relationTmp.relatedColumns[relationColumnIndex]
);
@ -301,19 +302,19 @@ export default abstract class AbstractDriver {
let isOneToMany: boolean;
isOneToMany = false;
const index = ownerEntity.indices.find(
ind =>
(ind) =>
ind.options.unique &&
ind.columns.length === ownerColumns.length &&
ownerColumns.every(ownerColumn =>
ind.columns.some(col => col === ownerColumn.tscName)
ownerColumns.every((ownerColumn) =>
ind.columns.some((col) => col === ownerColumn.tscName)
)
);
isOneToMany = !index;
ownerColumns.forEach(column => {
ownerColumns.forEach((column) => {
column.isUsedInRelationAsOwner = true;
});
relatedColumns.forEach(column => {
relatedColumns.forEach((column) => {
column.isUsedInRelationAsReferenced = true;
});
let fieldName = "";
@ -331,7 +332,7 @@ export default abstract class AbstractDriver {
const relationOptions: RelationOptions = {
onDelete: relationTmp.onDelete,
onUpdate: relationTmp.onUpdate
onUpdate: relationTmp.onUpdate,
};
const ownerRelation: Relation = {
@ -343,12 +344,12 @@ export default abstract class AbstractDriver {
joinColumnOptions: relationTmp.ownerColumns.map((v, idx) => {
const retVal: Required<JoinColumnOptions> = {
name: v,
referencedColumnName: relationTmp.relatedColumns[idx]
referencedColumnName: relationTmp.relatedColumns[idx],
};
return retVal;
}),
relatedTable: relationTmp.relatedTable.tscName,
relationType: isOneToMany ? "ManyToOne" : "OneToOne"
relationType: isOneToMany ? "ManyToOne" : "OneToOne",
};
if (JSON.stringify(relationOptions) !== "{}") {
ownerRelation.relationOptions = relationOptions;
@ -357,7 +358,7 @@ export default abstract class AbstractDriver {
fieldName: ownerRelation.relatedField,
relatedField: ownerRelation.fieldName,
relatedTable: relationTmp.ownerTable.tscName,
relationType: isOneToMany ? "OneToMany" : "OneToOne"
relationType: isOneToMany ? "OneToMany" : "OneToOne",
};
ownerEntity.relations.push(ownerRelation);
@ -383,7 +384,7 @@ export default abstract class AbstractDriver {
ownerEntity.relationIds.push({
fieldName: relationIdFieldName,
fieldType,
relationField: ownerRelation.fieldName
relationField: ownerRelation.fieldName,
});
// TODO: RelationId on ManyToMany
}
@ -411,17 +412,17 @@ export default abstract class AbstractDriver {
): Promise<Entity[]>;
public static FindPrimaryColumnsFromIndexes(dbModel: Entity[]) {
dbModel.forEach(entity => {
const primaryIndex = entity.indices.find(v => v.primary);
dbModel.forEach((entity) => {
const primaryIndex = entity.indices.find((v) => v.primary);
entity.columns
.filter(
col =>
(col) =>
primaryIndex &&
primaryIndex.columns.some(
cIndex => cIndex === col.tscName
(cIndex) => cIndex === col.tscName
)
)
.forEach(col => {
.forEach((col) => {
// eslint-disable-next-line no-param-reassign
col.primary = true;
if (
@ -432,7 +433,7 @@ export default abstract class AbstractDriver {
}
});
if (
!entity.columns.some(v => {
!entity.columns.some((v) => {
return !!v.primary;
})
) {

View File

@ -1,4 +1,4 @@
import * as MSSQL from "mssql";
import type * as MSSQL from "mssql";
import { ConnectionOptions } from "typeorm";
import * as TypeormDriver from "typeorm/driver/sqlserver/SqlServerDriver";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
@ -13,7 +13,7 @@ import { RelationInternal } from "../models/RelationInternal";
export default class MssqlDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.SqlServerDriver({
options: { replication: undefined } as ConnectionOptions
options: { replication: undefined } as ConnectionOptions,
} as any).dataTypeDefaults;
public readonly standardPort = 1433;
@ -22,14 +22,27 @@ export default class MssqlDriver extends AbstractDriver {
public readonly standardUser = "sa";
private MSSQL: typeof MSSQL;
private Connection: MSSQL.ConnectionPool;
public constructor() {
super();
try {
// eslint-disable-next-line import/no-extraneous-dependencies, global-require, import/no-unresolved
this.MSSQL = require("mssql");
} catch (error) {
TomgUtils.LogError("", false, error);
throw error;
}
}
public GetAllTablesQuery = async (
schema: string,
dbNames: string,
tableNames: string[]
) => {
const request = new MSSQL.Request(this.Connection);
const request = new this.MSSQL.Request(this.Connection);
const tableCondition =
tableNames.length > 0
? ` AND NOT TABLE_NAME IN ('${tableNames.join("','")}')`
@ -54,7 +67,7 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
schema: string,
dbNames: string
): Promise<Entity[]> {
const request = new MSSQL.Request(this.Connection);
const request = new this.MSSQL.Request(this.Connection);
const response: {
TABLE_NAME: string;
COLUMN_NAME: string;
@ -86,18 +99,18 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
)})
order by ordinal_position`)
).recordset;
entities.forEach(ent => {
entities.forEach((ent) => {
response
.filter(filterVal => {
.filter((filterVal) => {
return (
filterVal.TABLE_NAME === ent.tscName &&
filterVal.TABLE_SCHEMA === ent.schema
);
})
.forEach(resp => {
.forEach((resp) => {
const tscName = resp.COLUMN_NAME;
const options: Column["options"] = {
name: resp.COLUMN_NAME
name: resp.COLUMN_NAME,
};
if (resp.IS_NULLABLE === "YES") options.nullable = true;
if (resp.IsUnique === 1) options.unique = true;
@ -217,7 +230,7 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
if (
this.ColumnTypesWithPrecision.some(
v => v === columnType
(v) => v === columnType
)
) {
if (resp.NUMERIC_PRECISION !== null) {
@ -228,7 +241,7 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
}
}
if (
this.ColumnTypesWithLength.some(v => v === columnType)
this.ColumnTypesWithLength.some((v) => v === columnType)
) {
options.length =
resp.CHARACTER_MAXIMUM_LENGTH > 0
@ -241,7 +254,7 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
default: defaultValue,
options,
tscName,
tscType
tscType,
});
});
});
@ -253,7 +266,7 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
schema: string,
dbNames: string
): Promise<Entity[]> {
const request = new MSSQL.Request(this.Connection);
const request = new this.MSSQL.Request(this.Connection);
const response: {
TableName: string;
TableSchema: string;
@ -263,8 +276,10 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
is_primary_key: boolean;
}[] = [];
await Promise.all(
dbNames.split(",").map(async dbName => {
await this.UseDB(dbName);
dbNames.split(",").map(async (dbName) => {
if (dbNames.length > 1) {
await this.UseDB(dbName);
}
const resp: {
TableName: string;
TableSchema: string;
@ -299,25 +314,25 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
})
);
entities.forEach(ent => {
entities.forEach((ent) => {
const entityIndices = response.filter(
filterVal =>
(filterVal) =>
filterVal.TableName === ent.tscName &&
filterVal.TableSchema === ent.schema
);
const indexNames = new Set(entityIndices.map(v => v.IndexName));
indexNames.forEach(indexName => {
const indexNames = new Set(entityIndices.map((v) => v.IndexName));
indexNames.forEach((indexName) => {
const records = entityIndices.filter(
v => v.IndexName === indexName
(v) => v.IndexName === indexName
);
const indexInfo: Index = {
columns: [],
options: {},
name: records[0].IndexName
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 => {
records.forEach((record) => {
indexInfo.columns.push(record.ColumnName);
});
ent.indices.push(indexInfo);
@ -333,7 +348,7 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
const request = new MSSQL.Request(this.Connection);
const request = new this.MSSQL.Request(this.Connection);
const response: {
TableWithForeignKey: string;
FK_PartNo: number;
@ -345,8 +360,10 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
objectId: number;
}[] = [];
await Promise.all(
dbNames.split(",").map(async dbName => {
await this.UseDB(dbName);
dbNames.split(",").map(async (dbName) => {
if (dbNames.length > 1) {
await this.UseDB(dbName);
}
const resp: {
TableWithForeignKey: string;
FK_PartNo: number;
@ -389,15 +406,15 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
})
);
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.objectId));
const relationKeys = new Set(response.map((v) => v.objectId));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.objectId === relationId);
relationKeys.forEach((relationId) => {
const rows = response.filter((v) => v.objectId === relationId);
const ownerTable = entities.find(
v => v.sqlName === rows[0].TableWithForeignKey
(v) => v.sqlName === rows[0].TableWithForeignKey
);
const relatedTable = entities.find(
v => v.sqlName === rows[0].TableReferenced
(v) => v.sqlName === rows[0].TableReferenced
);
if (!ownerTable || !relatedTable) {
TomgUtils.LogError(
@ -410,7 +427,7 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
relatedTable,
};
switch (rows[0].onDelete) {
case "NO_ACTION":
@ -432,7 +449,7 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
internal.onUpdate = rows[0].onUpdate;
break;
}
rows.forEach(row => {
rows.forEach((row) => {
internal.ownerColumns.push(row.ForeignKeyColumn);
internal.relatedColumns.push(row.ForeignKeyColumnReferenced);
});
@ -459,17 +476,17 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
database: databaseName,
options: {
appName: "typeorm-model-generator",
encrypt: connectionOptons.ssl
encrypt: connectionOptons.ssl,
},
password: connectionOptons.password,
port: connectionOptons.port,
requestTimeout: 60 * 60 * 1000,
server: connectionOptons.host,
user: connectionOptons.user
user: connectionOptons.user,
};
const promise = new Promise<boolean>((resolve, reject) => {
this.Connection = new MSSQL.ConnectionPool(config, err => {
this.Connection = new this.MSSQL.ConnectionPool(config, (err) => {
if (!err) {
resolve(true);
} else {
@ -487,22 +504,22 @@ WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG
}
public async CreateDB(dbName: string) {
const request = new MSSQL.Request(this.Connection);
const request = new this.MSSQL.Request(this.Connection);
await request.query(`CREATE DATABASE ${dbName}; `);
}
public async UseDB(dbName: string) {
const request = new MSSQL.Request(this.Connection);
const request = new this.MSSQL.Request(this.Connection);
await request.query(`USE ${dbName}; `);
}
public async DropDB(dbName: string) {
const request = new MSSQL.Request(this.Connection);
const request = new this.MSSQL.Request(this.Connection);
await request.query(`DROP DATABASE ${dbName}; `);
}
public async CheckIfDBExists(dbName: string): Promise<boolean> {
const request = new MSSQL.Request(this.Connection);
const request = new this.MSSQL.Request(this.Connection);
const resp = await request.query(
`SELECT name FROM master.sys.databases WHERE name = N'${dbName}' `
);

View File

@ -1,4 +1,4 @@
import * as MYSQL from "mysql";
import type * as MYSQL from "mysql";
import { ConnectionOptions } from "typeorm";
import * as TypeormDriver from "typeorm/driver/mysql/MysqlDriver";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
@ -13,7 +13,7 @@ import IGenerationOptions from "../IGenerationOptions";
export default class MysqlDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.MysqlDriver({
options: { replication: undefined } as ConnectionOptions
options: { replication: undefined } as ConnectionOptions,
} as any).dataTypeDefaults;
public readonly EngineName: string = "MySQL";
@ -24,8 +24,21 @@ export default class MysqlDriver extends AbstractDriver {
public readonly standardSchema = "";
private MYSQL: typeof MYSQL;
private Connection: MYSQL.Connection;
public constructor() {
super();
try {
// eslint-disable-next-line import/no-extraneous-dependencies, global-require, import/no-unresolved
this.MYSQL = require("mysql");
} catch (error) {
TomgUtils.LogError("", false, error);
throw error;
}
}
public GetAllTablesQuery = async (
schema: string,
dbNames: string,
@ -65,21 +78,22 @@ export default class MysqlDriver extends AbstractDriver {
IsIdentity: number;
COLUMN_TYPE: string;
COLUMN_KEY: string;
COLUMN_COMMENT: string;
}>(`SELECT TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,IS_NULLABLE,
DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE,
CASE WHEN EXTRA like '%auto_increment%' THEN 1 ELSE 0 END IsIdentity, COLUMN_TYPE, COLUMN_KEY
CASE WHEN EXTRA like '%auto_increment%' THEN 1 ELSE 0 END IsIdentity, COLUMN_TYPE, COLUMN_KEY, COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA IN (${MysqlDriver.escapeCommaSeparatedList(
dbNames
)})
order by ordinal_position`);
entities.forEach(ent => {
entities.forEach((ent) => {
response
.filter(filterVal => filterVal.TABLE_NAME === ent.tscName)
.forEach(resp => {
.filter((filterVal) => filterVal.TABLE_NAME === ent.tscName)
.forEach((resp) => {
const tscName = resp.COLUMN_NAME;
let tscType = "";
const options: Column["options"] = {
name: resp.COLUMN_NAME
name: resp.COLUMN_NAME,
};
const generated = resp.IsIdentity === 1 ? true : undefined;
const defaultValue = MysqlDriver.ReturnDefaultValueFunction(
@ -89,6 +103,8 @@ export default class MysqlDriver extends AbstractDriver {
let columnType = resp.DATA_TYPE;
if (resp.IS_NULLABLE === "YES") options.nullable = true;
if (resp.COLUMN_KEY === "UNI") options.unique = true;
if (resp.COLUMN_COMMENT)
options.comment = resp.COLUMN_COMMENT;
if (resp.COLUMN_TYPE.endsWith(" unsigned"))
options.unsigned = true;
switch (resp.DATA_TYPE) {
@ -245,7 +261,7 @@ export default class MysqlDriver extends AbstractDriver {
}
if (
this.ColumnTypesWithPrecision.some(
v => v === columnType
(v) => v === columnType
)
) {
if (resp.NUMERIC_PRECISION !== null) {
@ -256,7 +272,7 @@ export default class MysqlDriver extends AbstractDriver {
}
}
if (
this.ColumnTypesWithLength.some(v => v === columnType)
this.ColumnTypesWithLength.some((v) => v === columnType)
) {
options.length =
resp.CHARACTER_MAXIMUM_LENGTH > 0
@ -265,7 +281,7 @@ export default class MysqlDriver extends AbstractDriver {
}
if (
this.ColumnTypesWithWidth.some(
v => v === columnType && tscType !== "boolean"
(v) => v === columnType && tscType !== "boolean"
)
) {
options.width =
@ -280,7 +296,7 @@ export default class MysqlDriver extends AbstractDriver {
default: defaultValue,
options,
tscName,
tscType
tscType,
});
});
});
@ -304,25 +320,25 @@ export default class MysqlDriver extends AbstractDriver {
WHERE table_schema IN (${MysqlDriver.escapeCommaSeparatedList(
dbNames
)})`);
entities.forEach(ent => {
entities.forEach((ent) => {
const entityIndices = response.filter(
filterVal => filterVal.TableName === ent.tscName
(filterVal) => filterVal.TableName === ent.tscName
);
const indexNames = new Set(entityIndices.map(v => v.IndexName));
indexNames.forEach(indexName => {
const indexNames = new Set(entityIndices.map((v) => v.IndexName));
indexNames.forEach((indexName) => {
const records = entityIndices.filter(
v => v.IndexName === indexName
(v) => v.IndexName === indexName
);
const indexInfo: Index = {
name: indexName,
columns: [],
options: {}
options: {},
};
if (records[0].is_primary_key === 1) indexInfo.primary = true;
if (records[0].is_unique === 1) indexInfo.options.unique = true;
records.forEach(record => {
records.forEach((record) => {
indexInfo.columns.push(record.ColumnName);
});
ent.indices.push(indexInfo);
@ -366,15 +382,15 @@ export default class MysqlDriver extends AbstractDriver {
AND CU.REFERENCED_TABLE_NAME IS NOT NULL;
`);
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.object_id));
const relationKeys = new Set(response.map((v) => v.object_id));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.object_id === relationId);
relationKeys.forEach((relationId) => {
const rows = response.filter((v) => v.object_id === relationId);
const ownerTable = entities.find(
v => v.sqlName === rows[0].TableWithForeignKey
(v) => v.sqlName === rows[0].TableWithForeignKey
);
const relatedTable = entities.find(
v => v.sqlName === rows[0].TableReferenced
(v) => v.sqlName === rows[0].TableReferenced
);
if (!ownerTable || !relatedTable) {
@ -388,7 +404,7 @@ export default class MysqlDriver extends AbstractDriver {
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
relatedTable,
};
if (rows[0].onDelete !== "NO_ACTION") {
internal.onDelete = rows[0].onDelete;
@ -396,7 +412,7 @@ export default class MysqlDriver extends AbstractDriver {
if (rows[0].onUpdate !== "NO_ACTION") {
internal.onUpdate = rows[0].onUpdate;
}
rows.forEach(row => {
rows.forEach((row) => {
internal.ownerColumns.push(row.ForeignKeyColumn);
internal.relatedColumns.push(row.ForeignKeyColumnReferenced);
});
@ -413,12 +429,12 @@ export default class MysqlDriver extends AbstractDriver {
public async DisconnectFromServer() {
const promise = new Promise<boolean>((resolve, reject) => {
this.Connection.end(err => {
this.Connection.end((err) => {
if (!err) {
resolve(true);
} else {
TomgUtils.LogError(
`Error disconnecting to ${this.EngineName} Server.`,
`Error disconnecting from ${this.EngineName} Server.`,
false,
err.message
);
@ -441,10 +457,10 @@ export default class MysqlDriver extends AbstractDriver {
password: connectionOptons.password,
port: connectionOptons.port,
ssl: {
rejectUnauthorized: false
rejectUnauthorized: false,
},
timeout: 60 * 60 * 1000,
user: connectionOptons.user
user: connectionOptons.user,
};
} else {
config = {
@ -453,14 +469,14 @@ export default class MysqlDriver extends AbstractDriver {
password: connectionOptons.password,
port: connectionOptons.port,
timeout: 60 * 60 * 1000,
user: connectionOptons.user
user: connectionOptons.user,
};
}
const promise = new Promise<boolean>((resolve, reject) => {
this.Connection = MYSQL.createConnection(config);
this.Connection = this.MYSQL.createConnection(config);
this.Connection.connect(err => {
this.Connection.connect((err) => {
if (!err) {
resolve(true);
} else {
@ -499,10 +515,10 @@ export default class MysqlDriver extends AbstractDriver {
const query = this.Connection.query(sql);
const stream = query.stream({});
const promise = new Promise<boolean>((resolve, reject) => {
stream.on("data", chunk => {
stream.on("data", (chunk) => {
ret.push((chunk as unknown) as T);
});
stream.on("error", err => reject(err));
stream.on("error", (err) => reject(err));
stream.on("end", () => resolve(true));
});
await promise;

View File

@ -1,3 +1,5 @@
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
import type * as Oracle from "oracledb";
import * as TypeormDriver from "typeorm/driver/oracle/OracleDriver";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import * as TomgUtils from "../Utils";
@ -11,7 +13,7 @@ import { RelationInternal } from "../models/RelationInternal";
export default class OracleDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.OracleDriver({
options: undefined
options: undefined,
} as any).dataTypeDefaults;
public readonly standardPort = 1521;
@ -20,16 +22,16 @@ export default class OracleDriver extends AbstractDriver {
public readonly standardSchema = "";
public Oracle: any;
private Oracle: typeof Oracle;
private Connection: any /* Oracle.IConnection */;
private Connection: Oracle.Connection;
public constructor() {
super();
try {
// eslint-disable-next-line import/no-extraneous-dependencies, global-require, import/no-unresolved
this.Oracle = require("oracledb");
this.Oracle.outFormat = this.Oracle.OBJECT;
this.Oracle.outFormat = (this.Oracle as any).OBJECT;
} catch (error) {
TomgUtils.LogError("", false, error);
throw error;
@ -45,12 +47,12 @@ export default class OracleDriver extends AbstractDriver {
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(
const response = (
await this.Connection.execute<{
TABLE_SCHEMA: string;
TABLE_NAME: string;
DB_NAME: string;
}>(
`SELECT NULL AS TABLE_SCHEMA, TABLE_NAME, NULL AS DB_NAME FROM all_tables WHERE owner = (select user from dual) ${tableCondition}`
)
).rows!;
@ -58,32 +60,31 @@ export default class OracleDriver extends AbstractDriver {
};
public async GetCoulmnsFromEntity(entities: Entity[]): Promise<Entity[]> {
const response: {
TABLE_NAME: string;
COLUMN_NAME: string;
DATA_DEFAULT: string;
NULLABLE: string;
DATA_TYPE: string;
DATA_LENGTH: number;
DATA_PRECISION: number;
DATA_SCALE: number;
IDENTITY_COLUMN: string; // doesn't exist in old oracle versions (#195)
IS_UNIQUE: number;
}[] = (
await this.Connection
.execute(`SELECT utc.*, (select count(*) from USER_CONS_COLUMNS ucc
const response = (
await this.Connection.execute<{
TABLE_NAME: string;
COLUMN_NAME: string;
DATA_DEFAULT: string;
NULLABLE: string;
DATA_TYPE: string;
DATA_LENGTH: number;
DATA_PRECISION: number;
DATA_SCALE: number;
IDENTITY_COLUMN: string; // doesn't exist in old oracle versions (#195)
IS_UNIQUE: number;
}>(`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!;
entities.forEach(ent => {
entities.forEach((ent) => {
response
.filter(filterVal => filterVal.TABLE_NAME === ent.tscName)
.forEach(resp => {
.filter((filterVal) => filterVal.TABLE_NAME === ent.tscName)
.forEach((resp) => {
const tscName = resp.COLUMN_NAME;
const options: Column["options"] = {
name: resp.COLUMN_NAME
name: resp.COLUMN_NAME,
};
if (resp.NULLABLE === "Y") options.nullable = true;
if (resp.IS_UNIQUE > 0) options.unique = true;
@ -195,7 +196,7 @@ export default class OracleDriver extends AbstractDriver {
}
if (
this.ColumnTypesWithPrecision.some(
v => v === columnType
(v) => v === columnType
)
) {
if (resp.DATA_PRECISION !== null) {
@ -206,7 +207,7 @@ export default class OracleDriver extends AbstractDriver {
}
}
if (
this.ColumnTypesWithLength.some(v => v === columnType)
this.ColumnTypesWithLength.some((v) => v === columnType)
) {
options.length =
resp.DATA_LENGTH > 0 ? resp.DATA_LENGTH : undefined;
@ -218,7 +219,7 @@ export default class OracleDriver extends AbstractDriver {
default: defaultValue,
options,
tscName,
tscType
tscType,
});
});
});
@ -226,39 +227,38 @@ export default class OracleDriver extends AbstractDriver {
}
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
const response = (
await this.Connection.execute<{
COLUMN_NAME: string;
TABLE_NAME: string;
INDEX_NAME: string;
UNIQUENESS: string;
ISPRIMARYKEY: number;
}>(`SELECT ind.TABLE_NAME, ind.INDEX_NAME, col.COLUMN_NAME,ind.UNIQUENESS, CASE WHEN uc.CONSTRAINT_NAME IS NULL THEN 0 ELSE 1 END ISPRIMARYKEY
FROM USER_INDEXES ind
JOIN USER_IND_COLUMNS col ON ind.INDEX_NAME=col.INDEX_NAME
LEFT JOIN USER_CONSTRAINTS uc ON uc.INDEX_NAME = ind.INDEX_NAME
ORDER BY col.INDEX_NAME ASC ,col.COLUMN_POSITION ASC`)
).rows!;
entities.forEach(ent => {
entities.forEach((ent) => {
const entityIndices = response.filter(
filterVal => filterVal.TABLE_NAME === ent.tscName
(filterVal) => filterVal.TABLE_NAME === ent.tscName
);
const indexNames = new Set(entityIndices.map(v => v.INDEX_NAME));
indexNames.forEach(indexName => {
const indexNames = new Set(entityIndices.map((v) => v.INDEX_NAME));
indexNames.forEach((indexName) => {
const records = entityIndices.filter(
v => v.INDEX_NAME === indexName
(v) => v.INDEX_NAME === indexName
);
const indexInfo: Index = {
columns: [],
options: {},
name: records[0].INDEX_NAME
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 => {
records.forEach((record) => {
indexInfo.columns.push(record.COLUMN_NAME);
});
ent.indices.push(indexInfo);
@ -274,17 +274,16 @@ export default class OracleDriver extends AbstractDriver {
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
const response: {
OWNER_TABLE_NAME: string;
OWNER_POSITION: string;
OWNER_COLUMN_NAME: string;
CHILD_TABLE_NAME: string;
CHILD_COLUMN_NAME: string;
DELETE_RULE: "RESTRICT" | "CASCADE" | "SET NULL" | "NO ACTION";
CONSTRAINT_NAME: string;
}[] = (
await this.Connection
.execute(`select owner.TABLE_NAME OWNER_TABLE_NAME,ownCol.POSITION OWNER_POSITION,ownCol.COLUMN_NAME OWNER_COLUMN_NAME,
const response = (
await this.Connection.execute<{
OWNER_TABLE_NAME: string;
OWNER_POSITION: string;
OWNER_COLUMN_NAME: string;
CHILD_TABLE_NAME: string;
CHILD_COLUMN_NAME: string;
DELETE_RULE: "RESTRICT" | "CASCADE" | "SET NULL" | "NO ACTION";
CONSTRAINT_NAME: string;
}>(`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
@ -296,15 +295,17 @@ export default class OracleDriver extends AbstractDriver {
).rows!;
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.CONSTRAINT_NAME));
const relationKeys = new Set(response.map((v) => v.CONSTRAINT_NAME));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.CONSTRAINT_NAME === relationId);
relationKeys.forEach((relationId) => {
const rows = response.filter(
(v) => v.CONSTRAINT_NAME === relationId
);
const ownerTable = entities.find(
v => v.sqlName === rows[0].OWNER_TABLE_NAME
(v) => v.sqlName === rows[0].OWNER_TABLE_NAME
);
const relatedTable = entities.find(
v => v.sqlName === rows[0].CHILD_TABLE_NAME
(v) => v.sqlName === rows[0].CHILD_TABLE_NAME
);
if (!ownerTable || !relatedTable) {
@ -318,12 +319,12 @@ export default class OracleDriver extends AbstractDriver {
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
relatedTable,
};
if (rows[0].DELETE_RULE !== "NO ACTION") {
internal.onDelete = rows[0].DELETE_RULE;
}
rows.forEach(row => {
rows.forEach((row) => {
internal.ownerColumns.push(row.OWNER_COLUMN_NAME);
internal.relatedColumns.push(row.CHILD_COLUMN_NAME);
});
@ -345,21 +346,21 @@ export default class OracleDriver extends AbstractDriver {
}
public async ConnectToServer(connectionOptions: IConnectionOptions) {
let config: any;
let config: Oracle.ConnectionAttributes;
if (connectionOptions.user === String(process.env.ORACLE_UsernameSys)) {
config /* Oracle.IConnectionAttributes */ = {
config = {
connectString: `${connectionOptions.host}:${connectionOptions.port}/${connectionOptions.databaseName}`,
externalAuth: connectionOptions.ssl,
password: connectionOptions.password,
privilege: this.Oracle.SYSDBA,
user: connectionOptions.user
user: connectionOptions.user,
};
} else {
config /* Oracle.IConnectionAttributes */ = {
config = {
connectString: `${connectionOptions.host}:${connectionOptions.port}/${connectionOptions.databaseName}`,
externalAuth: connectionOptions.ssl,
password: connectionOptions.password,
user: connectionOptions.user
user: connectionOptions.user,
};
}
const promise = new Promise<boolean>((resolve, reject) => {
@ -400,16 +401,16 @@ export default class OracleDriver extends AbstractDriver {
}
public async CheckIfDBExists(dbName: string): Promise<boolean> {
const x = await this.Connection.execute(
const { rows } = await this.Connection.execute<any>(
`select count(*) as CNT from dba_users where username='${dbName.toUpperCase()}'`
);
return x.rows[0][0] > 0 || x.rows[0].CNT;
return rows![0][0] > 0 || rows![0].CNT;
}
private static ReturnDefaultValueFunction(
defVal: string | null
): string | undefined {
let defaultVal = defVal;
let defaultVal = defVal?.trim();
if (!defaultVal) {
return undefined;
}

View File

@ -1,4 +1,4 @@
import * as PG from "pg";
import type * as PG from "pg";
import { ConnectionOptions } from "typeorm";
import * as TypeormDriver from "typeorm/driver/postgres/PostgresDriver";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
@ -13,7 +13,7 @@ import { RelationInternal } from "../models/RelationInternal";
export default class PostgresDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.PostgresDriver({
options: { replication: undefined } as ConnectionOptions
options: { replication: undefined } as ConnectionOptions,
} as any).dataTypeDefaults;
public readonly standardPort = 5432;
@ -22,8 +22,21 @@ export default class PostgresDriver extends AbstractDriver {
public readonly standardSchema = "public";
private PG: typeof PG;
private Connection: PG.Client;
public constructor() {
super();
try {
// eslint-disable-next-line import/no-extraneous-dependencies, global-require, import/no-unresolved
this.PG = require("pg");
} catch (error) {
TomgUtils.LogError("", false, error);
throw error;
}
}
public GetAllTablesQuery = async (
schema: string,
dbNames: string,
@ -87,13 +100,13 @@ export default class PostgresDriver extends AbstractDriver {
where table_schema in (${schema})
order by ordinal_position`)
).rows;
entities.forEach(ent => {
entities.forEach((ent) => {
response
.filter(filterVal => filterVal.table_name === ent.tscName)
.forEach(resp => {
.filter((filterVal) => filterVal.table_name === ent.tscName)
.forEach((resp) => {
const tscName = resp.column_name;
const options: Column["options"] = {
name: resp.column_name
name: resp.column_name,
};
if (resp.is_nullable === "YES") options.nullable = true;
if (resp.isunique === "1") options.unique = true;
@ -136,13 +149,13 @@ export default class PostgresDriver extends AbstractDriver {
if (options.array) {
tscType = tscType
.split("|")
.map(x => `${x.replace("|", "").trim()}[]`)
.map((x) => `${x.replace("|", "").trim()}[]`)
.join(" | ");
}
if (
this.ColumnTypesWithPrecision.some(
v => v === columnType
(v) => v === columnType
)
) {
if (resp.numeric_precision !== null) {
@ -153,14 +166,16 @@ export default class PostgresDriver extends AbstractDriver {
}
}
if (
this.ColumnTypesWithLength.some(v => v === columnType)
this.ColumnTypesWithLength.some((v) => v === columnType)
) {
options.length =
resp.character_maximum_length > 0
? resp.character_maximum_length
: undefined;
}
if (this.ColumnTypesWithWidth.some(v => v === columnType)) {
if (
this.ColumnTypesWithWidth.some((v) => v === columnType)
) {
options.width =
resp.character_maximum_length > 0
? resp.character_maximum_length
@ -173,7 +188,7 @@ export default class PostgresDriver extends AbstractDriver {
default: defaultValue,
options,
tscName,
tscType
tscType,
});
});
});
@ -194,7 +209,7 @@ export default class PostgresDriver extends AbstractDriver {
tsType: "",
sqlType: dataType,
isArray: false,
enumValues: []
enumValues: [],
};
switch (dataType) {
case "int2":
@ -449,23 +464,23 @@ export default class PostgresDriver extends AbstractDriver {
AND i.oid<>0
ORDER BY c.relname,f.attname;`)
).rows;
entities.forEach(ent => {
entities.forEach((ent) => {
const entityIndices = response.filter(
filterVal => filterVal.tablename === ent.tscName
(filterVal) => filterVal.tablename === ent.tscName
);
const indexNames = new Set(entityIndices.map(v => v.indexname));
indexNames.forEach(indexName => {
const indexNames = new Set(entityIndices.map((v) => v.indexname));
indexNames.forEach((indexName) => {
const records = entityIndices.filter(
v => v.indexname === indexName
(v) => v.indexname === indexName
);
const indexInfo: Index = {
columns: [],
options: {},
name: records[0].indexname
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 => {
records.forEach((record) => {
indexInfo.columns.push(record.columnname);
});
ent.indices.push(indexInfo);
@ -535,15 +550,15 @@ export default class PostgresDriver extends AbstractDriver {
).rows;
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.object_id));
const relationKeys = new Set(response.map((v) => v.object_id));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.object_id === relationId);
relationKeys.forEach((relationId) => {
const rows = response.filter((v) => v.object_id === relationId);
const ownerTable = entities.find(
v => v.sqlName === rows[0].tablewithforeignkey
(v) => v.sqlName === rows[0].tablewithforeignkey
);
const relatedTable = entities.find(
v => v.sqlName === rows[0].tablereferenced
(v) => v.sqlName === rows[0].tablereferenced
);
if (!ownerTable || !relatedTable) {
TomgUtils.LogError(
@ -556,7 +571,7 @@ export default class PostgresDriver extends AbstractDriver {
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
relatedTable,
};
if (rows[0].ondelete !== "NO ACTION") {
internal.onDelete = rows[0].ondelete;
@ -564,7 +579,7 @@ export default class PostgresDriver extends AbstractDriver {
if (rows[0].onupdate !== "NO ACTION") {
internal.onUpdate = rows[0].onupdate;
}
rows.forEach(row => {
rows.forEach((row) => {
internal.ownerColumns.push(row.foreignkeycolumn);
internal.relatedColumns.push(row.foreignkeycolumnreferenced);
});
@ -582,12 +597,12 @@ export default class PostgresDriver extends AbstractDriver {
public async DisconnectFromServer() {
if (this.Connection) {
const promise = new Promise<boolean>((resolve, reject) => {
this.Connection.end(err => {
this.Connection.end((err) => {
if (!err) {
resolve(true);
} else {
TomgUtils.LogError(
"Error connecting to Postgres Server.",
"Error disconnecting from to Postgres Server.",
false,
err.message
);
@ -600,7 +615,7 @@ export default class PostgresDriver extends AbstractDriver {
}
public async ConnectToServer(connectionOptons: IConnectionOptions) {
this.Connection = new PG.Client({
this.Connection = new this.PG.Client({
database: connectionOptons.databaseName,
host: connectionOptons.host,
password: connectionOptons.password,
@ -608,11 +623,11 @@ export default class PostgresDriver extends AbstractDriver {
ssl: connectionOptons.ssl,
// eslint-disable-next-line @typescript-eslint/camelcase
statement_timeout: 60 * 60 * 1000,
user: connectionOptons.user
user: connectionOptons.user,
});
const promise = new Promise<boolean>((resolve, reject) => {
this.Connection.connect(err => {
this.Connection.connect((err) => {
if (!err) {
resolve(true);
} else {
@ -658,7 +673,7 @@ export default class PostgresDriver extends AbstractDriver {
}
defaultValue = defaultValue.replace(/'::[\w ]*/, "'");
if (["json", "jsonb"].some(x => x === dataType)) {
if (["json", "jsonb"].some((x) => x === dataType)) {
return `${defaultValue.slice(1, defaultValue.length - 1)}`;
}
return `() => "${defaultValue}"`;

View File

@ -1,7 +1,7 @@
import { ConnectionOptions } from "typeorm";
import * as TypeormDriver from "typeorm/driver/sqlite/SqliteDriver";
import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults";
import * as sqliteLib from "sqlite3";
import type * as sqliteLib from "sqlite3";
import * as TomgUtils from "../Utils";
import AbstractDriver from "./AbstractDriver";
import IConnectionOptions from "../IConnectionOptions";
@ -13,7 +13,7 @@ import { RelationInternal } from "../models/RelationInternal";
export default class SqliteDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.SqliteDriver({
options: { database: "true" } as ConnectionOptions
options: { database: "true" } as ConnectionOptions,
} as any).dataTypeDefaults;
public readonly standardPort = 0;
@ -22,14 +22,28 @@ export default class SqliteDriver extends AbstractDriver {
public readonly standardSchema = "";
public sqlite = sqliteLib.verbose();
private sqliteLib: typeof sqliteLib;
public db: sqliteLib.Database;
private sqlite: sqliteLib.sqlite3;
public tablesWithGeneratedPrimaryKey: string[] = new Array<string>();
private db: sqliteLib.Database;
private tablesWithGeneratedPrimaryKey: string[] = new Array<string>();
public GetAllTablesQuery: any;
public constructor() {
super();
try {
// eslint-disable-next-line import/no-extraneous-dependencies, global-require, import/no-unresolved
this.sqliteLib = require("sqlite3");
this.sqlite = this.sqliteLib.verbose();
} catch (error) {
TomgUtils.LogError("", false, error);
throw error;
}
}
public async GetAllTables(
schema: string,
dbNames: string,
@ -43,7 +57,7 @@ export default class SqliteDriver extends AbstractDriver {
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_%' ${tableCondition}`
);
rows.forEach(val => {
rows.forEach((val) => {
if (val.sql.includes("AUTOINCREMENT")) {
this.tablesWithGeneratedPrimaryKey.push(val.tbl_name);
}
@ -54,7 +68,7 @@ export default class SqliteDriver extends AbstractDriver {
relationIds: [],
sqlName: val.tbl_name,
tscName: val.tbl_name,
fileImports: []
fileImports: [],
});
});
return ret;
@ -62,7 +76,7 @@ export default class SqliteDriver extends AbstractDriver {
public async GetCoulmnsFromEntity(entities: Entity[]): Promise<Entity[]> {
await Promise.all(
entities.map(async ent => {
entities.map(async (ent) => {
const response = await this.ExecQuery<{
cid: number;
name: string;
@ -71,7 +85,7 @@ export default class SqliteDriver extends AbstractDriver {
dflt_value: string;
pk: number;
}>(`PRAGMA table_info('${ent.tscName}');`);
response.forEach(resp => {
response.forEach((resp) => {
const tscName = resp.name;
let tscType = "";
const options: Column["options"] = { name: resp.name };
@ -181,7 +195,7 @@ export default class SqliteDriver extends AbstractDriver {
const sqlOptions = resp.type.match(/\([0-9 ,]+\)/g);
if (
this.ColumnTypesWithPrecision.some(
v => v === columnType
(v) => v === columnType
) &&
sqlOptions
) {
@ -200,7 +214,7 @@ export default class SqliteDriver extends AbstractDriver {
}
if (
this.ColumnTypesWithLength.some(
v => v === columnType
(v) => v === columnType
) &&
sqlOptions
) {
@ -214,7 +228,7 @@ export default class SqliteDriver extends AbstractDriver {
}
if (
this.ColumnTypesWithWidth.some(
v => v === columnType && tscType !== "boolean"
(v) => v === columnType && tscType !== "boolean"
) &&
sqlOptions
) {
@ -234,7 +248,7 @@ export default class SqliteDriver extends AbstractDriver {
default: defaultValue,
options,
tscName,
tscType
tscType,
});
});
})
@ -245,7 +259,7 @@ export default class SqliteDriver extends AbstractDriver {
public async GetIndexesFromEntity(entities: Entity[]): Promise<Entity[]> {
await Promise.all(
entities.map(async ent => {
entities.map(async (ent) => {
const response = await this.ExecQuery<{
seq: number;
name: string;
@ -254,7 +268,7 @@ export default class SqliteDriver extends AbstractDriver {
partial: number;
}>(`PRAGMA index_list('${ent.tscName}');`);
await Promise.all(
response.map(async resp => {
response.map(async (resp) => {
const indexColumnsResponse = await this.ExecQuery<{
seqno: number;
cid: number;
@ -264,11 +278,11 @@ export default class SqliteDriver extends AbstractDriver {
const indexInfo: Index = {
name: resp.name,
columns: [],
options: {}
options: {},
};
if (resp.unique === 1) indexInfo.options.unique = true;
indexColumnsResponse.forEach(record => {
indexColumnsResponse.forEach((record) => {
indexInfo.columns.push(record.name);
});
if (
@ -276,8 +290,10 @@ export default class SqliteDriver extends AbstractDriver {
indexInfo.options.unique
) {
ent.columns
.filter(v => v.tscName === indexInfo.columns[0])
.forEach(v => {
.filter(
(v) => v.tscName === indexInfo.columns[0]
)
.forEach((v) => {
// eslint-disable-next-line no-param-reassign
v.options.unique = true;
});
@ -299,7 +315,7 @@ export default class SqliteDriver extends AbstractDriver {
): Promise<Entity[]> {
let retVal = entities;
await Promise.all(
retVal.map(async entity => {
retVal.map(async (entity) => {
const response = await this.ExecQuery<{
id: number;
seq: number;
@ -320,15 +336,15 @@ export default class SqliteDriver extends AbstractDriver {
}>(`PRAGMA foreign_key_list('${entity.tscName}');`);
const relationsTemp: RelationInternal[] = [] as RelationInternal[];
const relationKeys = new Set(response.map(v => v.id));
const relationKeys = new Set(response.map((v) => v.id));
relationKeys.forEach(relationId => {
const rows = response.filter(v => v.id === relationId);
relationKeys.forEach((relationId) => {
const rows = response.filter((v) => v.id === relationId);
const ownerTable = entities.find(
v => v.sqlName === entity.tscName
(v) => v.sqlName === entity.tscName
);
const relatedTable = entities.find(
v => v.sqlName === rows[0].table
(v) => v.sqlName === rows[0].table
);
if (!ownerTable || !relatedTable) {
TomgUtils.LogError(
@ -341,7 +357,7 @@ export default class SqliteDriver extends AbstractDriver {
ownerColumns: [],
relatedColumns: [],
ownerTable,
relatedTable
relatedTable,
};
if (rows[0].on_delete !== "NO ACTION") {
internal.onDelete = rows[0].on_delete;
@ -349,7 +365,7 @@ export default class SqliteDriver extends AbstractDriver {
if (rows[0].on_update !== "NO ACTION") {
internal.onUpdate = rows[0].on_update;
}
rows.forEach(row => {
rows.forEach((row) => {
internal.ownerColumns.push(row.from);
internal.relatedColumns.push(row.to);
});
@ -381,7 +397,7 @@ export default class SqliteDriver extends AbstractDriver {
public async UseDB(dbName: string) {
const promise = new Promise<boolean>((resolve, reject) => {
this.db = new this.sqlite.Database(dbName, err => {
this.db = new this.sqlite.Database(dbName, (err) => {
if (err) {
TomgUtils.LogError(
"Error connecting to SQLite database.",

View File

@ -2,15 +2,16 @@ import * as Yargs from "yargs";
import { createDriver, createModelFromDatabase } from "./Engine";
import * as TomgUtils from "./Utils";
import IConnectionOptions, {
getDefaultConnectionOptions
getDefaultConnectionOptions,
} from "./IConnectionOptions";
import IGenerationOptions, {
getDefaultGenerationOptions
getDefaultGenerationOptions,
} from "./IGenerationOptions";
import fs = require("fs-extra");
import inquirer = require("inquirer");
import path = require("path");
// eslint-disable-next-line @typescript-eslint/no-floating-promises
CliLogic();
@ -61,7 +62,7 @@ function makeDefaultConfigs() {
const connectionOptions = getDefaultConnectionOptions();
return {
generationOptions,
connectionOptions
connectionOptions,
};
}
function readTOMLConfig(
@ -81,7 +82,7 @@ function readTOMLConfig(
let hasUnknownProperties = false;
if (loadedConnectionOptions) {
Object.keys(loadedConnectionOptions).forEach(key => {
Object.keys(loadedConnectionOptions).forEach((key) => {
if (
Object.prototype.hasOwnProperty.call(
options.connectionOptions,
@ -96,7 +97,7 @@ function readTOMLConfig(
});
}
if (loadedGenerationOptions) {
Object.keys(loadedGenerationOptions).forEach(key => {
Object.keys(loadedGenerationOptions).forEach((key) => {
if (
Object.prototype.hasOwnProperty.call(
options.generationOptions,
@ -122,7 +123,7 @@ function readTOMLConfig(
return {
options,
fullConfigFile
fullConfigFile,
};
}
function checkYargsParameters(options: options): options {
@ -133,7 +134,7 @@ function checkYargsParameters(options: options): options {
alias: "host",
string: true,
default: options.connectionOptions.host,
describe: "IP address/Hostname for database server"
describe: "IP address/Hostname for database server",
},
d: {
alias: "database",
@ -141,25 +142,25 @@ function checkYargsParameters(options: options): options {
demand: true,
default: options.connectionOptions.databaseName,
describe:
"Database name(or path for sqlite). You can pass multiple values separated by comma."
"Database name(or path for sqlite). You can pass multiple values separated by comma.",
},
u: {
alias: "user",
string: true,
default: options.connectionOptions.user,
describe: "Username for database server"
describe: "Username for database server",
},
x: {
alias: "pass",
string: true,
default: options.connectionOptions.password,
describe: "Password for database server"
describe: "Password for database server",
},
p: {
number: true,
alias: "port",
default: options.connectionOptions.port,
describe: "Port number for database server"
describe: "Port number for database server",
},
e: {
alias: "engine",
@ -169,121 +170,121 @@ function checkYargsParameters(options: options): options {
"mysql",
"mariadb",
"oracle",
"sqlite"
"sqlite",
],
demand: true,
default: options.connectionOptions.databaseType,
describe: "Database engine"
describe: "Database engine",
},
o: {
alias: "output",
default: options.generationOptions.resultsPath,
describe: "Where to place generated models"
describe: "Where to place generated models",
},
s: {
alias: "schema",
string: true,
default: options.connectionOptions.schemaName,
describe:
"Schema name to create model from. Only for mssql and postgres. You can pass multiple values separated by comma eg. -s scheme1,scheme2,scheme3"
"Schema name to create model from. Only for mssql and postgres. You can pass multiple values separated by comma eg. -s scheme1,scheme2,scheme3",
},
ssl: {
boolean: true,
default: options.connectionOptions.ssl
default: options.connectionOptions.ssl,
},
noConfig: {
boolean: true,
default: options.generationOptions.noConfigs,
describe: `Doesn't create tsconfig.json and ormconfig.json`
describe: `Doesn't create tsconfig.json and ormconfig.json`,
},
cf: {
alias: "case-file",
choices: ["pascal", "param", "camel", "none"],
default: options.generationOptions.convertCaseFile,
describe: "Convert file names to specified case"
describe: "Convert file names to specified case",
},
ce: {
alias: "case-entity",
choices: ["pascal", "camel", "none"],
default: options.generationOptions.convertCaseEntity,
describe: "Convert class names to specified case"
describe: "Convert class names to specified case",
},
cp: {
alias: "case-property",
choices: ["pascal", "camel", "none"],
default: options.generationOptions.convertCaseProperty,
describe: "Convert property names to specified case"
describe: "Convert property names to specified case",
},
eol: {
choices: ["LF", "CRLF"],
default: options.generationOptions.convertEol,
describe: "Force EOL to be LF or CRLF"
describe: "Force EOL to be LF or CRLF",
},
pv: {
alias: "property-visibility",
choices: ["public", "protected", "private", "none"],
default: options.generationOptions.propertyVisibility,
describe:
"Defines which visibility should have the generated property"
"Defines which visibility should have the generated property",
},
lazy: {
boolean: true,
default: options.generationOptions.lazy,
describe: "Generate lazy relations"
describe: "Generate lazy relations",
},
a: {
alias: "active-record",
boolean: true,
default: options.generationOptions.activeRecord,
describe: "Use ActiveRecord syntax for generated models"
describe: "Use ActiveRecord syntax for generated models",
},
namingStrategy: {
describe: "Use custom naming strategy",
default: options.generationOptions.customNamingStrategyPath,
string: true
string: true,
},
relationIds: {
boolean: true,
default: options.generationOptions.relationIds,
describe: "Generate RelationId fields"
describe: "Generate RelationId fields",
},
skipSchema: {
boolean: true,
default: options.generationOptions.skipSchema,
describe: "Omits schema identifier in generated entities"
describe: "Omits schema identifier in generated entities",
},
generateConstructor: {
boolean: true,
default: options.generationOptions.generateConstructor,
describe: "Generate constructor allowing partial initialization"
describe: "Generate constructor allowing partial initialization",
},
disablePluralization: {
boolean: true,
default: !options.generationOptions.pluralizeNames,
describe:
"Disable pluralization of OneToMany, ManyToMany relation names"
"Disable pluralization of OneToMany, ManyToMany relation names",
},
skipTables: {
string: true,
default: options.connectionOptions.skipTables.join(","),
describe:
"Skip schema generation for specific tables. You can pass multiple values separated by comma"
"Skip schema generation for specific tables. You can pass multiple values separated by comma",
},
strictMode: {
choices: ["none", "?", "!"],
default: options.generationOptions.strictMode,
describe: "Mark fields as optional(?) or non-null(!)"
describe: "Mark fields as optional(?) or non-null(!)",
},
index: {
boolean: true,
default: options.generationOptions.indexFile,
describe: "Generate index file"
describe: "Generate index file",
},
defaultExport: {
boolean: true,
default: options.generationOptions.exportType === "default",
describe: "Generate index file"
}
describe: "Generate index file",
},
});
options.connectionOptions.databaseName = argv.d;
@ -300,7 +301,11 @@ function checkYargsParameters(options: options): options {
: standardSchema;
options.connectionOptions.ssl = argv.ssl;
options.connectionOptions.user = argv.u || standardUser;
options.connectionOptions.skipTables = argv.skipTables.split(",");
let skipTables = argv.skipTables.split(",");
if (skipTables.length === 1 && skipTables[0] === "") {
skipTables = []; // #252
}
options.connectionOptions.skipTables = skipTables;
options.generationOptions.activeRecord = argv.a;
options.generationOptions.generateConstructor = argv.generateConstructor;
options.generationOptions.convertCaseEntity = argv.ce as IGenerationOptions["convertCaseEntity"];
@ -335,13 +340,13 @@ async function useInquirer(options: options): Promise<options> {
"mysql",
"mariadb",
"oracle",
"sqlite"
"sqlite",
],
default: options.connectionOptions.databaseType,
message: "Choose database engine",
name: "engine",
type: "list"
}
type: "list",
},
])
).engine;
const driver = createDriver(options.connectionOptions.databaseType);
@ -356,7 +361,7 @@ async function useInquirer(options: options): Promise<options> {
default: options.connectionOptions.host,
message: "Database address:",
name: "host",
type: "input"
type: "input",
},
{
message: "Database port:",
@ -366,32 +371,32 @@ async function useInquirer(options: options): Promise<options> {
validate(value) {
const valid = !Number.isNaN(parseInt(value, 10));
return valid || "Please enter a valid port number";
}
},
},
{
default: options.connectionOptions.ssl,
message: "Use SSL:",
name: "ssl",
type: "confirm"
type: "confirm",
},
{
message: "Database user name:",
name: "login",
type: "input",
default: options.connectionOptions.user
default: options.connectionOptions.user,
},
{
message: "Database user password:",
name: "password",
type: "password"
type: "password",
},
{
default: options.connectionOptions.databaseName,
message:
"Database name: (You can pass multiple values separated by comma)",
name: "dbName",
type: "input"
}
type: "input",
},
]);
if (
options.connectionOptions.databaseType === "mssql" ||
@ -404,8 +409,8 @@ async function useInquirer(options: options): Promise<options> {
message:
"Database schema: (You can pass multiple values separated by comma)",
name: "schema",
type: "input"
}
type: "input",
},
])
).schema;
}
@ -422,8 +427,8 @@ async function useInquirer(options: options): Promise<options> {
default: options.connectionOptions.databaseName,
message: "Path to database file:",
name: "dbName",
type: "input"
}
type: "input",
},
])
).dbName;
}
@ -438,8 +443,8 @@ async function useInquirer(options: options): Promise<options> {
message: "Generate schema for tables:",
choices: ["All of them", "Ignore specific tables"],
name: "specyficTables",
type: "list"
}
type: "list",
},
])
).specyficTables;
if (ignoreSpecyficTables === "Ignore specific tables") {
@ -447,7 +452,7 @@ async function useInquirer(options: options): Promise<options> {
default: options.connectionOptions.skipTables.join(","),
message: "Table names(separated by comma)",
name: "tableNames",
type: "input"
type: "input",
});
options.connectionOptions.skipTables = tableNames.split(",");
} else {
@ -460,8 +465,8 @@ async function useInquirer(options: options): Promise<options> {
default: options.generationOptions.resultsPath,
message: "Path where generated models should be stored:",
name: "output",
type: "input"
}
type: "input",
},
])
).output;
const { customizeGeneration } = await inquirer.prompt([
@ -469,8 +474,8 @@ async function useInquirer(options: options): Promise<options> {
default: false,
message: "Do you want to customize generated model?",
name: "customizeGeneration",
type: "confirm"
}
type: "confirm",
},
]);
if (customizeGeneration) {
const defaultGenerationOptions = getDefaultGenerationOptions();
@ -481,42 +486,42 @@ async function useInquirer(options: options): Promise<options> {
{
checked: !options.generationOptions.noConfigs,
name: "Generate config files",
value: "config"
value: "config",
},
{
name: "Generate lazy relations",
value: "lazy",
checked: options.generationOptions.lazy
checked: options.generationOptions.lazy,
},
{
name:
"Use ActiveRecord syntax for generated models",
value: "activeRecord",
checked: options.generationOptions.activeRecord
checked: options.generationOptions.activeRecord,
},
{
name: "Use custom naming strategy",
value: "namingStrategy",
checked: !!options.generationOptions
.customNamingStrategyPath
.customNamingStrategyPath,
},
{
name: "Generate RelationId fields",
value: "relationId",
checked: options.generationOptions.relationIds
checked: options.generationOptions.relationIds,
},
{
name:
"Omits schema identifier in generated entities",
value: "skipSchema",
checked: options.generationOptions.skipSchema
checked: options.generationOptions.skipSchema,
},
{
name:
"Generate constructor allowing partial initialization",
value: "constructor",
checked:
options.generationOptions.generateConstructor
options.generationOptions.generateConstructor,
},
{
name: "Use specific naming convention",
@ -528,36 +533,36 @@ async function useInquirer(options: options): Promise<options> {
.convertCaseProperty !==
defaultGenerationOptions.convertCaseProperty ||
options.generationOptions.convertCaseFile !==
defaultGenerationOptions.convertCaseFile
defaultGenerationOptions.convertCaseFile,
},
{
name: "Use specific EOL character",
value: "converteol",
checked: false
checked: false,
},
{
name:
"Pluralize OneToMany, ManyToMany relation names",
value: "pluralize",
checked: options.generationOptions.pluralizeNames
checked: options.generationOptions.pluralizeNames,
},
{
name: "Generate index file",
value: "index",
checked: options.generationOptions.indexFile
checked: options.generationOptions.indexFile,
},
{
name: "Prefer default exports",
value: "defaultExport",
checked:
options.generationOptions.exportType ===
"default"
}
"default",
},
],
message: "Available customizations",
name: "selected",
type: "checkbox"
}
type: "checkbox",
},
])
).selected;
@ -569,8 +574,8 @@ async function useInquirer(options: options): Promise<options> {
"Defines which visibility should have the generated property",
name: "propertyVisibility",
default: options.generationOptions.propertyVisibility,
type: "list"
}
type: "list",
},
])
).propertyVisibility;
@ -581,8 +586,8 @@ async function useInquirer(options: options): Promise<options> {
message: "Mark fields as optional(?) or non-null(!)",
name: "strictMode",
default: options.generationOptions.strictMode,
type: "list"
}
type: "list",
},
])
).strictMode;
@ -627,8 +632,8 @@ async function useInquirer(options: options): Promise<options> {
valid ||
"Please enter a a valid path to custom naming strategy file"
);
}
}
},
},
])
).namingStrategy;
@ -645,22 +650,22 @@ async function useInquirer(options: options): Promise<options> {
default: options.generationOptions.convertCaseFile,
message: "Convert file names to specified case:",
name: "fileCase",
type: "list"
type: "list",
},
{
choices: ["pascal", "camel", "none"],
default: options.generationOptions.convertCaseEntity,
message: "Convert class names to specified case:",
name: "entityCase",
type: "list"
type: "list",
},
{
choices: ["pascal", "camel", "none"],
default: options.generationOptions.convertCaseProperty,
message: "Convert property names to specified case:",
name: "propertyCase",
type: "list"
}
type: "list",
},
]);
options.generationOptions.convertCaseFile =
namingConventions.fileCase;
@ -676,8 +681,8 @@ async function useInquirer(options: options): Promise<options> {
default: options.generationOptions.convertEol,
message: "Force EOL to be:",
name: "eol",
type: "list"
}
type: "list",
},
]);
options.generationOptions.convertEol = eolChoice.eol;
}
@ -687,13 +692,13 @@ async function useInquirer(options: options): Promise<options> {
choices: [
"Yes, only model customization options",
"Yes, with connection details",
"No"
"No",
],
default: "No",
message: "Save configuration to config file?",
name: "saveConfig",
type: "list"
}
type: "list",
},
]);
if (saveConfig === "Yes, with connection details") {
await fs.writeJson(

View File

@ -14,5 +14,5 @@ export {
IConnectionOptions,
IGenerationOptions,
NamingStrategy,
Utils
Utils,
};

View File

@ -21,5 +21,6 @@ export type Column = {
unsigned?: boolean;
enum?: string[];
array?: boolean; // ?
comment?: string;
};
};

View File

@ -0,0 +1,22 @@
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({ comment: "comment" })
body: string;
}

View File

@ -5,7 +5,7 @@ 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 * as yn from "yn";
import { CLIEngine } from "eslint";
import EntityFileToJson from "../utils/EntityFileToJson";
import { createDriver, dataCollectionPhase } from "../../src/Engine";
@ -128,6 +128,11 @@ function runTestForMultipleDrivers(
dbDriver
)
);
case "93":
return dbDrivers.filter(
dbDriver =>
["mysql", "mariadb"].includes(dbDriver) // Only db engines supported by typeorm at the time of writing
);
case "144":
return dbDrivers.filter(dbDriver =>
["mysql", "mariadb"].includes(dbDriver)

View File

@ -29,6 +29,10 @@ class EntityIndex {
public isUnique = false;
}
function removeTrailingComas(input: string) {
return input.replace(/,(?=\s*?[}\]])/g, "");
}
export default class EntityFileToJson {
public static getEntityOptions(trimmedLine: string, ent: EntityJson): void {
const decoratorParameters = trimmedLine.slice(
@ -50,7 +54,7 @@ export default class EntityFileToJson {
badJSON[badJSON.length - 1];
}
ent.entityOptions = JSON.parse(
badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ')
removeTrailingComas(badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '))
);
}
}
@ -90,7 +94,7 @@ export default class EntityFileToJson {
`default: $1`
);
col.columnOptions = JSON.parse(
badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ')
removeTrailingComas(badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '))
);
} else if (
decoratorParameters[0] === '"' &&
@ -102,8 +106,8 @@ export default class EntityFileToJson {
} else {
let badJSON = !primaryGeneratedColumn
? decoratorParameters.substring(
decoratorParameters.indexOf(",") + 1
)
decoratorParameters.indexOf(",") + 1
)
: decoratorParameters;
badJSON = badJSON.trim();
if (badJSON.lastIndexOf(",") === badJSON.length - 3) {
@ -113,7 +117,7 @@ export default class EntityFileToJson {
badJSON[badJSON.length - 1];
}
col.columnOptions = JSON.parse(
badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ')
removeTrailingComas(badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '))
);
}
}
@ -143,9 +147,9 @@ export default class EntityFileToJson {
badJSON[badJSON.length - 1];
}
col.columnOptions = JSON.parse(
badJSON
removeTrailingComas(badJSON
.replace(/(')/g, `"`)
.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ')
.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '))
);
}
}
@ -411,7 +415,7 @@ export default class EntityFileToJson {
if (decoratorParameters.length > 0) {
const column =
retVal.columns[retVal.columns.length - 1];
const options = JSON.parse(decoratorParameters);
const options = JSON.parse(removeTrailingComas(decoratorParameters));
if (Array.isArray(options)) {
column.joinOptions = options as any;
} else {
@ -437,7 +441,7 @@ export default class EntityFileToJson {
if (decoratorParameters.length > 0) {
const column =
retVal.columns[retVal.columns.length - 1];
const options = JSON.parse(decoratorParameters);
const options = JSON.parse(removeTrailingComas(decoratorParameters));
if (
options.inverseJoinColumn &&
!Array.isArray(options.inverseJoinColumn)