Commit 903552dd authored by William Naslund's avatar William Naslund

Added constraint implementation for non-user-defined constraints

parent a4338a93
import { DBTableInformation } from "../decorators";
import { DBConstraint, DBConstraintConstructor } from "../constraints";
/** Manages table constriants */
export interface DBConstraintAdapter {
/** Returns the constraint that exist for a table */
list(table: DBTableInformation): Promise<DBConstraintId[]>;
/** Creates a new constraint on the given table */
create(table: DBTableInformation, name: string, constraint: DBConstraint): Promise<void>;
/** Drops a constraint on a table */
drop(table: DBTableInformation, name: string): Promise<void>;
}
export interface DBConstraintId {
name: string;
type: DBConstraintConstructor;
}
......@@ -3,6 +3,7 @@ import { DBTableAdapter } from "./table-adapter";
import { DBCommandAdapter } from "./command-adapter";
import { DBModelConstructor } from "../model";
import { DBColumnAdapter } from "./column-adapter";
import { DBConstraintAdapter } from "./constraint-adapter";
/** A collection of adapters to interface with a database system */
export interface DBAdapter {
......@@ -12,6 +13,7 @@ export interface DBAdapter {
schema: DBSchemaAdapter;
table: DBTableAdapter;
column: DBColumnAdapter;
constraint: DBConstraintAdapter;
/** Closes the database connection */
close(): Promise<void>;
......
......@@ -2,4 +2,5 @@ export * from './database-adapter';
export * from './command-adapter';
export * from './schema-adapter';
export * from './table-adapter';
export * from './column-adapter';
\ No newline at end of file
export * from './column-adapter';
export * from './constraint-adapter';
\ No newline at end of file
/** A table level constraint */
export interface DBConstraint {
/** Returns the SQL necessary to create the constraint */
create(): Promise<string>;
/** Returns the type identifer of this constraint (used in the constraint name) */
getTypeId(): string;
}
/** A constraint constructor */
export interface DBConstraintConstructor {
new(...args: any[]): DBConstraint;
}
import { DBConstraint } from "./constraint";
import { DBTableInformation } from "../decorators";
/** Constrains one or more fields to reference one or more fields on a single record of another table */
export class DBForeignKeyConstraint implements DBConstraint {
constructor(
private readonly fields: string[],
private readonly otherTable: DBTableInformation,
private readonly otherFields: string[]
) { }
async create() {
return `
FOREIGN KEY (${this.fields.join(', ')})
REFERENCES ${this.otherTable.options.schema}.${this.otherTable.name}
(${this.otherFields.join(', ')})
`;
}
getTypeId() { return 'foreign_key'; }
}
export * from './constraint';
export * from './foreign-key-constraint';
export * from './unique-constraint';
\ No newline at end of file
import { DBConstraint } from "./constraint";
export class DBUniqueConstraint implements DBConstraint {
constructor(
private readonly fields: string[]
) { }
async create() {
return `
UNIQUE (${this.fields.join(', ')})
`;
}
getTypeId() { return 'unique'; }
}
import { DBAdapter } from "./adapter/database-adapter";
import { DBModelConstructor } from "./model";
import { getTableInformation, getTableFields } from "./internal/meta-keys";
import { getTableInformation, getTableFields, getParentLookups } from "./internal/meta-keys";
import { DBUniqueConstraint, DBConstraintConstructor, DBForeignKeyConstraint } from "./constraints";
import { DBTableInformation } from "./decorators";
export class Database {
......@@ -50,6 +52,39 @@ export class Database {
await this.adapter.column.drop(tableInfo, orphanField);
}
// Id Unique Constraint
if(tableInfo.options && tableInfo.options.id) {
const idConstraint = new DBUniqueConstraint([ String(tableInfo.options.id) ]);
const idConstraintName = `${tableInfo.name}__id__${idConstraint.getTypeId()}`;
const existingType = await this.getConstraintType(tableInfo, idConstraintName);
if(existingType == null) {
await this.adapter.constraint.create(tableInfo, idConstraintName, idConstraint);
} else {
if(existingType !== DBUniqueConstraint) {
await this.adapter.constraint.drop(tableInfo, idConstraintName);
await this.adapter.constraint.create(tableInfo, idConstraintName, idConstraint);
}
}
}
// Parent lookup constraints
for(const lookup of getParentLookups(modelType)) {
const parentInfo = getTableInformation(lookup.model);
const lookupConstraint = new DBForeignKeyConstraint([ lookup.name ], parentInfo, [ String(parentInfo.options.id) ]);
const constraintName = `${tableInfo.name}__lookup__${lookup.name}`;
const existingType = await this.getConstraintType(tableInfo, constraintName);
if(existingType == null) {
await this.adapter.constraint.create(tableInfo, constraintName, lookupConstraint);
} else {
if(existingType !== DBForeignKeyConstraint) {
await this.adapter.constraint.drop(tableInfo, constraintName);
await this.adapter.constraint.create(tableInfo, constraintName, lookupConstraint);
}
}
}
}
}
......@@ -74,4 +109,15 @@ export class Database {
await this.adapter.close();
}
/** Returns the type of the constraint if it exists, or null if it does not */
private async getConstraintType(table: DBTableInformation, name: string): Promise<DBConstraintConstructor> {
for(const id of await this.adapter.constraint.list(table)) {
if(id.name == name) {
return id.type;
}
}
return null;
}
}
......@@ -6,3 +6,4 @@ export * from './postgres/pg-database-adapter';
export * from './decorators/index';
export * from './adapter/index';
export * from './types/index';
export * from './constraints/index';
......@@ -57,3 +57,8 @@ export function getTableFields(modelType: DBModelConstructor<any>): DBFieldInfor
export function getFieldInformation(modelType: DBModelConstructor<any>, propertyKey: string | symbol): DBFieldInformation {
return Reflect.getMetadata(FIELD_INFO, modelType, propertyKey);
}
/** Loads the parent lookups on the child table */
export function getParentLookups(modelType: DBModelConstructor<any>): DBLookupInformation[] {
return Reflect.getMetadata(TABLE_PARENT_LOOKUPS, modelType) || [];
}
import { DBConstraintAdapter, DBConstraintId } from "../adapter/constraint-adapter";
import { DBTableInformation } from "../decorators";
import { DBConstraint, DBUniqueConstraint, DBForeignKeyConstraint } from "../constraints";
import { PGAdapter } from "./pg-database-adapter";
/** Constraint adapter for a PostgreSQL database */
export class PGConstraintAdapter implements DBConstraintAdapter {
constructor(
private readonly db: PGAdapter
) { }
async list(table: DBTableInformation) {
const constraints: DBConstraintId[] = [];
const res = await this.db.pool.query(`
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE
table_schema = $1
AND table_name = $2
`, [ table.options.schema, table.name ]);
for(const row of res.rows) {
const id: DBConstraintId = {
name: row.constraint_name,
type: null
};
switch(row.constraint_type.toUpperCase()) {
case 'UNIQUE':
id.type = DBUniqueConstraint;
break;
case 'FOREIGN KEY':
id.type = DBForeignKeyConstraint;
break;
}
if(id.type != null) {
constraints.push(id);
}
}
return constraints;
}
async create(table: DBTableInformation, name: string, constraint: DBConstraint) {
await this.db.pool.query(`
ALTER TABLE ${table.options.schema}.${table.name}
ADD CONSTRAINT ${name}
${await constraint.create()}
`);
}
async drop(table: DBTableInformation, name: string) {
await this.db.pool.query(`
ALTER TABLE ${table.options.schema}.${table.name}
DROP CONSTRAINT ${name}
`);
}
}
......@@ -4,6 +4,7 @@ import { PGSchemaAdapter } from "./pg-schema-adapter";
import { PGCommandAdapter } from "./pg-command-adapter";
import { PGTableAdapter } from "./pg-table-adapter";
import { PGColumnAdapter } from "./pg-column-adapter";
import { PGConstraintAdapter } from "./pg-constraint-adapter";
import { Pool } from "pg";
/** Adapts to a PostgreSQL database system */
......@@ -14,6 +15,7 @@ export class PGAdapter implements DBAdapter {
schema: PGSchemaAdapter;
table: PGTableAdapter;
column: PGColumnAdapter;
constraint: PGConstraintAdapter;
/** The connection pool for this adapter */
public readonly pool: Pool;
......@@ -25,6 +27,7 @@ export class PGAdapter implements DBAdapter {
this.schema = new PGSchemaAdapter(this);
this.table = new PGTableAdapter(this);
this.column = new PGColumnAdapter(this);
this.constraint = new PGConstraintAdapter(this);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment