postgres implementation

This commit is contained in:
Kononnable 2019-10-11 10:05:21 +02:00
parent 9c546dfdef
commit 6d4a97d39a
3 changed files with 290 additions and 274 deletions

View File

@ -11,6 +11,10 @@ import IndexColumnInfo from "../oldModels/IndexColumnInfo";
import RelationTempInfo from "../oldModels/RelationTempInfo";
import IConnectionOptions from "../IConnectionOptions";
import { Entity } from "../models/Entity";
import { Column } from "../models/Column";
import { Index } from "../models/Index";
import IGenerationOptions from "../IGenerationOptions";
import { RelationInternal } from "../models/RelationInternal";
export default class PostgresDriver extends AbstractDriver {
public defaultValues: DataTypeDefaults = new TypeormDriver.PostgresDriver({
@ -40,125 +44,131 @@ export default class PostgresDriver extends AbstractDriver {
entities: Entity[],
schema: string
): Promise<Entity[]> {
throw new Error();
// TODO: Remove
// const response: {
// table_name: string;
// column_name: string;
// udt_name: string;
// column_default: string;
// is_nullable: string;
// data_type: string;
// character_maximum_length: number;
// numeric_precision: number;
// numeric_scale: number;
// 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;
// entities.forEach(ent => {
// response
// .filter(filterVal => filterVal.table_name === ent.tsEntityName)
// .forEach(resp => {
// const colInfo: ColumnInfo = new ColumnInfo();
// colInfo.tsName = resp.column_name;
// colInfo.options.name = resp.column_name;
// colInfo.options.nullable = resp.is_nullable === "YES";
// colInfo.options.generated = resp.isidentity === "YES";
// colInfo.options.unique = resp.isunique === "1";
// colInfo.options.default = colInfo.options.generated
// ? null
// : PostgresDriver.ReturnDefaultValueFunction(
// resp.column_default
// );
const response: {
table_name: string;
column_name: string;
udt_name: string;
column_default: string;
is_nullable: string;
data_type: string;
character_maximum_length: number;
numeric_precision: number;
numeric_scale: number;
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;
entities.forEach(ent => {
response
.filter(filterVal => filterVal.table_name === ent.tscName)
.forEach(resp => {
const tscName = resp.column_name;
const options: Partial<Column["options"]> = {};
options.name = resp.column_name;
if (resp.is_nullable === "YES") options.nullable = true;
if (resp.isunique === "1") options.unique = true;
// const columnTypes = this.MatchColumnTypes(
// resp.data_type,
// resp.udt_name,
// resp.enumvalues
// );
// if (!columnTypes.sqlType || !columnTypes.tsType) {
// 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}`
// );
// } else {
// TomgUtils.LogError(
// `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
// .split("|")
// .map(x => `${x.replace("|", "").trim()}[]`)
// .join(" | ") as any;
// }
const generated =
resp.isidentity === "YES" ? true : undefined;
const defaultValue = generated
? undefined
: PostgresDriver.ReturnDefaultValueFunction(
resp.column_default
);
// if (
// this.ColumnTypesWithPrecision.some(
// v => v === colInfo.options.type
// )
// ) {
// colInfo.options.precision = resp.numeric_precision;
// colInfo.options.scale = resp.numeric_scale;
// }
// if (
// this.ColumnTypesWithLength.some(
// v => v === colInfo.options.type
// )
// ) {
// colInfo.options.length =
// resp.character_maximum_length > 0
// ? resp.character_maximum_length
// : undefined;
// }
// if (
// this.ColumnTypesWithWidth.some(
// v => v === colInfo.options.type
// )
// ) {
// colInfo.options.width =
// resp.character_maximum_length > 0
// ? resp.character_maximum_length
// : undefined;
// }
// if (colInfo.options.type && colInfo.tsType) {
// ent.Columns.push(colInfo);
// }
// });
// });
// return entities;
const columnTypes = this.MatchColumnTypes(
resp.data_type,
resp.udt_name,
resp.enumvalues
);
if (!columnTypes.sqlType || !columnTypes.tsType) {
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}`
);
} else {
TomgUtils.LogError(
`Unknown column type: ${resp.data_type} table name: ${resp.table_name} column name: ${resp.column_name}`
);
}
return;
}
const columnType = columnTypes.sqlType as any;
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;
}
if (
this.ColumnTypesWithPrecision.some(
v => v === columnType
)
) {
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 === columnType)
) {
options.length =
resp.character_maximum_length > 0
? resp.character_maximum_length
: undefined;
}
if (this.ColumnTypesWithWidth.some(v => v === columnType)) {
options.width =
resp.character_maximum_length > 0
? resp.character_maximum_length
: undefined;
}
if (columnType && tscType) {
ent.columns.push({
generated,
type: columnType,
default: defaultValue,
options: { name: "", ...options }, // TODO: Change
tscName,
tscType
});
}
});
});
return entities;
}
public MatchColumnTypes(
@ -167,7 +177,7 @@ export default class PostgresDriver extends AbstractDriver {
enumValues: string | null
) {
let ret: {
tsType?: ColumnInfo["tsType"];
tsType?: Column["tscType"];
sqlType: string | null;
isArray: boolean;
enumValues: string[];
@ -382,9 +392,7 @@ export default class PostgresDriver extends AbstractDriver {
.split(",")
.join('" | "')}"` as never) as string;
ret.sqlType = "enum";
ret.enumValues = (`"${enumValues
.split(",")
.join('","')}"` as never) as string[];
ret.enumValues = enumValues.split(",");
} else {
ret.tsType = undefined;
ret.sqlType = null;
@ -404,152 +412,162 @@ export default class PostgresDriver extends AbstractDriver {
entities: Entity[],
schema: string
): Promise<Entity[]> {
throw new Error();
// TODO: Remove
// const response: {
// tablename: string;
// indexname: string;
// columnname: string;
// is_unique: number;
// is_primary_key: number;
// }[] = (await this.Connection.query(`SELECT
// c.relname AS tablename,
// i.relname as indexname,
// f.attname AS columnname,
// CASE
// WHEN ix.indisunique = true THEN 1
// ELSE 0
// END AS is_unique,
// CASE
// WHEN ix.indisprimary='true' THEN 1
// ELSE 0
// END AS is_primary_key
// FROM pg_attribute f
// JOIN pg_class c ON c.oid = f.attrelid
// JOIN pg_type t ON t.oid = f.atttypid
// LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
// LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
// LEFT JOIN pg_index AS ix ON f.attnum = ANY(ix.indkey) and c.oid = f.attrelid and c.oid = ix.indrelid
// LEFT JOIN pg_class AS i ON ix.indexrelid = i.oid
// WHERE c.relkind = 'r'::char
// AND n.nspname in (${schema})
// AND f.attnum > 0
// AND i.oid<>0
// 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 response: {
tablename: string;
indexname: string;
columnname: string;
is_unique: number;
is_primary_key: number;
}[] = (await this.Connection.query(`SELECT
c.relname AS tablename,
i.relname as indexname,
f.attname AS columnname,
CASE
WHEN ix.indisunique = true THEN 1
ELSE 0
END AS is_unique,
CASE
WHEN ix.indisprimary='true' THEN 1
ELSE 0
END AS is_primary_key
FROM pg_attribute f
JOIN pg_class c ON c.oid = f.attrelid
JOIN pg_type t ON t.oid = f.atttypid
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_index AS ix ON f.attnum = ANY(ix.indkey) and c.oid = f.attrelid and c.oid = ix.indrelid
LEFT JOIN pg_class AS i ON ix.indexrelid = i.oid
WHERE c.relkind = 'r'::char
AND n.nspname in (${schema})
AND f.attnum > 0
AND i.oid<>0
ORDER BY c.relname,f.attname;`)).rows;
entities.forEach(ent => {
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;
return entities;
}
public async GetRelations(
entities: Entity[],
schema: string
schema: string,
dbNames: string,
generationOptions: IGenerationOptions
): Promise<Entity[]> {
throw new Error();
// TODO: Remove
// const response: {
// tablewithforeignkey: string;
// fk_partno: number;
// foreignkeycolumn: string;
// tablereferenced: string;
// foreignkeycolumnreferenced: string;
// ondelete: "RESTRICT" | "CASCADE" | "SET NULL" | "NO ACTION";
// 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
// con.relname AS tablewithforeignkey,
// att.attnum as fk_partno,
// att2.attname AS foreignkeycolumn,
// cl.relname AS tablereferenced,
// att.attname AS foreignkeycolumnreferenced,
// delete_rule as ondelete,
// update_rule as onupdate,
// concat(con.conname,con.conrelid,con.confrelid) as object_id
// FROM (
// SELECT
// unnest(con1.conkey) AS parent,
// unnest(con1.confkey) AS child,
// con1.confrelid,
// con1.conrelid,
// cl_1.relname,
// con1.conname,
// nspname
// FROM
// pg_class cl_1,
// pg_namespace ns,
// pg_constraint con1
// WHERE
// con1.contype = 'f'::"char"
// AND cl_1.relnamespace = ns.oid
// AND con1.conrelid = cl_1.oid
// and nspname in (${schema})
// ) con,
// pg_attribute att,
// pg_class cl,
// pg_attribute att2,
// information_schema.referential_constraints rc
// WHERE
// att.attrelid = con.confrelid
// AND att.attnum = con.child
// AND cl.oid = con.confrelid
// 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
// );
// 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);
// }
// rels.ownerColumnsNames.push(resp.foreignkeycolumn);
// rels.referencedColumnsNames.push(resp.foreignkeycolumnreferenced);
// });
// const retVal = PostgresDriver.GetRelationsFromRelationTempInfo(
// relationsTemp,
// entities
// );
// return retVal;
const response: {
tablewithforeignkey: string;
fk_partno: number;
foreignkeycolumn: string;
tablereferenced: string;
foreignkeycolumnreferenced: string;
ondelete: "RESTRICT" | "CASCADE" | "SET NULL" | "NO ACTION";
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
con.relname AS tablewithforeignkey,
att.attnum as fk_partno,
att2.attname AS foreignkeycolumn,
cl.relname AS tablereferenced,
att.attname AS foreignkeycolumnreferenced,
delete_rule as ondelete,
update_rule as onupdate,
concat(con.conname,con.conrelid,con.confrelid) as object_id
FROM (
SELECT
unnest(con1.conkey) AS parent,
unnest(con1.confkey) AS child,
con1.confrelid,
con1.conrelid,
cl_1.relname,
con1.conname,
nspname
FROM
pg_class cl_1,
pg_namespace ns,
pg_constraint con1
WHERE
con1.contype = 'f'::"char"
AND cl_1.relnamespace = ns.oid
AND con1.conrelid = cl_1.oid
and nspname in (${schema})
) con,
pg_attribute att,
pg_class cl,
pg_attribute att2,
information_schema.referential_constraints rc
WHERE
att.attrelid = con.confrelid
AND att.attnum = con.child
AND cl.oid = con.confrelid
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: 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
);
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;
}
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,
generationOptions
);
return retVal;
}
public async DisconnectFromServer() {
@ -623,10 +641,10 @@ export default class PostgresDriver extends AbstractDriver {
private static ReturnDefaultValueFunction(
defVal: string | null
): string | null {
): string | undefined {
let defaultValue = defVal;
if (!defaultValue) {
return null;
return undefined;
}
defaultValue = defaultValue.replace(/'::[\w ]*/, "'");
if (defaultValue.startsWith(`'`)) {

View File

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

View File

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