Skip to content

Commit

Permalink
feat: Aurora Data API - Postgres Support (#5651)
Browse files Browse the repository at this point in the history
* Data API Postgres WIP

* Refactored the code to be more supportable
  • Loading branch information
ArsenyYankovsky committed May 16, 2020
1 parent 2ab88c2 commit e584297
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 4 deletions.
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);
})));

});
});

0 comments on commit e584297

Please sign in to comment.