mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
feat(sap): use the native driver for connection pooling (#11520)
* feat(sap): use the native driver for connection pooling * Add pool error handler
This commit is contained in:
parent
5904ac3db2
commit
aebc7ebc67
@ -203,9 +203,7 @@ await timber.remove()
|
||||
- **SAP Hana**
|
||||
|
||||
```
|
||||
npm config set @sap:registry https://npm.sap.com
|
||||
npm i @sap/hana-client
|
||||
npm i hdb-pool
|
||||
```
|
||||
|
||||
##### TypeScript 配置
|
||||
|
||||
@ -215,11 +215,8 @@ await timber.remove()
|
||||
|
||||
```
|
||||
npm install @sap/hana-client
|
||||
npm install hdb-pool
|
||||
```
|
||||
|
||||
_SAP Hana support made possible by the sponsorship of [Neptune Software](https://www.neptune-software.com/)._
|
||||
|
||||
- for **Google Cloud Spanner**
|
||||
|
||||
```
|
||||
|
||||
@ -176,11 +176,8 @@ await timber.remove()
|
||||
|
||||
```
|
||||
npm i @sap/hana-client
|
||||
npm i hdb-pool
|
||||
```
|
||||
|
||||
_[Neptune Software](https://www.neptune-software.com/)의 후원으로 SAP Hana 지원이 가능해졌다._
|
||||
|
||||
- **MongoDB** (experimental)의 경우
|
||||
|
||||
`npm install mongodb@^5.2.0 --save`
|
||||
|
||||
@ -544,6 +544,24 @@ The following TNS connection string will be used in the next explanations:
|
||||
- `sid` - The System Identifier (SID) identifies a specific database instance. For example, "sales".
|
||||
- `serviceName` - The Service Name is an identifier of a database service. For example, `sales.us.example.com`.
|
||||
|
||||
## `sap` data source options
|
||||
|
||||
- `host` - The hostname of the SAP HANA server. For example, `"localhost"`.
|
||||
- `port` - The port number of the SAP HANA server. For example, `30015`.
|
||||
- `username` - The username to connect to the SAP HANA server. For example, `"SYSTEM"`.
|
||||
- `password` - The password to connect to the SAP HANA server. For example, `"password"`.
|
||||
- `database` - The name of the database to connect to. For example, `"HXE"`.
|
||||
- `encrypt` - Whether to encrypt the connection. For example, `true`.
|
||||
- `sslValidateCertificate` - Whether to validate the SSL certificate. For example, `true`.
|
||||
- `key`, `cert` and `ca` - Private key, public certificate and certificate authority for the encrypted connection.
|
||||
- `pool` — Connection pool configuration object:
|
||||
- `maxConnectedOrPooled` (number) — Max active or idle connections in the pool (default: 10).
|
||||
- `maxPooledIdleTime` (seconds) — Time before an idle connection is closed (default: 30).
|
||||
- `pingCheck` (boolean) — Whether to validate connections before use (default: false).
|
||||
- `poolCapacity` (number) — Maximum number of connections to be kept available (default: no limit).
|
||||
|
||||
See the official documentation of SAP HANA Client for more details as well as the `extra` properties: [Node.js Connection Properties](https://help.sap.com/docs/SAP_HANA_CLIENT/f1b440ded6144a54ada97ff95dac7adf/4fe9978ebac44f35b9369ef5a4a26f4c.html).
|
||||
|
||||
## Data Source Options example
|
||||
|
||||
Here is a small example of data source options for mysql:
|
||||
|
||||
@ -193,18 +193,11 @@ await timber.remove()
|
||||
|
||||
- for **SAP Hana**
|
||||
|
||||
```
|
||||
npm install @sap/hana-client
|
||||
npm install hdb-pool
|
||||
```
|
||||
|
||||
_SAP Hana support made possible by the sponsorship of [Neptune Software](https://www.neptune-software.com/)._
|
||||
`npm install @sap/hana-client --save`
|
||||
|
||||
- for **Google Cloud Spanner**
|
||||
|
||||
```
|
||||
npm install @google-cloud/spanner --save
|
||||
```
|
||||
`npm install @google-cloud/spanner --save`
|
||||
|
||||
Provide authentication credentials to your application code
|
||||
by setting the environment variable `GOOGLE_APPLICATION_CREDENTIALS`:
|
||||
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
@ -58,7 +58,6 @@
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"gulpclass": "^0.2.0",
|
||||
"hdb-pool": "^0.1.6",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^15.5.2",
|
||||
"mocha": "^10.8.2",
|
||||
@ -95,9 +94,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0",
|
||||
"@sap/hana-client": "^2.12.25",
|
||||
"@sap/hana-client": "^2.14.22",
|
||||
"better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
||||
"hdb-pool": "^0.1.6",
|
||||
"ioredis": "^5.0.4",
|
||||
"mongodb": "^5.8.0 || ^6.0.0",
|
||||
"mssql": "^9.1.1 || ^10.0.1 || ^11.0.1",
|
||||
@ -123,9 +121,6 @@
|
||||
"better-sqlite3": {
|
||||
"optional": true
|
||||
},
|
||||
"hdb-pool": {
|
||||
"optional": true
|
||||
},
|
||||
"ioredis": {
|
||||
"optional": true
|
||||
},
|
||||
@ -8384,17 +8379,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hdb-pool": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/hdb-pool/-/hdb-pool-0.1.6.tgz",
|
||||
"integrity": "sha512-8VZOLn1EHamm1NmTFQj2iqjVcfonYIsD7F5DU2bz2N+gF+knp6/MbAVeRXkJtya717IBkPeA5iv0/1iPuYo4ZA==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
|
||||
@ -136,7 +136,6 @@
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"gulpclass": "^0.2.0",
|
||||
"hdb-pool": "^0.1.6",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^15.5.2",
|
||||
"mocha": "^10.8.2",
|
||||
@ -167,9 +166,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0",
|
||||
"@sap/hana-client": "^2.12.25",
|
||||
"@sap/hana-client": "^2.14.22",
|
||||
"better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
||||
"hdb-pool": "^0.1.6",
|
||||
"ioredis": "^5.0.4",
|
||||
"mongodb": "^5.8.0 || ^6.0.0",
|
||||
"mssql": "^9.1.1 || ^10.0.1 || ^11.0.1",
|
||||
@ -195,9 +193,6 @@
|
||||
"better-sqlite3": {
|
||||
"optional": true
|
||||
},
|
||||
"hdb-pool": {
|
||||
"optional": true
|
||||
},
|
||||
"ioredis": {
|
||||
"optional": true
|
||||
},
|
||||
|
||||
@ -19,13 +19,12 @@ export interface SapConnectionOptions
|
||||
|
||||
/**
|
||||
* The driver objects
|
||||
* This defaults to require("hdb-pool")
|
||||
* This defaults to require("@sap/hana-client")
|
||||
*/
|
||||
readonly driver?: any
|
||||
|
||||
/**
|
||||
* The driver objects
|
||||
* This defaults to require("@sap/hana-client")
|
||||
* @deprecated Use {@link driver} instead.
|
||||
*/
|
||||
readonly hanaClientDriver?: any
|
||||
|
||||
@ -33,30 +32,64 @@ export interface SapConnectionOptions
|
||||
* Pool options.
|
||||
*/
|
||||
readonly pool?: {
|
||||
/**
|
||||
* Maximum number of open connections created by the pool, each of which
|
||||
* may be in the pool waiting to be reused or may no longer be in the
|
||||
* pool and actively being used (default: 10).
|
||||
*/
|
||||
readonly maxConnectedOrPooled?: number
|
||||
|
||||
/**
|
||||
* Defines the maximum time, in seconds, that connections are allowed to
|
||||
* remain in the pool before being marked for eviction (default: 30).
|
||||
*/
|
||||
readonly maxPooledIdleTime?: number
|
||||
|
||||
/**
|
||||
* Determines whether or not the pooled connection should be tested for
|
||||
* viability before being reused (default: false).
|
||||
*/
|
||||
readonly pingCheck?: boolean
|
||||
|
||||
/**
|
||||
* Maximum number of connections allowed to be in the pool, waiting to
|
||||
* be reused (default: 0, no limit).
|
||||
*/
|
||||
readonly poolCapacity?: number
|
||||
|
||||
/**
|
||||
* Max number of connections.
|
||||
* @deprecated Use {@link maxConnectedOrPooled} instead.
|
||||
*/
|
||||
readonly max?: number
|
||||
|
||||
/**
|
||||
* Minimum number of connections.
|
||||
* @deprecated Obsolete, no alternative exists.
|
||||
*/
|
||||
readonly min?: number
|
||||
|
||||
/**
|
||||
* Maximum number of waiting requests allowed. (default=0, no limit).
|
||||
* Maximum number of waiting requests allowed.
|
||||
* @deprecated Obsolete, no alternative exists.
|
||||
*/
|
||||
readonly maxWaitingRequests?: number
|
||||
|
||||
/**
|
||||
* Max milliseconds a request will wait for a resource before timing out. (default=5000)
|
||||
* Max milliseconds a request will wait for a resource before timing out.
|
||||
* @deprecated Obsolete, no alternative exists.
|
||||
*/
|
||||
readonly requestTimeout?: number
|
||||
|
||||
/**
|
||||
* How often to run resource timeout checks. (default=0, disabled)
|
||||
* How often to run resource timeout checks.
|
||||
* @deprecated Obsolete, no alternative exists.
|
||||
*/
|
||||
readonly checkInterval?: number
|
||||
|
||||
/**
|
||||
* Idle timeout
|
||||
* Idle timeout (in milliseconds).
|
||||
* @deprecated Use {@link maxPooledIdleTime} (in seconds) instead .
|
||||
*/
|
||||
readonly idleTimeout?: number
|
||||
|
||||
@ -66,6 +99,4 @@ export interface SapConnectionOptions
|
||||
*/
|
||||
readonly poolErrorHandler?: (err: any) => any
|
||||
}
|
||||
|
||||
readonly poolSize?: never
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { promisify } from "node:util"
|
||||
import {
|
||||
ColumnType,
|
||||
ConnectionIsNotSetError,
|
||||
@ -13,21 +14,20 @@ import { TypeORMError } from "../../error/TypeORMError"
|
||||
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
|
||||
import { PlatformTools } from "../../platform/PlatformTools"
|
||||
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
|
||||
import { View } from "../../schema-builder/view/View"
|
||||
import { ApplyValueTransformers } from "../../util/ApplyValueTransformers"
|
||||
import { DateUtils } from "../../util/DateUtils"
|
||||
import { InstanceChecker } from "../../util/InstanceChecker"
|
||||
import { OrmUtils } from "../../util/OrmUtils"
|
||||
import { Driver } from "../Driver"
|
||||
import { DriverUtils } from "../DriverUtils"
|
||||
import { CteCapabilities } from "../types/CteCapabilities"
|
||||
import { DataTypeDefaults } from "../types/DataTypeDefaults"
|
||||
import { MappedColumnTypes } from "../types/MappedColumnTypes"
|
||||
import { ReplicationMode } from "../types/ReplicationMode"
|
||||
import { UpsertType } from "../types/UpsertType"
|
||||
import { SapConnectionOptions } from "./SapConnectionOptions"
|
||||
import { SapQueryRunner } from "./SapQueryRunner"
|
||||
import { ReplicationMode } from "../types/ReplicationMode"
|
||||
import { DriverUtils } from "../DriverUtils"
|
||||
import { View } from "../../schema-builder/view/View"
|
||||
import { InstanceChecker } from "../../util/InstanceChecker"
|
||||
import { UpsertType } from "../types/UpsertType"
|
||||
|
||||
/**
|
||||
* Organizes communication with SAP Hana DBMS.
|
||||
*
|
||||
@ -44,24 +44,24 @@ export class SapDriver implements Driver {
|
||||
connection: DataSource
|
||||
|
||||
/**
|
||||
* Hana Pool instance.
|
||||
* SAP HANA Client Pool instance.
|
||||
*/
|
||||
client: any
|
||||
|
||||
/**
|
||||
* Hana Client streaming extension.
|
||||
* SAP HANA Client streaming extension.
|
||||
*/
|
||||
streamClient: any
|
||||
|
||||
/**
|
||||
* Pool for master database.
|
||||
*/
|
||||
master: any
|
||||
|
||||
/**
|
||||
* Pool for slave databases.
|
||||
* Used in replication.
|
||||
* Function handling errors thrown by drivers pool.
|
||||
*/
|
||||
slaves: any[] = []
|
||||
poolErrorHandler: (error: any) => void
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Implemented Properties
|
||||
@ -225,7 +225,7 @@ export class SapDriver implements Driver {
|
||||
|
||||
/**
|
||||
* Max length allowed by SAP HANA for aliases (identifiers).
|
||||
* @see https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.03/en-US/20a760537519101497e3cfe07b348f3c.html
|
||||
* @see https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/system-limitations
|
||||
*/
|
||||
maxAliasLength = 128
|
||||
|
||||
@ -259,54 +259,62 @@ export class SapDriver implements Driver {
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
// HANA connection info
|
||||
const dbParams = {
|
||||
hostName: this.options.host,
|
||||
const connectionOptions: any = {
|
||||
host: this.options.host,
|
||||
port: this.options.port,
|
||||
userName: this.options.username,
|
||||
user: this.options.username,
|
||||
password: this.options.password,
|
||||
...this.options.extra,
|
||||
database: this.options.database,
|
||||
currentSchema: this.options.schema,
|
||||
encrypt: this.options.encrypt,
|
||||
sslValidateCertificate: this.options.sslValidateCertificate,
|
||||
key: this.options.key,
|
||||
cert: this.options.cert,
|
||||
ca: this.options.ca,
|
||||
}
|
||||
|
||||
if (this.options.database) dbParams.databaseName = this.options.database
|
||||
if (this.options.schema) dbParams.currentSchema = this.options.schema
|
||||
if (this.options.encrypt) dbParams.encrypt = this.options.encrypt
|
||||
if (this.options.sslValidateCertificate)
|
||||
dbParams.validateCertificate = this.options.sslValidateCertificate
|
||||
if (this.options.key) dbParams.key = this.options.key
|
||||
if (this.options.cert) dbParams.cert = this.options.cert
|
||||
if (this.options.ca) dbParams.ca = this.options.ca
|
||||
Object.keys(connectionOptions).forEach((key) => {
|
||||
if (connectionOptions[key] === undefined) {
|
||||
delete connectionOptions[key]
|
||||
}
|
||||
})
|
||||
Object.assign(connectionOptions, this.options.extra ?? {})
|
||||
|
||||
// pool options
|
||||
const options: any = {
|
||||
min:
|
||||
this.options.pool && this.options.pool.min
|
||||
? this.options.pool.min
|
||||
: 1,
|
||||
max:
|
||||
this.options.pool && this.options.pool.max
|
||||
? this.options.pool.max
|
||||
: 10,
|
||||
const poolOptions: any = {
|
||||
maxConnectedOrPooled:
|
||||
this.options.pool?.maxConnectedOrPooled ??
|
||||
this.options.pool?.max ??
|
||||
this.options.poolSize ??
|
||||
10,
|
||||
maxPooledIdleTime:
|
||||
this.options.pool?.maxPooledIdleTime ??
|
||||
(this.options.pool?.idleTimeout
|
||||
? this.options.pool.idleTimeout / 1000
|
||||
: 30),
|
||||
}
|
||||
if (this.options.pool?.pingCheck) {
|
||||
poolOptions.pingCheck = this.options.pool.pingCheck
|
||||
}
|
||||
if (this.options.pool?.poolCapacity) {
|
||||
poolOptions.poolCapacity = this.options.pool.poolCapacity
|
||||
}
|
||||
|
||||
if (this.options.pool && this.options.pool.checkInterval)
|
||||
options.checkInterval = this.options.pool.checkInterval
|
||||
if (this.options.pool && this.options.pool.maxWaitingRequests)
|
||||
options.maxWaitingRequests = this.options.pool.maxWaitingRequests
|
||||
if (this.options.pool && this.options.pool.requestTimeout)
|
||||
options.requestTimeout = this.options.pool.requestTimeout
|
||||
if (this.options.pool && this.options.pool.idleTimeout)
|
||||
options.idleTimeout = this.options.pool.idleTimeout
|
||||
|
||||
const { logger } = this.connection
|
||||
|
||||
const poolErrorHandler =
|
||||
options.poolErrorHandler ||
|
||||
((error: any) =>
|
||||
logger.log("warn", `SAP Hana pool raised an error. ${error}`))
|
||||
this.client.eventEmitter.on("poolError", poolErrorHandler)
|
||||
this.poolErrorHandler =
|
||||
this.options.pool?.poolErrorHandler ??
|
||||
((error: Error) => {
|
||||
this.connection.logger.log(
|
||||
"warn",
|
||||
`SAP HANA pool raised an error: ${error}`,
|
||||
)
|
||||
})
|
||||
|
||||
// create the pool
|
||||
this.master = this.client.createPool(dbParams, options)
|
||||
try {
|
||||
this.master = this.client.createPool(connectionOptions, poolOptions)
|
||||
} catch (error) {
|
||||
this.poolErrorHandler(error)
|
||||
throw error
|
||||
}
|
||||
|
||||
const queryRunner = this.createQueryRunner("master")
|
||||
|
||||
@ -338,7 +346,40 @@ export class SapDriver implements Driver {
|
||||
}
|
||||
|
||||
this.master = undefined
|
||||
await pool.clear()
|
||||
try {
|
||||
await promisify(pool.clear).call(pool)
|
||||
} catch (error) {
|
||||
this.poolErrorHandler(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a new database connection to a master server.
|
||||
* Used for replication.
|
||||
* If replication is not setup then returns default connection's database connection.
|
||||
*/
|
||||
async obtainMasterConnection(): Promise<any> {
|
||||
const pool = this.master
|
||||
if (!pool) {
|
||||
throw new TypeORMError("Driver not Connected")
|
||||
}
|
||||
|
||||
try {
|
||||
return await promisify(pool.getConnection).call(pool)
|
||||
} catch (error) {
|
||||
this.poolErrorHandler(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a new database connection to a slave server.
|
||||
* Used for replication.
|
||||
* If replication is not setup then returns master (default) connection's database connection.
|
||||
*/
|
||||
async obtainSlaveConnection(): Promise<any> {
|
||||
return this.obtainMasterConnection()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -724,28 +765,6 @@ export class SapDriver implements Driver {
|
||||
return type
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a new database connection to a master server.
|
||||
* Used for replication.
|
||||
* If replication is not setup then returns default connection's database connection.
|
||||
*/
|
||||
obtainMasterConnection(): Promise<any> {
|
||||
if (!this.master) {
|
||||
throw new TypeORMError("Driver not Connected")
|
||||
}
|
||||
|
||||
return this.master.getConnection()
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a new database connection to a slave server.
|
||||
* Used for replication.
|
||||
* If replication is not setup then returns master (default) connection's database connection.
|
||||
*/
|
||||
obtainSlaveConnection(): Promise<any> {
|
||||
return this.obtainMasterConnection()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates generated map of values generated or returned by database after INSERT query.
|
||||
*/
|
||||
@ -851,22 +870,19 @@ export class SapDriver implements Driver {
|
||||
* If driver dependency is not given explicitly, then try to load it via "require".
|
||||
*/
|
||||
protected loadDependencies(): void {
|
||||
try {
|
||||
const client = this.options.driver || PlatformTools.load("hdb-pool")
|
||||
const client = this.options.driver ?? this.options.hanaClientDriver
|
||||
if (client) {
|
||||
this.client = client
|
||||
} catch (e) {
|
||||
// todo: better error for browser env
|
||||
throw new DriverPackageNotInstalledError("SAP Hana", "hdb-pool")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.options.hanaClientDriver) {
|
||||
PlatformTools.load("@sap/hana-client")
|
||||
this.streamClient = PlatformTools.load(
|
||||
"@sap/hana-client/extension/Stream",
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
this.client = PlatformTools.load("@sap/hana-client")
|
||||
this.streamClient = PlatformTools.load(
|
||||
"@sap/hana-client/extension/Stream",
|
||||
)
|
||||
} catch {
|
||||
// todo: better error for browser env
|
||||
throw new DriverPackageNotInstalledError(
|
||||
"SAP Hana",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { promisify } from "util"
|
||||
import { promisify } from "node:util"
|
||||
import { ObjectLiteral } from "../../common/ObjectLiteral"
|
||||
import { QueryFailedError, TypeORMError } from "../../error"
|
||||
import { QueryRunnerAlreadyReleasedError } from "../../error/QueryRunnerAlreadyReleasedError"
|
||||
@ -85,14 +85,20 @@ export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
|
||||
* Releases used database connection.
|
||||
* You cannot use query runner methods once its released.
|
||||
*/
|
||||
release(): Promise<void> {
|
||||
async release(): Promise<void> {
|
||||
this.isReleased = true
|
||||
|
||||
if (this.databaseConnection) {
|
||||
return this.driver.master.release(this.databaseConnection)
|
||||
// return the connection back to the pool
|
||||
try {
|
||||
await promisify(this.databaseConnection.disconnect).call(
|
||||
this.databaseConnection,
|
||||
)
|
||||
} catch (error) {
|
||||
this.driver.poolErrorHandler(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,14 +174,12 @@ export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
|
||||
*/
|
||||
async setAutoCommit(options: { status: "on" | "off" }) {
|
||||
const connection = await this.connect()
|
||||
|
||||
const execute = promisify(connection.exec.bind(connection))
|
||||
|
||||
connection.setAutoCommit(options.status === "on")
|
||||
|
||||
const query = `SET TRANSACTION AUTOCOMMIT DDL ${options.status.toUpperCase()};`
|
||||
const query = `SET TRANSACTION AUTOCOMMIT DDL ${options.status.toUpperCase()}`
|
||||
this.driver.connection.logger.logQuery(query, [], this)
|
||||
try {
|
||||
await execute(query)
|
||||
await promisify(connection.exec).call(connection, query)
|
||||
} catch (error) {
|
||||
throw new QueryFailedError(query, [], error)
|
||||
}
|
||||
@ -270,26 +274,20 @@ export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
|
||||
if (isInsertQuery) {
|
||||
const lastIdQuery = `SELECT CURRENT_IDENTITY_VALUE() FROM "SYS"."DUMMY"`
|
||||
this.driver.connection.logger.logQuery(lastIdQuery, [], this)
|
||||
const identityValueResult = await new Promise<any>(
|
||||
(ok, fail) => {
|
||||
databaseConnection.exec(
|
||||
lastIdQuery,
|
||||
(err: any, raw: any) =>
|
||||
err
|
||||
? fail(
|
||||
new QueryFailedError(
|
||||
lastIdQuery,
|
||||
[],
|
||||
err,
|
||||
),
|
||||
)
|
||||
: ok(raw),
|
||||
)
|
||||
},
|
||||
)
|
||||
try {
|
||||
const identityValueResult: [
|
||||
{ "CURRENT_IDENTITY_VALUE()": unknown },
|
||||
] = await promisify(databaseConnection.exec).call(
|
||||
databaseConnection,
|
||||
lastIdQuery,
|
||||
)
|
||||
|
||||
result.raw = identityValueResult[0]["CURRENT_IDENTITY_VALUE()"]
|
||||
result.records = identityValueResult
|
||||
result.raw =
|
||||
identityValueResult[0]["CURRENT_IDENTITY_VALUE()"]
|
||||
result.records = identityValueResult
|
||||
} catch (error) {
|
||||
throw new QueryFailedError(lastIdQuery, [], error)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.driver.connection.logger.logQueryError(
|
||||
@ -311,7 +309,7 @@ export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
|
||||
} finally {
|
||||
// Never forget to drop the statement we reserved
|
||||
if (statement?.drop) {
|
||||
await new Promise<void>((ok) => statement.drop(() => ok()))
|
||||
await promisify(statement.drop).call(statement)
|
||||
}
|
||||
|
||||
await broadcasterResult.wait()
|
||||
@ -343,11 +341,15 @@ export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
|
||||
let resultSet: any
|
||||
|
||||
const cleanup = async () => {
|
||||
if (resultSet) {
|
||||
await promisify(resultSet.close).call(resultSet)
|
||||
const originalStatement = statement
|
||||
const originalResultSet = resultSet
|
||||
statement = null
|
||||
resultSet = null
|
||||
if (originalResultSet) {
|
||||
await promisify(originalResultSet.close).call(originalResultSet)
|
||||
}
|
||||
if (statement) {
|
||||
await promisify(statement.drop).call(statement)
|
||||
if (originalStatement) {
|
||||
await promisify(originalStatement.drop).call(originalStatement)
|
||||
}
|
||||
release()
|
||||
}
|
||||
@ -367,20 +369,20 @@ export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
|
||||
|
||||
const stream =
|
||||
this.driver.streamClient.createObjectStream(resultSet)
|
||||
stream.on("end", async () => {
|
||||
await cleanup()
|
||||
onEnd?.()
|
||||
})
|
||||
stream.on("error", async (error: Error) => {
|
||||
|
||||
if (onEnd) {
|
||||
stream.on("end", onEnd)
|
||||
}
|
||||
stream.on("error", (error: Error) => {
|
||||
this.driver.connection.logger.logQueryError(
|
||||
error,
|
||||
query,
|
||||
parameters,
|
||||
this,
|
||||
)
|
||||
await cleanup()
|
||||
onError?.(error)
|
||||
})
|
||||
stream.on("close", cleanup)
|
||||
|
||||
return stream
|
||||
} catch (error) {
|
||||
|
||||
@ -60,9 +60,6 @@ export class PlatformTools {
|
||||
case "@sap/hana-client/extension/Stream":
|
||||
return require("@sap/hana-client/extension/Stream")
|
||||
|
||||
case "hdb-pool":
|
||||
return require("hdb-pool")
|
||||
|
||||
/**
|
||||
* mysql
|
||||
*/
|
||||
|
||||
72
test/functional/driver/sap/connection-pool.ts
Normal file
72
test/functional/driver/sap/connection-pool.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import "reflect-metadata"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../../utils/test-utils"
|
||||
import { DataSource } from "../../../../src/data-source/DataSource"
|
||||
import { expect } from "chai"
|
||||
import { SapDriver } from "../../../../src/driver/sap/SapDriver"
|
||||
import { QueryRunner } from "../../../../src"
|
||||
import { ConnectionPool } from "@sap/hana-client"
|
||||
|
||||
describe("driver > sap > connection pool", () => {
|
||||
let dataSources: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
(dataSources = await createTestingConnections({
|
||||
enabledDrivers: ["sap"],
|
||||
driverSpecific: {
|
||||
pool: {
|
||||
maxConnectedOrPooled: 3,
|
||||
},
|
||||
},
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(dataSources))
|
||||
after(() => closeTestingConnections(dataSources))
|
||||
|
||||
it("should be managed correctly", () =>
|
||||
Promise.all(
|
||||
dataSources.map(async (dataSource) => {
|
||||
const poolClient = (dataSource.driver as SapDriver)
|
||||
.master as ConnectionPool
|
||||
expect(poolClient.getInUseCount()).to.equal(0)
|
||||
expect(poolClient.getPooledCount()).to.be.at.most(3)
|
||||
|
||||
const queryRunners: QueryRunner[] = []
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const queryRunner = dataSource.createQueryRunner()
|
||||
queryRunners.push(queryRunner)
|
||||
|
||||
// the QueryRunner takes a connection from the pool once the first query is executed
|
||||
await queryRunner.sql`SELECT * FROM SYS.DUMMY`
|
||||
}
|
||||
expect(poolClient.getInUseCount()).to.equal(3)
|
||||
expect(poolClient.getPooledCount()).to.equal(0)
|
||||
|
||||
const newQueryRunner = dataSource.createQueryRunner()
|
||||
await expect(newQueryRunner.connect()).to.be.rejectedWith(
|
||||
"Unable to create connection, the maxConnectedOrPool limit has been reached",
|
||||
)
|
||||
await newQueryRunner.release()
|
||||
|
||||
const oldQueryRunner = queryRunners.pop()!
|
||||
await oldQueryRunner.release()
|
||||
expect(poolClient.getInUseCount()).to.equal(2)
|
||||
expect(poolClient.getPooledCount()).to.equal(1)
|
||||
|
||||
const queryRunner = dataSource.createQueryRunner()
|
||||
queryRunners.push(queryRunner)
|
||||
await expect(queryRunner.connect()).to.be.fulfilled
|
||||
expect(poolClient.getInUseCount()).to.equal(3)
|
||||
expect(poolClient.getPooledCount()).to.equal(0)
|
||||
|
||||
for (const queryRunner of queryRunners) {
|
||||
await queryRunner.release()
|
||||
}
|
||||
expect(poolClient.getInUseCount()).to.equal(0)
|
||||
expect(poolClient.getPooledCount()).to.equal(3)
|
||||
}),
|
||||
))
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user