From 9abab82d51e3b76d717f2f473cbf3e4d96f51488 Mon Sep 17 00:00:00 2001 From: Arseny Yankovsky Date: Tue, 21 Jul 2020 04:49:35 +0200 Subject: [PATCH] fix: pass formatOptions to Data API Client, fix extensions (#6404) * fix(aurora): pass formatOptions to Data API Client, fix UUID type support and all other extensions * fix(aurora): refactored the code to avoid duplication (#1) * fix(aurora): refactored the code to avoid duplication --- src/driver/DriverFactory.ts | 3 +- .../AuroraDataApiPostgresConnectionOptions.ts | 2 + .../AuroraDataApiConnectionOptions.ts | 2 + .../aurora-data-api/AuroraDataApiDriver.ts | 3 +- src/driver/postgres/PostgresDriver.ts | 149 +++++++++++------- 5 files changed, 101 insertions(+), 58 deletions(-) diff --git a/src/driver/DriverFactory.ts b/src/driver/DriverFactory.ts index 3d6456b3bc..e3eed40e59 100644 --- a/src/driver/DriverFactory.ts +++ b/src/driver/DriverFactory.ts @@ -9,13 +9,12 @@ import {ReactNativeDriver} from "./react-native/ReactNativeDriver"; import {NativescriptDriver} from "./nativescript/NativescriptDriver"; import {SqljsDriver} from "./sqljs/SqljsDriver"; import {MysqlDriver} from "./mysql/MysqlDriver"; -import {PostgresDriver} from "./postgres/PostgresDriver"; +import {PostgresDriver, AuroraDataApiPostgresDriver} from "./postgres/PostgresDriver"; import {ExpoDriver} from "./expo/ExpoDriver"; 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"; import {BetterSqlite3Driver} from "./better-sqlite3/BetterSqlite3Driver"; /** diff --git a/src/driver/aurora-data-api-pg/AuroraDataApiPostgresConnectionOptions.ts b/src/driver/aurora-data-api-pg/AuroraDataApiPostgresConnectionOptions.ts index f8d30d4f55..18078aed82 100644 --- a/src/driver/aurora-data-api-pg/AuroraDataApiPostgresConnectionOptions.ts +++ b/src/driver/aurora-data-api-pg/AuroraDataApiPostgresConnectionOptions.ts @@ -33,4 +33,6 @@ export interface AuroraDataApiPostgresConnectionOptions extends BaseConnectionOp readonly poolErrorHandler?: (err: any) => any; readonly serviceConfigOptions?: { [key: string]: any }; + + readonly formatOptions?: { [key: string]: any }; } diff --git a/src/driver/aurora-data-api/AuroraDataApiConnectionOptions.ts b/src/driver/aurora-data-api/AuroraDataApiConnectionOptions.ts index d9468d1e36..5e5722c227 100644 --- a/src/driver/aurora-data-api/AuroraDataApiConnectionOptions.ts +++ b/src/driver/aurora-data-api/AuroraDataApiConnectionOptions.ts @@ -23,6 +23,8 @@ export interface AuroraDataApiConnectionOptions extends BaseConnectionOptions, A readonly serviceConfigOptions?: { [key: string]: any }; // pass optional AWS.ConfigurationOptions here + readonly formatOptions?: { [key: string]: any }; + /** * Use spatial functions like GeomFromText and AsText which are removed in MySQL 8. * (Default: true) diff --git a/src/driver/aurora-data-api/AuroraDataApiDriver.ts b/src/driver/aurora-data-api/AuroraDataApiDriver.ts index 89b93ccc03..901ff22435 100644 --- a/src/driver/aurora-data-api/AuroraDataApiDriver.ts +++ b/src/driver/aurora-data-api/AuroraDataApiDriver.ts @@ -306,7 +306,8 @@ export class AuroraDataApiDriver implements Driver { this.options.resourceArn, this.options.database, (query: string, parameters?: any[]) => this.connection.logger.logQuery(query, parameters), - this.options.serviceConfigOptions + this.options.serviceConfigOptions, + this.options.formatOptions, ); // validate options to make sure everything is set diff --git a/src/driver/postgres/PostgresDriver.ts b/src/driver/postgres/PostgresDriver.ts index b45ac1c935..5aa25b585d 100644 --- a/src/driver/postgres/PostgresDriver.ts +++ b/src/driver/postgres/PostgresDriver.ts @@ -301,6 +301,76 @@ export class PostgresDriver implements Driver { * Makes any action after connection (e.g. create extensions in Postgres driver). */ async afterConnect(): Promise { + const extensionsMetadata = await this.checkMetadataForExtensions(); + + if (extensionsMetadata.hasExtensions) { + await Promise.all([this.master, ...this.slaves].map(pool => { + return new Promise((ok, fail) => { + pool.connect(async (err: any, connection: any, release: Function) => { + await this.enableExtensions(extensionsMetadata, connection); + if (err) return fail(err); + release(); + ok(); + }); + }); + })); + } + + return Promise.resolve(); + } + + protected async enableExtensions(extensionsMetadata: any, connection: any) { + const { logger } = this.connection; + + const { + hasUuidColumns, + hasCitextColumns, + hasHstoreColumns, + hasCubeColumns, + hasGeometryColumns, + hasExclusionConstraints, + } = extensionsMetadata; + + if (hasUuidColumns) + try { + await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "${this.options.uuidExtension || "uuid-ossp"}"`); + } catch (_) { + logger.log("warn", `At least one of the entities has uuid column, but the '${this.options.uuidExtension || "uuid-ossp"}' extension cannot be installed automatically. Please install it manually using superuser rights, or select another uuid extension.`); + } + if (hasCitextColumns) + try { + await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "citext"`); + } catch (_) { + logger.log("warn", "At least one of the entities has citext column, but the 'citext' extension cannot be installed automatically. Please install it manually using superuser rights"); + } + if (hasHstoreColumns) + try { + await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "hstore"`); + } catch (_) { + logger.log("warn", "At least one of the entities has hstore column, but the 'hstore' extension cannot be installed automatically. Please install it manually using superuser rights"); + } + if (hasGeometryColumns) + try { + await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "postgis"`); + } catch (_) { + logger.log("warn", "At least one of the entities has a geometry column, but the 'postgis' extension cannot be installed automatically. Please install it manually using superuser rights"); + } + if (hasCubeColumns) + try { + await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "cube"`); + } catch (_) { + logger.log("warn", "At least one of the entities has a cube column, but the 'cube' extension cannot be installed automatically. Please install it manually using superuser rights"); + } + if (hasExclusionConstraints) + try { + // The btree_gist extension provides operator support in PostgreSQL exclusion constraints + await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "btree_gist"`); + } catch (_) { + logger.log("warn", "At least one of the entities has an exclusion constraint, but the 'btree_gist' extension cannot be installed automatically. Please install it manually using superuser rights"); + } + } + + protected async checkMetadataForExtensions() { const hasUuidColumns = this.connection.entityMetadatas.some(metadata => { return metadata.generatedColumns.filter(column => column.generationStrategy === "uuid").length > 0; }); @@ -319,57 +389,16 @@ export class PostgresDriver implements Driver { const hasExclusionConstraints = this.connection.entityMetadatas.some(metadata => { return metadata.exclusions.length > 0; }); - if (hasUuidColumns || hasCitextColumns || hasHstoreColumns || hasGeometryColumns || hasCubeColumns || hasExclusionConstraints) { - await Promise.all([this.master, ...this.slaves].map(pool => { - return new Promise((ok, fail) => { - pool.connect(async (err: any, connection: any, release: Function) => { - const { logger } = this.connection; - if (err) return fail(err); - if (hasUuidColumns) - try { - await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "${this.options.uuidExtension || "uuid-ossp"}"`); - } catch (_) { - logger.log("warn", `At least one of the entities has uuid column, but the '${this.options.uuidExtension || "uuid-ossp"}' extension cannot be installed automatically. Please install it manually using superuser rights, or select another uuid extension.`); - } - if (hasCitextColumns) - try { - await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "citext"`); - } catch (_) { - logger.log("warn", "At least one of the entities has citext column, but the 'citext' extension cannot be installed automatically. Please install it manually using superuser rights"); - } - if (hasHstoreColumns) - try { - await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "hstore"`); - } catch (_) { - logger.log("warn", "At least one of the entities has hstore column, but the 'hstore' extension cannot be installed automatically. Please install it manually using superuser rights"); - } - if (hasGeometryColumns) - try { - await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "postgis"`); - } catch (_) { - logger.log("warn", "At least one of the entities has a geometry column, but the 'postgis' extension cannot be installed automatically. Please install it manually using superuser rights"); - } - if (hasCubeColumns) - try { - await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "cube"`); - } catch (_) { - logger.log("warn", "At least one of the entities has a cube column, but the 'cube' extension cannot be installed automatically. Please install it manually using superuser rights"); - } - if (hasExclusionConstraints) - try { - // The btree_gist extension provides operator support in PostgreSQL exclusion constraints - await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "btree_gist"`); - } catch (_) { - logger.log("warn", "At least one of the entities has an exclusion constraint, but the 'btree_gist' extension cannot be installed automatically. Please install it manually using superuser rights"); - } - release(); - ok(); - }); - }); - })); - } - return Promise.resolve(); + return { + hasUuidColumns, + hasCitextColumns, + hasHstoreColumns, + hasCubeColumns, + hasGeometryColumns, + hasExclusionConstraints, + hasExtensions: hasUuidColumns || hasCitextColumns || hasHstoreColumns || hasGeometryColumns || hasCubeColumns || hasExclusionConstraints, + }; } /** @@ -395,7 +424,7 @@ export class PostgresDriver implements Driver { /** * Creates a query runner used to execute database queries. */ - createQueryRunner(mode: "master"|"slave" = "master") { + createQueryRunner(mode: "master"|"slave" = "master"): QueryRunner { return new PostgresQueryRunner(this, mode); } @@ -987,9 +1016,6 @@ abstract class PostgresWrapper extends PostgresDriver { abstract createQueryRunner(mode: "master"|"slave"): any; } -/** - * Organizes communication with PostgreSQL DBMS. - */ export class AuroraDataApiPostgresDriver extends PostgresWrapper { // ------------------------------------------------------------------------- @@ -1041,7 +1067,8 @@ export class AuroraDataApiPostgresDriver extends PostgresWrapper { this.options.resourceArn, this.options.database, (query: string, parameters?: any[]) => this.connection.logger.logQuery(query, parameters), - this.options.serviceConfigOptions + this.options.serviceConfigOptions, + this.options.formatOptions, ); } @@ -1090,4 +1117,16 @@ export class AuroraDataApiPostgresDriver extends PostgresWrapper { return this.client.query(query); } + /** + * Makes any action after connection (e.g. create extensions in Postgres driver). + */ + async afterConnect(): Promise { + const extensionsMetadata = await this.checkMetadataForExtensions(); + + if (extensionsMetadata.hasExtensions) { + await this.enableExtensions(extensionsMetadata, this.connection); + } + + return Promise.resolve(); + } }