added postgres required driver classes

This commit is contained in:
Umed Khudoiberdiev 2016-06-06 14:16:32 +05:00
parent 20afae93ef
commit 7252137ae6
33 changed files with 666 additions and 187 deletions

View File

@ -35,6 +35,7 @@
"gulpclass": "0.1.1",
"mocha": "^2.5.3",
"mysql": "^2.10.2",
"pg": "^4.5.5",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"tslint": "next",

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
@ -13,6 +14,21 @@ const options: CreateConnectionOptions = {
},
entities: [Post]
};
/*const options: CreateConnectionOptions = {
driver: "postgres",
connection: {
host: "192.168.99.100",
port: 5432,
username: "test",
password: "admin",
database: "test",
autoSchemaCreate: true,
logging: {
logQueries: true
}
},
entities: [Post]
};*/
createConnection(options).then(connection => {

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {PostDetails} from "./entity/PostDetails";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {EverythingEntity} from "./entity/EverythingEntity";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {CustomNamingStrategy} from "./naming-strategy/CustomNamingStrategy";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {PostCategory} from "./entity/PostCategory";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {CreateConnectionOptions, createConnection} from "../../src/index";
import {Post} from "./entity/Post";
import {PostAuthor} from "./entity/PostAuthor";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {Author} from "./entity/Author";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {Author} from "./entity/Author";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {Author} from "./entity/Author";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {Author} from "./entity/Author";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Category} from "./entity/Category";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {Author} from "./entity/Author";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {CreateConnectionOptions, createConnection} from "../../src/index";
import {Post} from "./entity/Post";
import {PostDetails} from "./entity/PostDetails";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {CreateConnectionOptions, createConnection} from "../../src/index";
import {Post} from "./entity/Post";
import {PostDetails} from "./entity/PostDetails";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {PostCategory} from "./entity/PostCategory";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {PostCategory} from "./entity/PostCategory";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Post} from "./entity/Post";
import {PostCategory} from "./entity/PostCategory";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Category} from "./entity/Category";

View File

@ -1,3 +1,4 @@
import "reflect-metadata";
import {CreateConnectionOptions, createConnection} from "../../src/index";
import {Post} from "./entity/Post";
import {PostCategory} from "./entity/PostCategory";

View File

