Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aurora Data API - Postgres Support #5651

Merged
merged 2 commits into from May 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/connection/ConnectionOptions.ts
Expand Up @@ -12,6 +12,7 @@ import {NativescriptConnectionOptions} from "../driver/nativescript/Nativescript
import {ExpoConnectionOptions} from "../driver/expo/ExpoConnectionOptions";
import {AuroraDataApiConnectionOptions} from "../driver/aurora-data-api/AuroraDataApiConnectionOptions";
import {SapConnectionOptions} from "../driver/sap/SapConnectionOptions";
import {AuroraDataApiPostgresConnectionOptions} from "../driver/aurora-data-api-pg/AuroraDataApiPostgresConnectionOptions";


/**
Expand All @@ -33,4 +34,5 @@ export type ConnectionOptions =
SqljsConnectionOptions|
MongoConnectionOptions|
AuroraDataApiConnectionOptions|
AuroraDataApiPostgresConnectionOptions|
ExpoConnectionOptions;
3 changes: 3 additions & 0 deletions src/driver/DriverFactory.ts
Expand Up @@ -15,6 +15,7 @@ import {AuroraDataApiDriver} from "./aurora-data-api/AuroraDataApiDriver";
import {Driver} from "./Driver";
import {Connection} from "../connection/Connection";
import {SapDriver} from "./sap/SapDriver";
import {AuroraDataApiPostgresDriver} from "./postgres/PostgresDriver";

/**
* Helps to create drivers.
Expand Down Expand Up @@ -57,6 +58,8 @@ export class DriverFactory {
return new ExpoDriver(connection);
case "aurora-data-api":
return new AuroraDataApiDriver(connection);
case "aurora-data-api-pg":
return new AuroraDataApiPostgresDriver(connection);
default:
throw new MissingDriverError(type);
}
Expand Down
@@ -0,0 +1,34 @@
import {BaseConnectionOptions} from "../../connection/BaseConnectionOptions";

/**
* Postgres-specific connection options.
*/
export interface AuroraDataApiPostgresConnectionOptions extends BaseConnectionOptions {

/**
* Database type.
*/
readonly type: "aurora-data-api-pg";

readonly region: string;

readonly secretArn: string;

readonly resourceArn: string;

readonly database: string;

/**
* The Postgres extension to use to generate UUID columns. Defaults to uuid-ossp.
* If pgcrypto is selected, TypeORM will use the gen_random_uuid() function from this extension.
* If uuid-ossp is selected, TypeORM will use the uuid_generate_v4() function from this extension.
*/
readonly uuidExtension?: "pgcrypto" | "uuid-ossp";


/*
* Function handling errors thrown by drivers pool.
* Defaults to logging error with `warn` level.
*/
readonly poolErrorHandler?: (err: any) => any;
}
138 changes: 138 additions & 0 deletions src/driver/aurora-data-api-pg/AuroraDataApiPostgresQueryRunner.ts
@@ -0,0 +1,138 @@
import {QueryRunnerAlreadyReleasedError} from "../../error/QueryRunnerAlreadyReleasedError";
import {TransactionAlreadyStartedError} from "../../error/TransactionAlreadyStartedError";
import {TransactionNotStartedError} from "../../error/TransactionNotStartedError";
import {QueryRunner} from "../../query-runner/QueryRunner";
import {IsolationLevel} from "../types/IsolationLevel";
import {AuroraDataApiPostgresDriver} from "../postgres/PostgresDriver";
import {PostgresQueryRunner} from "../postgres/PostgresQueryRunner";

class PostgresQueryRunnerWrapper extends PostgresQueryRunner {
driver: any;

constructor(driver: any, mode: "master"|"slave") {
super(driver, mode);
}
}

/**
* Runs queries on a single postgres database connection.
*/
export class AuroraDataApiPostgresQueryRunner extends PostgresQueryRunnerWrapper implements QueryRunner {

// -------------------------------------------------------------------------
// Public Implemented Properties
// -------------------------------------------------------------------------

/**
* Database driver used by connection.
*/
driver: AuroraDataApiPostgresDriver;

// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------

/**
* Promise used to obtain a database connection for a first time.
*/
protected databaseConnectionPromise: Promise<any>;

/**
* Special callback provided by a driver used to release a created connection.
*/
protected releaseCallback: Function;

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------

constructor(driver: AuroraDataApiPostgresDriver, mode: "master"|"slave" = "master") {
super(driver, mode);
}

// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------

/**
* Creates/uses database connection from the connection pool to perform further operations.
* Returns obtained database connection.
*/
connect(): Promise<any> {
if (this.databaseConnection)
return Promise.resolve(this.databaseConnection);

if (this.databaseConnectionPromise)
return this.databaseConnectionPromise;

if (this.mode === "slave" && this.driver.isReplicated) {
this.databaseConnectionPromise = this.driver.obtainSlaveConnection().then(([ connection, release]: any[]) => {
this.driver.connectedQueryRunners.push(this);
this.databaseConnection = connection;
this.releaseCallback = release;
return this.databaseConnection;
});

} else { // master
this.databaseConnectionPromise = this.driver.obtainMasterConnection().then(([connection, release]: any[]) => {
this.driver.connectedQueryRunners.push(this);
this.databaseConnection = connection;
this.releaseCallback = release;
return this.databaseConnection;
});
}

return this.databaseConnectionPromise;
}

/**
* Starts transaction on the current connection.
*/
async startTransaction(isolationLevel?: IsolationLevel): Promise<void> {
if (this.isTransactionActive)
throw new TransactionAlreadyStartedError();

this.isTransactionActive = true;
await this.driver.client.startTransaction();
}

/**
* Commits transaction.
* Error will be thrown if transaction was not started.
*/
async commitTransaction(): Promise<void> {
if (!this.isTransactionActive)
throw new TransactionNotStartedError();

await this.driver.client.commitTransaction();
this.isTransactionActive = false;
}

/**
* Rollbacks transaction.
* Error will be thrown if transaction was not started.
*/
async rollbackTransaction(): Promise<void> {
if (!this.isTransactionActive)
throw new TransactionNotStartedError();

await this.driver.client.rollbackTransaction();
this.isTransactionActive = false;
}

/**
* Executes a given SQL query.
*/
async query(query: string, parameters?: any[]): Promise<any> {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();

const result = await this.driver.client.query(query, parameters);

if (result.records) {
return result.records;
}

return result;
}
}
118 changes: 117 additions & 1 deletion src/driver/postgres/PostgresDriver.ts
Expand Up @@ -19,6 +19,8 @@ import {PostgresConnectionCredentialsOptions} from "./PostgresConnectionCredenti
import {EntityMetadata} from "../../metadata/EntityMetadata";
import {OrmUtils} from "../../util/OrmUtils";
import {ApplyValueTransformers} from "../../util/ApplyValueTransformers";
import {AuroraDataApiPostgresConnectionOptions} from "../aurora-data-api-pg/AuroraDataApiPostgresConnectionOptions";
import {AuroraDataApiPostgresQueryRunner} from "../aurora-data-api-pg/AuroraDataApiPostgresQueryRunner";