@ -5,6 +5,7 @@ import {CreateConnectionOptions} from "./CreateConnectionOptions";
import {ConnectionOptions} from "../connection/ConnectionOptions";
import {Driver} from "../driver/Driver";
import {MissingDriverError} from "./error/MissingDriverError";
import {PostgresDriver} from "../driver/PostgresDriver";
/**
* Connection manager holds all connections made to the databases and providers helper management functions
@ -74,10 +75,12 @@ export class ConnectionManager {
// Private Methods
// -------------------------------------------------------------------------
private createDriver(driverName: string) {
private createDriver(driverName: string): Driver {
switch (driverName) {
case "mysql":
return new MysqlDriver();
case "postgres":
return new PostgresDriver();
default:
throw new MissingDriverError(driverName);
}

View File

@ -7,9 +7,9 @@ import {EntitySchema} from "../metadata/entity-schema/EntitySchema";
export interface CreateConnectionOptions {
/**
* Driver type. Mysql is the only driver supported at this moment.
* Driver type. Mysql and postgres are the only drivers supported at this moment.
*/
driver: "mysql";
driver: "mysql"|"postgres";
/**
* Database connection options.

View File

@ -1,4 +1,7 @@
import {ConnectionOptions} from "../connection/ConnectionOptions";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {ColumnTypes} from "../metadata/types/ColumnTypes";
import * as moment from "moment";
/**
* Provides base functionality for all driver implementations.
@ -11,6 +14,170 @@ export abstract class BaseDriver {
abstract connectionOptions: ConnectionOptions;
// -------------------------------------------------------------------------
// Abstract Protected Methods
// -------------------------------------------------------------------------
protected abstract checkIfConnectionSet(): void;
// -------------------------------------------------------------------------
// Abstract Public Methods
// -------------------------------------------------------------------------
/**
* Executes a given SQL query and returns raw database results.
*/
abstract query<T>(query: string): Promise<T>;
/**
* Escapes given value.
*/
abstract escape(value: any): any;
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Updates rows that match given conditions in the given table.
*/
update(tableName: string, valuesMap: Object, conditions: Object): Promise<void> {
this.checkIfConnectionSet();
const updateValues = this.escapeObjectMap(valuesMap).join(",");
const conditionString = this.escapeObjectMap(conditions).join(" AND ");
const query = `UPDATE ${tableName} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
// console.log("executing update: ", query);
return this.query(query).then(() => {});
}
private escapeObjectMap(objectMap: { [key: string]: any }): string[] {
return Object.keys(objectMap).map(key => {
const value = (<any> objectMap)[key];
if (value === null || value === undefined) {
return key + "=NULL";
} else {
return key + "=" + this.escape(value);
}
});
}
/**
* Insert a new row into given table.
*/
insert(tableName: string, keyValues: Object): Promise<any> {
this.checkIfConnectionSet();
const columns = Object.keys(keyValues).join(",");
const values = Object.keys(keyValues).map(key => this.escape((<any> keyValues)[key])).join(","); // todo: escape here
const query = `INSERT INTO ${tableName}(${columns}) VALUES (${values})`;
return this.query<any>(query).then(result => result.insertId);
}
/**
* Deletes from the given table by a given conditions.
*/
delete(tableName: string, conditions: Object): Promise<void> {
this.checkIfConnectionSet();
const conditionString = this.escapeObjectMap(conditions).join(" AND ");
const query = `DELETE FROM ${tableName} WHERE ${conditionString}`;
return this.query(query).then(() => {});
}
/**
* Starts postgres transaction.
*/
beginTransaction(): Promise<void> {
return this.query("START TRANSACTION").then(() => {});
}
/**
* Ends postgres transaction.
*/
endTransaction(): Promise<void> {
return this.query("COMMIT").then(() => {});
}
/**
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
preparePersistentValue(value: any, column: ColumnMetadata): any {
switch (column.type) {
case ColumnTypes.BOOLEAN:
return value === true ? 1 : 0;
case ColumnTypes.DATE:
return moment(value).format("YYYY-MM-DD");
case ColumnTypes.TIME:
return moment(value).format("HH:mm:ss");
case ColumnTypes.DATETIME:
return moment(value).format("YYYY-MM-DD HH:mm:ss");
case ColumnTypes.JSON:
return JSON.stringify(value);
case ColumnTypes.SIMPLE_ARRAY:
return (value as Array<any>)
.map(i => String(i))
.join(",");
}
return value;
}
/**
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
prepareHydratedValue(value: any, column: ColumnMetadata): any {
switch (column.type) {
case ColumnTypes.BOOLEAN:
return value ? true : false;
case ColumnTypes.DATE:
if (value instanceof Date)
return value;
return moment(value, "YYYY-MM-DD").toDate();
case ColumnTypes.TIME:
return moment(value, "HH:mm:ss").toDate();
case ColumnTypes.DATETIME:
if (value instanceof Date)
return value;
return moment(value, "YYYY-MM-DD HH:mm:ss").toDate();
case ColumnTypes.JSON:
return JSON.parse(value);
case ColumnTypes.SIMPLE_ARRAY:
return (value as string).split(",");
}
return value;
}
/**
* Inserts rows into closure table.
*/
insertIntoClosureTable(tableName: string, newEntityId: any, parentId: any, hasLevel: boolean): Promise<number> {
let sql = "";
if (hasLevel) {
sql = `INSERT INTO ${tableName}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${tableName} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
} else {
sql = `INSERT INTO ${tableName}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${tableName} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
}
return this.query(sql).then(() => {
return this.query(`SELECT MAX(level) as level FROM ${tableName} WHERE descendant = ${parentId}`);
}).then((results: any) => {
return results && results[0] && results[0]["level"] ? parseInt(results[0]["level"]) + 1 : 1;
});
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
@ -19,20 +186,20 @@ export abstract class BaseDriver {
if (this.connectionOptions.logging && this.connectionOptions.logging.logQueries)
this.log("executing query: " + query, "log");
}
protected logQueryError(error: any) {
if (this.connectionOptions.logging && this.connectionOptions.logging.logFailedQueryError) {
this.log("error during executing query:", "error");
this.log(error, "error");
}
}
protected logFailedQuery(query: string) {
if (this.connectionOptions.logging &&
if (this.connectionOptions.logging &&
(this.connectionOptions.logging.logQueries || this.connectionOptions.logging.logOnlyFailedQueries))
this.log("query failed: " + query, "error");
}
protected log(message: any, level: "log"|"debug"|"info"|"error") {
if (!this.connectionOptions.logging) return;
if (this.connectionOptions && this.connectionOptions.logging && this.connectionOptions.logging.logger) {

View File

@ -27,13 +27,6 @@ export interface Driver {
*/
readonly db: string;
/**
* todo: driver should not define which query builder to create, it should create its own helper for query builder.
*
* Creates a query builder which can be used to build an sql queries.
*/
readonly queryBuilderClass: Function;
/**
* Creates a schema builder which can be used to build database/table schemas.
*/

View File

@ -1,6 +1,5 @@
import {Driver} from "./Driver";
import {SchemaBuilder} from "../schema-builder/SchemaBuilder";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {MysqlSchemaBuilder} from "../schema-builder/MysqlSchemaBuilder";
import {ConnectionIsNotSetError} from "./error/ConnectionIsNotSetError";
import {BaseDriver} from "./BaseDriver";
@ -95,13 +94,6 @@ export class MysqlDriver extends BaseDriver implements Driver {
// Public Methods
// -------------------------------------------------------------------------
/**
* Creates a query builder which can be used to build an sql queries.
*/
get queryBuilderClass() {
return QueryBuilder;
}
/**
* Creates a schema builder which can be used to build database/table schemas.
*/
@ -117,7 +109,8 @@ export class MysqlDriver extends BaseDriver implements Driver {
host: this.connectionOptions.host,
user: this.connectionOptions.username,
password: this.connectionOptions.password,
database: this.connectionOptions.database
database: this.connectionOptions.database,
port: this.connectionOptions.port
});
return new Promise<void>((ok, fail) => {
this.mysqlConnection.connect((err: any) => err ? fail(err) : ok());
@ -128,23 +121,28 @@ export class MysqlDriver extends BaseDriver implements Driver {
* Closes connection with database.
*/
disconnect(): Promise<void> {
if (!this.mysqlConnection)
throw new ConnectionIsNotSetError("mysql");
this.checkIfConnectionSet();
return new Promise<void>((ok, fail) => {
this.mysqlConnection.end((err: any) => err ? fail(err) : ok());
});
}
/**
* Escapes given value.
*/
escape(value: any): any {
return this.mysqlConnection.escape(value);
}
/**
* Executes a given SQL query.
*/
query<T>(query: string): Promise<T> {
if (!this.mysqlConnection)
throw new ConnectionIsNotSetError("mysql");
this.checkIfConnectionSet();
this.logQuery(query);
return new Promise<any>((ok, fail) => this.mysqlConnection.query(query, (err: any, result: any) => {
return new Promise<T>((ok, fail) => this.mysqlConnection.query(query, (err: any, result: any) => {
if (err) {
this.logFailedQuery(query);
this.logQueryError(err);
@ -159,8 +157,7 @@ export class MysqlDriver extends BaseDriver implements Driver {
* Clears all tables in the currently connected database.
*/
clearDatabase(): Promise<void> {
if (!this.mysqlConnection)
throw new ConnectionIsNotSetError("mysql");
this.checkIfConnectionSet();
const disableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 0;`;
const dropTablesQuery = `SELECT concat('DROP TABLE IF EXISTS ', table_name, ';') AS q FROM ` +
@ -174,161 +171,13 @@ export class MysqlDriver extends BaseDriver implements Driver {
.then(() => {});
}
/**
* Updates rows that match given conditions in the given table.
*/
update(tableName: string, valuesMap: Object, conditions: Object): Promise<void> {
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
protected checkIfConnectionSet() {
if (!this.mysqlConnection)
throw new ConnectionIsNotSetError("mysql");
const updateValues = this.escapeObjectMap(valuesMap).join(",");
const conditionString = this.escapeObjectMap(conditions).join(" AND ");
const query = `UPDATE ${tableName} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
// console.log("executing update: ", query);
return this.query(query).then(() => {});
// const qb = this.createQueryBuilder().update(tableName, valuesMap).from(tableName, "t");
// Object.keys(conditions).forEach(key => qb.andWhere(key + "=:" + key, { [key]: (<any> conditions)[key] }));
// return qb.execute().then(() => {});
}
private escapeObjectMap(objectMap: { [key: string]: any }): string[] {
return Object.keys(objectMap).map(key => {
const value = (<any> objectMap)[key];
if (value === null || value === undefined) {
return key + "=NULL";
} else {
return key + "=" + this.escape(value);
}
});
}
/**
* Insert a new row into given table.
*/
insert(tableName: string, keyValues: Object): Promise<any> {
if (!this.mysqlConnection)
throw new ConnectionIsNotSetError("mysql");
const columns = Object.keys(keyValues).join(",");
// const values = this.escapeObjectMap(keyValues).join(",");
const values = Object.keys(keyValues).map(key => this.escape((<any> keyValues)[key])).join(","); // todo: escape here
const query = `INSERT INTO ${tableName}(${columns}) VALUES (${values})`;
return this.query<any>(query).then(result => result.insertId);
}
/**
* Deletes from the given table by a given conditions.
*/
delete(tableName: string, conditions: Object): Promise<void> {
if (!this.mysqlConnection)
throw new ConnectionIsNotSetError("mysql");
const conditionString = this.escapeObjectMap(conditions).join(" AND ");
const query = `DELETE FROM ${tableName} WHERE ${conditionString}`;
return this.query(query).then(() => {});
// const qb = this.createQueryBuilder().delete(tableName);
// Object.keys(conditions).forEach(key => qb.andWhere(key + "=:" + key, { [key]: (<any> conditions)[key] }));
// return qb.execute().then(() => {});
}
/**
* Starts mysql transaction.
*/
beginTransaction(): Promise<void> {
return this.query("START TRANSACTION").then(() => {});
}
/**
* Ends mysql transaction.
*/
endTransaction(): Promise<void> {
return this.query("COMMIT").then(() => {});
}
/**
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
preparePersistentValue(value: any, column: ColumnMetadata): any {
switch (column.type) {
case ColumnTypes.BOOLEAN:
return value === true ? 1 : 0;
case ColumnTypes.DATE:
return moment(value).format("YYYY-MM-DD");
case ColumnTypes.TIME:
return moment(value).format("HH:mm:ss");
case ColumnTypes.DATETIME:
return moment(value).format("YYYY-MM-DD HH:mm:ss");
case ColumnTypes.JSON:
return JSON.stringify(value);
case ColumnTypes.SIMPLE_ARRAY:
return (value as Array<any>)
.map(i => String(i))
.join(",");
}
return value;
}
/**
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
prepareHydratedValue(value: any, column: ColumnMetadata): any {
switch (column.type) {
case ColumnTypes.BOOLEAN:
return value ? true : false;
case ColumnTypes.DATE:
if (value instanceof Date)
return value;
return moment(value, "YYYY-MM-DD").toDate();
case ColumnTypes.TIME:
return moment(value, "HH:mm:ss").toDate();
case ColumnTypes.DATETIME:
if (value instanceof Date)
return value;
return moment(value, "YYYY-MM-DD HH:mm:ss").toDate();
case ColumnTypes.JSON:
return JSON.parse(value);
case ColumnTypes.SIMPLE_ARRAY:
return (value as string).split(",");
}
return value;
}
/**
* Escapes given value.
*/
escape(value: any): any {
return this.mysqlConnection.escape(value);
}
/**
* Inserts rows into closure table.
*/
insertIntoClosureTable(tableName: string, newEntityId: any, parentId: any, hasLevel: boolean): Promise<number> {
let sql = "";
if (hasLevel) {
sql = `INSERT INTO ${tableName}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${tableName} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
} else {
sql = `INSERT INTO ${tableName}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${tableName} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
}
return this.query(sql).then(() => {
return this.query(`SELECT MAX(level) as level FROM ${tableName} WHERE descendant = ${parentId}`);
}).then((results: any) => {
return results && results[0] && results[0]["level"] ? parseInt(results[0]["level"]) + 1 : 1;
});
}
}

View File

@ -0,0 +1,181 @@
import {Driver} from "./Driver";
import {SchemaBuilder} from "../schema-builder/SchemaBuilder";
import {ConnectionIsNotSetError} from "./error/ConnectionIsNotSetError";
import {BaseDriver} from "./BaseDriver";
import {ConnectionOptions} from "../connection/ConnectionOptions";
import {PostgresSchemaBuilder} from "../schema-builder/PostgresSchemaBuilder";
/**
* This driver organizes work with postgres database.
*/
export class PostgresDriver extends BaseDriver implements Driver {
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Connection used in this driver.
*/
connectionOptions: ConnectionOptions;
// -------------------------------------------------------------------------
// Private Properties
// -------------------------------------------------------------------------
/**
* Postgres library.
*/
private postgres: any;
/**
* Connection to postgres database.
*/
private postgresConnection: any;
// -------------------------------------------------------------------------
// Getter Methods
// -------------------------------------------------------------------------
/**
* Access to the native implementation of the database.
*/
get native(): any {
return this.postgres;
}
/**
* Access to the native connection to the database.
*/
get nativeConnection(): any {
return this.postgresConnection;
}
/**
* Database name to which this connection is made.
*/
get db(): string {
// if (this.postgresConnection && this.postgresConnection.config.database)
// return this.postgresConnection.config.database;
if (this.connectionOptions.database)
return this.connectionOptions.database;
throw new Error("Cannot get the database name. Since database name is not explicitly given in configuration " +
"(maybe connection url is used?), database name cannot be retrieved until connection is made.");
}
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(postgres?: any) {
super();
// if driver dependency is not given explicitly, then try to load it via "require"
if (!postgres && require) {
try {
postgres = require("pg");
} catch (e) {
throw new Error("Postgres package has not been found installed. Try to install it: npm install pg --save");
}
} else {
throw new Error("Cannot load postgres driver dependencies. Try to install all required dependencies.");
}
this.postgres = postgres;
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Creates a schema builder which can be used to build database/table schemas.
*/
createSchemaBuilder(): SchemaBuilder {
return new PostgresSchemaBuilder(this);
}
/**
* Performs connection to the database based on given connection options.
*/
connect(): Promise<void> {
return new Promise<void>((ok, fail) => {
this.postgresConnection = new this.postgres.Client({
host: this.connectionOptions.host,
user: this.connectionOptions.username,
password: this.connectionOptions.password,
database: this.connectionOptions.database,
port: this.connectionOptions.port
});
this.postgresConnection.connect((err: any) => err ? fail(err) : ok());
});
}
/**
* Closes connection with database.
*/
disconnect(): Promise<void> {
this.checkIfConnectionSet();
return new Promise<void>((ok, fail) => {
this.postgresConnection.end(/*(err: any) => err ? fail(err) : ok()*/); // todo: check if it can emit errors
ok();
});
}
/**
* Escapes given value.
*/
escape(value: any): any {
return this.postgresConnection.escape(value);
}
/**
* Executes a given SQL query.
*/
query<T>(query: string): Promise<T> {
this.checkIfConnectionSet();
this.logQuery(query);
return new Promise<T>((ok, fail) => this.postgresConnection.query(query, (err: any, result: any) => {
if (err) {
this.logFailedQuery(query);
this.logQueryError(err);
fail(err);
} else {
ok(result.rows);
}
}));
}
/**
* Clears all tables in the currently connected database.
*/
clearDatabase(): Promise<void> {
this.checkIfConnectionSet(); // todo:
const disableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 0;`;
const dropTablesQuery = `SELECT concat('DROP TABLE IF EXISTS ', table_name, ';') AS q FROM ` +
`information_schema.tables WHERE table_schema = '${this.db}';`;
const enableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 1;`;
return this.query(disableForeignKeysCheckQuery)
.then(() => this.query<any[]>(dropTablesQuery))
.then(results => Promise.all(results.map(q => this.query(q["q"]))))
.then(() => this.query(enableForeignKeysCheckQuery))
.then(() => {});
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
protected checkIfConnectionSet() {
if (!this.postgresConnection)
throw new ConnectionIsNotSetError("postgres");
}
}

View File

@ -1,4 +1,3 @@
import {ColumnMode} from "../ColumnMetadata";
import {ColumnType} from "../types/ColumnTypes";
import {TableType} from "../TableMetadata";
import {RelationType} from "../types/RelationTypes";
@ -40,12 +39,12 @@ export interface EntitySchema {
/**
* Specifies array of properties that will be used in a composite primary key of the table.
*/
primaryKeys?: string|((object: any) => string|any)[];
primaryKeys?: string[];
/**
* Specifies a property name by which queries will perform ordering by default when fetching rows.
*/
orderBy?: string|((object: any) => string|any);
orderBy?: string;
};
/**

View File

@ -0,0 +1,237 @@
import {SchemaBuilder} from "./SchemaBuilder";
import {PostgresDriver} from "../driver/PostgresDriver";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
import {TableMetadata} from "../metadata/TableMetadata";
import {IndexMetadata} from "../metadata/IndexMetadata";
/**
* @internal
*/
export class PostgresSchemaBuilder extends SchemaBuilder {
constructor(private driver: PostgresDriver) {
super();
}
getChangedColumns(tableName: string, columns: ColumnMetadata[]): Promise<{columnName: string, hasPrimaryKey: boolean}[]> {
const sql = `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG = '${this.driver.db}'` +
` AND TABLE_NAME = '${tableName}'`;
return this.query<any[]>(sql).then(results => {
return columns.filter(column => {
const dbData = results.find(result => result.column_name === column.name);
if (!dbData) return false;
const newType = this.normalizeType(column);
const isNullable = column.isNullable === true ? "YES" : "NO";
let columnType = dbData.data_type.toLowerCase();
if (dbData.character_maximum_length) {
columnType += "(" + dbData.character_maximum_length + ")";
}
// const hasDbColumnAutoIncrement = dbData.EXTRA.indexOf("auto_increment") !== -1;
// const hasDbColumnPrimaryIndex = dbData.column_key.indexOf("PRI") !== -1;
return columnType !== newType ||
// dbData.COLUMN_COMMENT !== column.comment ||
dbData.is_nullable !== isNullable; // ||
// hasDbColumnAutoIncrement !== column.isGenerated ||
// hasDbColumnPrimaryIndex !== column.isPrimary;
}).map(column => {
// const dbData = results.find(result => result.column_name === column.name);
// const hasDbColumnPrimaryIndex = dbData.column_key.indexOf("PRI") !== -1;
return { columnName: column.name/*, hasPrimaryKey: hasDbColumnPrimaryIndex*/ };
});
});
}
checkIfTableExist(tableName: string): Promise<boolean> {
const sql = `SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_CATALOG = '${this.driver.db}' AND TABLE_NAME = '${tableName}'`;
return this.query<any[]>(sql).then(results => !!(results && results.length));
}
addColumnQuery(tableName: string, column: ColumnMetadata): Promise<void> {
const sql = `ALTER TABLE ${tableName} ADD ${this.buildCreateColumnSql(column, false)}`;
return this.query(sql).then(() => {});
}
dropColumnQuery(tableName: string, columnName: string): Promise<void> {
const sql = `ALTER TABLE ${tableName} DROP ${columnName}`;
return this.query(sql).then(() => {});
}
addForeignKeyQuery(foreignKey: ForeignKeyMetadata): Promise<void> {
let sql = `ALTER TABLE ${foreignKey.tableName} ADD CONSTRAINT \`${foreignKey.name}\` ` +
`FOREIGN KEY (${foreignKey.columnNames.join(", ")}) ` +
`REFERENCES ${foreignKey.referencedTable.name}(${foreignKey.referencedColumnNames.join(",")})`;
if (foreignKey.onDelete)
sql += " ON DELETE " + foreignKey.onDelete;
return this.query(sql).then(() => {});
}
dropForeignKeyQuery(foreignKey: ForeignKeyMetadata): Promise<void>;
dropForeignKeyQuery(tableName: string, foreignKeyName: string): Promise<void>;
dropForeignKeyQuery(tableNameOrForeignKey: string|ForeignKeyMetadata, foreignKeyName?: string): Promise<void> {
let tableName = <string> tableNameOrForeignKey;
if (tableNameOrForeignKey instanceof ForeignKeyMetadata) {
tableName = tableNameOrForeignKey.tableName;
foreignKeyName = tableNameOrForeignKey.name;
}
const sql = `ALTER TABLE ${tableName} DROP FOREIGN KEY \`${foreignKeyName}\``;
return this.query(sql).then(() => {});
}
getTableForeignQuery(tableName: string): Promise<string[]> {
const sql = `SELECT * FROM INFORMATION_SCHEMA.table_constraints WHERE TABLE_CATALOG = '${this.driver.db}' `
+ `AND TABLE_NAME = '${tableName}' AND CONSTRAINT_TYPE='FOREIGN KEY'`;
return this.query<any[]>(sql).then(results => results.map(result => result.CONSTRAINT_NAME));
}
getTableUniqueKeysQuery(tableName: string): Promise<string[]> {
const sql = `SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_CATALOG = '${this.driver.db}' ` +
`AND TABLE_NAME = '${tableName}' AND CONSTRAINT_TYPE = 'UNIQUE'`;
return this.query<any[]>(sql).then(results => results.map(result => result.CONSTRAINT_NAME));
}
getTableIndicesQuery(tableName: string): Promise<{ key: string, sequence: number, column: string }[]> {
const sql = `SHOW INDEX FROM ${tableName}`;
return this.query<any[]>(sql).then(results => {
// exclude foreign keys
return this.getTableForeignQuery(tableName).then(foreignKeys => {
return results
.filter(result => result.Key_name !== "PRIMARY" && foreignKeys.indexOf(result.Key_name) === -1)
.map(result => ({
key: result.Key_name,
sequence: result.Seq_in_index,
column: result.Column_name
}));
});
});
}
getPrimaryConstraintName(tableName: string): Promise<string> {
const sql = `SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_CATALOG = '${this.driver.db}'`
+ ` AND TABLE_NAME = '${tableName}' AND CONSTRAINT_TYPE = 'PRIMARY KEY'`;
return this.query<any[]>(sql).then(results => results && results.length ? results[0].CONSTRAINT_NAME : undefined);
}
dropIndex(tableName: string, indexName: string): Promise<void> {
const sql = `ALTER TABLE ${tableName} DROP INDEX \`${indexName}\``;
return this.query(sql).then(() => {});
}
createIndex(tableName: string, index: IndexMetadata): Promise<void> {
const sql = `CREATE ${index.isUnique ? "UNIQUE" : ""} INDEX \`${index.name}\` ON ${tableName}(${index.columns.join(", ")})`;
return this.query(sql).then(() => {});
}
addUniqueKey(tableName: string, columnName: string, keyName: string): Promise<void> {
const sql = `ALTER TABLE ${tableName} ADD CONSTRAINT ${keyName} UNIQUE (${columnName})`;
return this.query(sql).then(() => {});
}
getTableColumns(tableName: string): Promise<string[]> {
const sql = `SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG = '${this.driver.db}'` +
` AND TABLE_NAME = '${tableName}'`;
return this.query<any[]>(sql).then(results => results.map(result => result.column_name));
}
changeColumnQuery(tableName: string, columnName: string, newColumn: ColumnMetadata, skipPrimary: boolean = false): Promise<void> {
const sql = `ALTER TABLE ${tableName} CHANGE ${columnName} ${this.buildCreateColumnSql(newColumn, skipPrimary)}`;
return this.query(sql).then(() => {});
}
createTableQuery(table: TableMetadata, columns: ColumnMetadata[]): Promise<void> {
const columnDefinitions = columns.map(column => this.buildCreateColumnSql(column, false)).join(", ");
const sql = `CREATE TABLE "${table.name}" (${columnDefinitions})`;
return this.query(sql).then(() => {});
}
// -------------------------------------------------------------------------
// Private Methods
// -------------------------------------------------------------------------
private query<T>(sql: string) {
return this.driver.query<T>(sql);
}
private buildCreateColumnSql(column: ColumnMetadata, skipPrimary: boolean) {
let c = column.name + " " + this.normalizeType(column);
if (column.isNullable !== true)
c += " NOT NULL";
if (column.isPrimary === true && !skipPrimary)
c += " PRIMARY KEY";
// if (column.isGenerated === true) // don't use skipPrimary here since updates can update already exist primary without auto inc.
// c += " AUTO_INCREMENT";
// TODO: implement auto increment
if (column.comment)
c += " COMMENT '" + column.comment + "'";
if (column.columnDefinition)
c += " " + column.columnDefinition;
return c;
}
private normalizeType(column: ColumnMetadata) {
let realType: string = "";
if (typeof column.type === "string") {
realType = column.type.toLowerCase();
// todo: remove casting to any after upgrade to typescript 2
} else if (typeof column.type === "object" && (<any>column.type).name && typeof (<any>column.type).name === "string") {
realType = (<any>column.type).toLowerCase();
}
switch (realType) {
case "string":
return "character varying(" + (column.length ? column.length : 255) + ")";
case "text":
return "text";
case "boolean":
return "boolean";
case "integer":
case "int":
return "integer";
case "smallint":
return "smallint";
case "bigint":
return "bigint";
case "float":
return "real";
case "double":
case "number":
return "double precision";
case "decimal":
if (column.precision && column.scale) {
return `decimal(${column.precision},${column.scale})`;
} else if (column.scale) {
return `decimal(${column.scale})`;
} else if (column.precision) {
return `decimal(${column.precision})`;
} else {
return "decimal";
}
case "date":
return "date";
case "time":
return "time";
case "datetime":
return "timestamp";
case "json":
return "text";
case "simple_array":
return column.length ? "character varying(" + column.length + ")" : "text";
}
throw new Error("Specified type (" + column.type + ") is not supported by current driver.");
}
}

View File

@ -4,7 +4,6 @@ import {expect} from "chai";
import {Connection} from "../../src/connection/Connection";
import {createConnection, CreateConnectionOptions} from "../../src/index";
import {Repository} from "../../src/repository/Repository";
import {SchemaCreator} from "../../src/schema-creator/SchemaCreator";
import {Post} from "../../sample/sample1-simple-entity/entity/Post";
chai.should();
@ -26,6 +25,18 @@ describe("insertion", function() {
},
entities: [Post]
};
/*const parameters: CreateConnectionOptions = {
driver: "postgres",
connection: {
host: "192.168.99.100",
port: 5432,
username: "test",
password: "admin",
database: "test",
autoSchemaCreate: true
},
entities: [Post]
};*/
// connect to db
let connection: Connection;