/**
* Organizes communication with PostgreSQL DBMS.
Expand Down Expand Up @@ -248,7 +250,11 @@ export class PostgresDriver implements Driver {
// Constructor
// -------------------------------------------------------------------------

constructor(connection: Connection) {
constructor(connection?: Connection) {
if (!connection) {
return;
}

this.connection = connection;
this.options = connection.options as PostgresConnectionOptions;
this.isReplicated = this.options.replication ? true : false;
Expand Down Expand Up @@ -972,3 +978,113 @@ export class PostgresDriver implements Driver {
}

}

abstract class PostgresWrapper extends PostgresDriver {
options: any;

abstract createQueryRunner(mode: "master"|"slave"): any;
}

/**
* Organizes communication with PostgreSQL DBMS.
*/
export class AuroraDataApiPostgresDriver extends PostgresWrapper {

// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------

/**
* Connection used by driver.
*/
connection: Connection;

/**
* Aurora Data API underlying library.
*/
DataApiDriver: any;

client: any;

// -------------------------------------------------------------------------
// Public Implemented Properties
// -------------------------------------------------------------------------

/**
* Connection options.
*/
options: AuroraDataApiPostgresConnectionOptions;

/**
* Master database used to perform all write queries.
*/
database?: string;

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------

constructor(connection: Connection) {
super();
this.connection = connection;
this.options = connection.options as AuroraDataApiPostgresConnectionOptions;
this.isReplicated = false;

// load data-api package
this.loadDependencies();

this.client = new this.DataApiDriver(
this.options.region,
this.options.secretArn,
this.options.resourceArn,
this.options.database,
(query: string, parameters?: any[]) => this.connection.logger.logQuery(query, parameters),
);
}

// -------------------------------------------------------------------------
// Public Implemented Methods
// -------------------------------------------------------------------------

/**
* Performs connection to the database.
* Based on pooling options, it can either create connection immediately,
* either create a pool and create connection when needed.
*/
async connect(): Promise<void> {
}

/**
* Closes connection with database.
*/
async disconnect(): Promise<void> {
}

/**
* Creates a query runner used to execute database queries.
*/
createQueryRunner(mode: "master"|"slave" = "master") {
return new AuroraDataApiPostgresQueryRunner(this, mode);
}

// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------

/**
* If driver dependency is not given explicitly, then try to load it via "require".
*/
protected loadDependencies(): void {
const { pg } = PlatformTools.load("typeorm-aurora-data-api-driver");

this.DataApiDriver = pg;
}

/**
* Executes given query.
*/
protected executeQuery(connection: any, query: string) {
return this.client.query(query);
}

}
1 change: 1 addition & 0 deletions src/driver/types/DatabaseType.ts
Expand Up @@ -16,4 +16,5 @@ export type DatabaseType =
"mssql"|
"mongodb"|
"aurora-data-api"|
"aurora-data-api-pg"|
"expo";
4 changes: 2 additions & 2 deletions src/error/MissingDriverError.ts
Expand Up @@ -7,7 +7,7 @@ export class MissingDriverError extends Error {
constructor(driverType: string) {
super();
Object.setPrototypeOf(this, MissingDriverError.prototype);
this.message = `Wrong driver: "${driverType}" given. Supported drivers are: "cordova", "expo", "mariadb", "mongodb", "mssql", "mysql", "oracle", "postgres", "sqlite", "sqljs", "react-native".`;
this.message = `Wrong driver: "${driverType}" given. Supported drivers are: "cordova", "expo", "mariadb", "mongodb", "mssql", "mysql", "oracle", "postgres", "sqlite", "sqljs", "react-native", "aurora-data-api", "aurora-data-api-pg".`;
}

}
}
Expand Up @@ -135,4 +135,4 @@ describe("query builder > order-by", () => {
expect(loadedPost2!.num2).to.be.equal(2);
})));

});
});