@bitblit/ratchet-rdbms 4.0.115-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/License.txt +13 -0
- package/README.md +41 -0
- package/lib/build/ratchet-rdbms-info.d.ts +5 -0
- package/lib/build/ratchet-rdbms-info.js +14 -0
- package/lib/model/connection-config.d.ts +4 -0
- package/lib/model/connection-config.js +1 -0
- package/lib/model/db-config.d.ts +9 -0
- package/lib/model/db-config.js +1 -0
- package/lib/model/group-by-count-result.d.ts +4 -0
- package/lib/model/group-by-count-result.js +1 -0
- package/lib/model/mysql/mysql-master-status.d.ts +6 -0
- package/lib/model/mysql/mysql-master-status.js +1 -0
- package/lib/model/mysql/mysql-results-wrapper.d.ts +5 -0
- package/lib/model/mysql/mysql-results-wrapper.js +1 -0
- package/lib/model/mysql/mysql-slave-status.d.ts +52 -0
- package/lib/model/mysql/mysql-slave-status.js +1 -0
- package/lib/model/mysql/mysql-style-connection-provider.d.ts +7 -0
- package/lib/model/mysql/mysql-style-connection-provider.js +1 -0
- package/lib/model/mysql/mysql-update-results.d.ts +9 -0
- package/lib/model/mysql/mysql-update-results.js +1 -0
- package/lib/model/paginated-results.d.ts +5 -0
- package/lib/model/paginated-results.js +1 -0
- package/lib/model/pagination-bounds.d.ts +6 -0
- package/lib/model/pagination-bounds.js +1 -0
- package/lib/model/paginator.d.ts +9 -0
- package/lib/model/paginator.js +1 -0
- package/lib/model/query-defaults.d.ts +4 -0
- package/lib/model/query-defaults.js +1 -0
- package/lib/model/query-text-provider.d.ts +4 -0
- package/lib/model/query-text-provider.js +1 -0
- package/lib/model/sort-direction.d.ts +4 -0
- package/lib/model/sort-direction.js +5 -0
- package/lib/model/ssh/ssh-tunnel-config.d.ts +7 -0
- package/lib/model/ssh/ssh-tunnel-config.js +1 -0
- package/lib/model/ssh/ssh-tunnel-container.d.ts +12 -0
- package/lib/model/ssh/ssh-tunnel-container.js +1 -0
- package/lib/model/ssh-config.d.ts +5 -0
- package/lib/model/ssh-config.js +1 -0
- package/lib/model/transaction-isolation-level.d.ts +4 -0
- package/lib/model/transaction-isolation-level.js +5 -0
- package/lib/named-parameter-maria-db-service.d.ts +41 -0
- package/lib/named-parameter-maria-db-service.js +251 -0
- package/lib/non-pooled-mysql-style-connection-provider.d.ts +8 -0
- package/lib/non-pooled-mysql-style-connection-provider.js +16 -0
- package/lib/query-builder/query-builder-result.d.ts +9 -0
- package/lib/query-builder/query-builder-result.js +12 -0
- package/lib/query-builder/query-builder.d.ts +52 -0
- package/lib/query-builder/query-builder.js +352 -0
- package/lib/query-builder/query-builder.spec.d.ts +1 -0
- package/lib/query-builder/query-builder.spec.js +126 -0
- package/lib/query-builder/query-util.d.ts +12 -0
- package/lib/query-builder/query-util.js +69 -0
- package/lib/rds-mysql-style-connection-provider.d.ts +23 -0
- package/lib/rds-mysql-style-connection-provider.js +159 -0
- package/lib/ssh-tunnel-service.d.ts +6 -0
- package/lib/ssh-tunnel-service.js +47 -0
- package/lib/transactional-named-parameter-maria-db-service.d.ts +23 -0
- package/lib/transactional-named-parameter-maria-db-service.js +129 -0
- package/lib/util/relational-database-utils.d.ts +4 -0
- package/lib/util/relational-database-utils.js +29 -0
- package/package.json +68 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
All notable changes to this project will be documented in this file.
|
|
3
|
+
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Notes]
|
|
8
|
+
Alpha releases are exactly what they sound like - places where I am trying out new things that aren't ready for prime
|
|
9
|
+
time, but I need published to see how they interact with the rest of my software ecosystem. If you use an alpha
|
|
10
|
+
package without knowing why it is alpha you'll get exactly what you deserve.
|
|
11
|
+
|
|
12
|
+
## [Unreleased]
|
|
13
|
+
|
|
14
|
+
## In Flight
|
|
15
|
+
|
|
16
|
+
## [4.0.x] - 2018-03-23
|
|
17
|
+
### Initial Release
|
|
18
|
+
- Basic support for RDBMS stuff
|
|
19
|
+
|
package/License.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2022-2023 Christopher Weiss
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @bitblit/ratchet-rdbms
|
|
2
|
+
|
|
3
|
+
Typescript library to simplify using relational database systems by externalizing the queries.
|
|
4
|
+
|
|
5
|
+
## Introduction
|
|
6
|
+
|
|
7
|
+
Meant to be very similar to the ibatis/mybatis library in Java, which has the right level of abstraction
|
|
8
|
+
of the RDBMS in my opinion.
|
|
9
|
+
|
|
10
|
+
You may wish to read [the changelog](CHANGELOG.md)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
`yarn install @bitblit/ratchet-rdbms`
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### Paginator
|
|
19
|
+
(To understand the terminology here you may want to read [this](https://nordicapis.com/understanding-5-types-of-web-api-pagination/))
|
|
20
|
+
|
|
21
|
+
This library natively supports:
|
|
22
|
+
* Cursor pagination (use columnName, sort, min, limit)
|
|
23
|
+
* Keyset API pagination (use columnName, sort, min, max, maybe limit)
|
|
24
|
+
* Seek API pagination (use columnName, sort, min, max, maybe limit)
|
|
25
|
+
* Time-based (use columnName on a time column, sort, min, max, maybe limit)
|
|
26
|
+
|
|
27
|
+
This library doesn't support offset pagination (e.g., using limit/offset in MySql) out of the box. It does this
|
|
28
|
+
on purpose - in general for large datasets offset pagination is an anti-pattern, and I hope by not supporting
|
|
29
|
+
it natively it'll make it a little less likely that I'll use it when I get in a rush. Obviously its possible
|
|
30
|
+
to still implement it using this architecture. At some point in the future I may give in and do it.
|
|
31
|
+
|
|
32
|
+
# Testing
|
|
33
|
+
|
|
34
|
+
Ha! No, seriously - all testing is done using Jest. To run them:
|
|
35
|
+
|
|
36
|
+
`yarn test`
|
|
37
|
+
|
|
38
|
+
# Contributing
|
|
39
|
+
|
|
40
|
+
Pull requests are welcome, although I'm not sure why you'd be interested!
|
|
41
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class RatchetRdbmsInfo {
|
|
2
|
+
constructor() { }
|
|
3
|
+
static buildInformation() {
|
|
4
|
+
const val = {
|
|
5
|
+
version: 'LOCAL-SNAPSHOT',
|
|
6
|
+
hash: 'LOCAL-HASH',
|
|
7
|
+
branch: 'LOCAL-BRANCH',
|
|
8
|
+
tag: 'LOCAL-TAG',
|
|
9
|
+
timeBuiltISO: 'LOCAL-TIME-ISO',
|
|
10
|
+
notes: 'LOCAL-NOTES',
|
|
11
|
+
};
|
|
12
|
+
return val;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface MysqlSlaveStatus {
|
|
2
|
+
Slave_IO_State: string;
|
|
3
|
+
Master_Host: string;
|
|
4
|
+
Master_User: string;
|
|
5
|
+
Master_Port: number;
|
|
6
|
+
Connect_Retry: number;
|
|
7
|
+
Master_Log_File: string;
|
|
8
|
+
Read_Master_Log_Pos: number;
|
|
9
|
+
Relay_Log_File: string;
|
|
10
|
+
Relay_Log_Pos: number;
|
|
11
|
+
Relay_Master_Log_File: string;
|
|
12
|
+
Slave_IO_Running: string;
|
|
13
|
+
Slave_SQL_Running: string;
|
|
14
|
+
Replicate_Do_DB: string;
|
|
15
|
+
Replicate_Ignore_DB: string;
|
|
16
|
+
Replicate_Do_Table: string;
|
|
17
|
+
Replicate_Ignore_Table: string;
|
|
18
|
+
Replicate_Wild_Do_Table: string;
|
|
19
|
+
Replicate_Wild_Ignore_Table: string;
|
|
20
|
+
Last_Errno: number;
|
|
21
|
+
Last_Error: string;
|
|
22
|
+
Skip_Counter: number;
|
|
23
|
+
Exec_Master_Log_Pos: number;
|
|
24
|
+
Relay_Log_Space: number;
|
|
25
|
+
Until_Condition: string;
|
|
26
|
+
Until_Log_File: string;
|
|
27
|
+
Until_Log_Pos: number;
|
|
28
|
+
Master_SSL_Allowed: string;
|
|
29
|
+
Master_SSL_CA_File: string;
|
|
30
|
+
Master_SSL_CA_Path: string;
|
|
31
|
+
Master_SSL_Cert: string;
|
|
32
|
+
Master_SSL_Cipher: string;
|
|
33
|
+
Master_SSL_Key: string;
|
|
34
|
+
Seconds_Behind_Master: number;
|
|
35
|
+
Master_SSL_Verify_Server_Cert: string;
|
|
36
|
+
Last_IO_Errno: number;
|
|
37
|
+
Last_IO_Error: string;
|
|
38
|
+
Last_SQL_Errno: number;
|
|
39
|
+
Last_SQL_Error: string;
|
|
40
|
+
Replicate_Ignore_Server_Ids: string;
|
|
41
|
+
Master_Server_Id: number;
|
|
42
|
+
Master_SSL_Crl: string;
|
|
43
|
+
Master_SSL_Crlpath: string;
|
|
44
|
+
Using_Gtid: string;
|
|
45
|
+
Gtid_IO_Pos: string;
|
|
46
|
+
Replicate_Do_Domain_Ids: string;
|
|
47
|
+
Replicate_Ignore_Domain_Ids: string;
|
|
48
|
+
Parallel_Mode: string;
|
|
49
|
+
SQL_Delay: number;
|
|
50
|
+
SQL_Remaining_Delay: string;
|
|
51
|
+
Slave_SQL_Running_State: string;
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Connection, ConnectionOptions } from 'mysql2/promise';
|
|
2
|
+
import { QueryDefaults } from '../query-defaults.js';
|
|
3
|
+
export interface MysqlStyleConnectionProvider {
|
|
4
|
+
getConnection(name?: string): Promise<Connection | undefined>;
|
|
5
|
+
clearConnectionCache(): Promise<boolean>;
|
|
6
|
+
createNonPooledDatabaseConnection?(queryDefaults: QueryDefaults, additionalConfig?: ConnectionOptions): Promise<Connection | undefined>;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SortDirection } from './sort-direction.js';
|
|
2
|
+
import { JwtTokenBase } from '@bitblit/ratchet-common/lib/jwt/jwt-token-base.js';
|
|
3
|
+
export interface Paginator<T> extends JwtTokenBase {
|
|
4
|
+
cn: string;
|
|
5
|
+
min?: T;
|
|
6
|
+
max?: T;
|
|
7
|
+
s?: SortDirection;
|
|
8
|
+
l?: number;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { ListenOptions, Server } from 'net';
|
|
3
|
+
import type { ConnectConfig, Connection } from 'ssh2';
|
|
4
|
+
import * as TunnelSsh from 'tunnel-ssh';
|
|
5
|
+
export interface SshTunnelContainer {
|
|
6
|
+
tunnelOptions: TunnelSsh.TunnelOptions;
|
|
7
|
+
serverOptions: ListenOptions;
|
|
8
|
+
sshOptions: ConnectConfig;
|
|
9
|
+
forwardOptions: TunnelSsh.ForwardOptions;
|
|
10
|
+
server: Server;
|
|
11
|
+
connection?: Connection;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Connection, ConnectionOptions } from 'mysql2/promise';
|
|
2
|
+
import { MysqlResultsWrapper } from './model/mysql/mysql-results-wrapper.js';
|
|
3
|
+
import { MysqlUpdateResults } from './model/mysql/mysql-update-results.js';
|
|
4
|
+
import { TransactionIsolationLevel } from './model/transaction-isolation-level.js';
|
|
5
|
+
import { NonPooledMysqlStyleConnectionProvider } from './non-pooled-mysql-style-connection-provider.js';
|
|
6
|
+
import { MysqlStyleConnectionProvider } from './model/mysql/mysql-style-connection-provider.js';
|
|
7
|
+
import { QueryBuilder } from './query-builder/query-builder.js';
|
|
8
|
+
import { GroupByCountResult } from './model/group-by-count-result.js';
|
|
9
|
+
import { QueryDefaults } from './model/query-defaults.js';
|
|
10
|
+
import { QueryTextProvider } from './model/query-text-provider.js';
|
|
11
|
+
export declare class NamedParameterMariaDbService {
|
|
12
|
+
private queryProvider;
|
|
13
|
+
private connectionProvider;
|
|
14
|
+
private queryDefaults;
|
|
15
|
+
private static LONG_QUERY_TIME_MS;
|
|
16
|
+
private serviceName;
|
|
17
|
+
constructor(queryProvider: QueryTextProvider, connectionProvider: MysqlStyleConnectionProvider, queryDefaults: QueryDefaults);
|
|
18
|
+
getQueryDefaults(): QueryDefaults;
|
|
19
|
+
getQueryProvider(): QueryTextProvider;
|
|
20
|
+
createNonPooledMysqlStyleConnectionProvider(queryDefaults: QueryDefaults, additionalConfig?: ConnectionOptions): Promise<NonPooledMysqlStyleConnectionProvider>;
|
|
21
|
+
fetchQueryRawTextByName(queryPath: string): string;
|
|
22
|
+
queryBuilder(queryPath?: string): QueryBuilder;
|
|
23
|
+
executeUpdateOrInsertByName(queryPath: string, params?: object, timeoutMS?: number): Promise<MysqlUpdateResults>;
|
|
24
|
+
buildAndExecuteUpdateOrInsert(queryBuilder: QueryBuilder, timeoutMS?: number): Promise<MysqlUpdateResults>;
|
|
25
|
+
buildAndExecuteUpdateOrInsertWithRetry(queryBuilder: QueryBuilder, maxRetries: number, timeoutMS?: number): Promise<MysqlUpdateResults>;
|
|
26
|
+
executeQueryByName<Row>(queryPath: string, params: object, timeoutMS?: number): Promise<Row[]>;
|
|
27
|
+
executeQueryByNameSingle<Row>(queryPath: string, params: object, timeoutMS?: number): Promise<Row | null>;
|
|
28
|
+
buildAndExecute<Row>(queryBuilder: QueryBuilder, timeoutMS?: number): Promise<Row[]>;
|
|
29
|
+
buildAndExecuteSingle<Row>(queryBuilder: QueryBuilder, timeoutMS?: number): Promise<Row | null>;
|
|
30
|
+
buildAndExecuteFetchTotalRows(queryBuilder: QueryBuilder, groupBy?: string, timeoutMS?: number): Promise<GroupByCountResult[]>;
|
|
31
|
+
executeQueryWithMeta<Row>(transactionIsolationLevel: TransactionIsolationLevel, query: string, fields?: object, timeoutMS?: number, debugComment?: string): Promise<MysqlResultsWrapper<Row>>;
|
|
32
|
+
shutdown(): Promise<boolean>;
|
|
33
|
+
testConnection(quietMode?: boolean): Promise<number | null>;
|
|
34
|
+
testDbFailure(): Promise<void>;
|
|
35
|
+
changeNextQueryTransactionIsolationLevel<Row>(tx: TransactionIsolationLevel | null): Promise<MysqlResultsWrapper<Row> | null>;
|
|
36
|
+
forceCloseConnectionForTesting(): Promise<boolean>;
|
|
37
|
+
private innerExecutePreparedAsPromiseWithRetryCloseConnection;
|
|
38
|
+
private innerExecutePreparedAsPromise;
|
|
39
|
+
getDB(): Promise<Connection>;
|
|
40
|
+
resetConnection(): Promise<boolean>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { Logger } from '@bitblit/ratchet-common/lib/logger/logger.js';
|
|
2
|
+
import { TimeoutToken } from '@bitblit/ratchet-common/lib/lang/timeout-token.js';
|
|
3
|
+
import { PromiseRatchet } from '@bitblit/ratchet-common/lib/lang/promise-ratchet.js';
|
|
4
|
+
import { DurationRatchet } from '@bitblit/ratchet-common/lib/lang/duration-ratchet.js';
|
|
5
|
+
import { ErrorRatchet } from '@bitblit/ratchet-common/lib/lang/error-ratchet.js';
|
|
6
|
+
import { StopWatch } from '@bitblit/ratchet-common/lib/lang/stop-watch.js';
|
|
7
|
+
import { QueryUtil } from './query-builder/query-util.js';
|
|
8
|
+
import { TransactionIsolationLevel } from './model/transaction-isolation-level.js';
|
|
9
|
+
import { NonPooledMysqlStyleConnectionProvider } from './non-pooled-mysql-style-connection-provider.js';
|
|
10
|
+
import { QueryBuilder } from './query-builder/query-builder.js';
|
|
11
|
+
export class NamedParameterMariaDbService {
|
|
12
|
+
queryProvider;
|
|
13
|
+
connectionProvider;
|
|
14
|
+
queryDefaults;
|
|
15
|
+
static LONG_QUERY_TIME_MS = 8500;
|
|
16
|
+
serviceName = 'TBD';
|
|
17
|
+
constructor(queryProvider, connectionProvider, queryDefaults) {
|
|
18
|
+
this.queryProvider = queryProvider;
|
|
19
|
+
this.connectionProvider = connectionProvider;
|
|
20
|
+
this.queryDefaults = queryDefaults;
|
|
21
|
+
this.serviceName = 'NamedParameterMariaDb';
|
|
22
|
+
}
|
|
23
|
+
getQueryDefaults() {
|
|
24
|
+
return this.queryDefaults;
|
|
25
|
+
}
|
|
26
|
+
getQueryProvider() {
|
|
27
|
+
return this.queryProvider;
|
|
28
|
+
}
|
|
29
|
+
async createNonPooledMysqlStyleConnectionProvider(queryDefaults, additionalConfig) {
|
|
30
|
+
Logger.info('createTransactional : %s : %j', queryDefaults, additionalConfig);
|
|
31
|
+
if (!this.connectionProvider.createNonPooledDatabaseConnection) {
|
|
32
|
+
throw new Error(`Connection provider does not implement createNonPooledDatabaseConnection`);
|
|
33
|
+
}
|
|
34
|
+
const newConn = await this.connectionProvider.createNonPooledDatabaseConnection(queryDefaults, additionalConfig);
|
|
35
|
+
if (!newConn) {
|
|
36
|
+
throw new Error(`Connection could not be created for DB type ${queryDefaults}`);
|
|
37
|
+
}
|
|
38
|
+
return new NonPooledMysqlStyleConnectionProvider(newConn);
|
|
39
|
+
}
|
|
40
|
+
fetchQueryRawTextByName(queryPath) {
|
|
41
|
+
return this.queryProvider.fetchQuery(queryPath);
|
|
42
|
+
}
|
|
43
|
+
queryBuilder(queryPath) {
|
|
44
|
+
const queryBuilder = new QueryBuilder(this.queryProvider);
|
|
45
|
+
if (queryPath) {
|
|
46
|
+
queryBuilder.withNamedQuery(queryPath);
|
|
47
|
+
}
|
|
48
|
+
return queryBuilder;
|
|
49
|
+
}
|
|
50
|
+
async executeUpdateOrInsertByName(queryPath, params, timeoutMS = this.queryDefaults.timeoutMS) {
|
|
51
|
+
const builder = this.queryBuilder(queryPath).withParams(params ?? {});
|
|
52
|
+
return this.buildAndExecuteUpdateOrInsert(builder, timeoutMS);
|
|
53
|
+
}
|
|
54
|
+
async buildAndExecuteUpdateOrInsert(queryBuilder, timeoutMS = this.queryDefaults.timeoutMS) {
|
|
55
|
+
const build = queryBuilder.build();
|
|
56
|
+
const resp = await this.executeQueryWithMeta(build.transactionIsolationLevel, build.query, build.namedParams, timeoutMS);
|
|
57
|
+
const rval = resp.results;
|
|
58
|
+
return rval;
|
|
59
|
+
}
|
|
60
|
+
async buildAndExecuteUpdateOrInsertWithRetry(queryBuilder, maxRetries, timeoutMS = this.queryDefaults.timeoutMS) {
|
|
61
|
+
let retry = 0;
|
|
62
|
+
let res;
|
|
63
|
+
while (!res && retry < maxRetries) {
|
|
64
|
+
retry++;
|
|
65
|
+
try {
|
|
66
|
+
res = await this.buildAndExecuteUpdateOrInsert(queryBuilder, timeoutMS);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
Logger.info('Caught problem while trying to update/insert : %d : %s ', retry, err);
|
|
70
|
+
await PromiseRatchet.wait(retry * 2000);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!res) {
|
|
74
|
+
throw new Error(`Failed to execute update after ${maxRetries} retries`);
|
|
75
|
+
}
|
|
76
|
+
return res;
|
|
77
|
+
}
|
|
78
|
+
async executeQueryByName(queryPath, params, timeoutMS = this.queryDefaults.timeoutMS) {
|
|
79
|
+
const builder = this.queryBuilder(queryPath).withParams(params);
|
|
80
|
+
const resp = await this.buildAndExecute(builder, timeoutMS);
|
|
81
|
+
return resp;
|
|
82
|
+
}
|
|
83
|
+
async executeQueryByNameSingle(queryPath, params, timeoutMS = this.queryDefaults.timeoutMS) {
|
|
84
|
+
const builder = this.queryBuilder(queryPath).withParams(params);
|
|
85
|
+
const resp = await this.buildAndExecute(builder, timeoutMS);
|
|
86
|
+
return resp.length === 1 ? resp[0] : null;
|
|
87
|
+
}
|
|
88
|
+
async buildAndExecute(queryBuilder, timeoutMS = this.queryDefaults.timeoutMS) {
|
|
89
|
+
const build = queryBuilder.build();
|
|
90
|
+
const resp = await this.executeQueryWithMeta(build.transactionIsolationLevel, build.query, build.namedParams, timeoutMS, queryBuilder.getDebugComment());
|
|
91
|
+
return resp.results;
|
|
92
|
+
}
|
|
93
|
+
async buildAndExecuteSingle(queryBuilder, timeoutMS = this.queryDefaults.timeoutMS) {
|
|
94
|
+
const build = queryBuilder.build();
|
|
95
|
+
const resp = await this.executeQueryWithMeta(build.transactionIsolationLevel, build.query, build.namedParams, timeoutMS, queryBuilder.getDebugComment());
|
|
96
|
+
return resp.results.length === 1 ? resp.results[0] : null;
|
|
97
|
+
}
|
|
98
|
+
async buildAndExecuteFetchTotalRows(queryBuilder, groupBy = '', timeoutMS = this.queryDefaults.timeoutMS) {
|
|
99
|
+
const buildUnfiltered = queryBuilder.buildUnfiltered();
|
|
100
|
+
let query = buildUnfiltered.query.replace('COUNT(*)', `${groupBy} as groupByField, COUNT(*) as count`);
|
|
101
|
+
query = `${query} GROUP BY ${groupBy}`;
|
|
102
|
+
Logger.info('Unfiltered query %s', buildUnfiltered.query);
|
|
103
|
+
const resp = await this.executeQueryWithMeta(buildUnfiltered.transactionIsolationLevel, query, buildUnfiltered.namedParams, timeoutMS);
|
|
104
|
+
return resp.results;
|
|
105
|
+
}
|
|
106
|
+
async executeQueryWithMeta(transactionIsolationLevel, query, fields = {}, timeoutMS = this.queryDefaults.timeoutMS, debugComment) {
|
|
107
|
+
const sw = new StopWatch();
|
|
108
|
+
if (!timeoutMS) {
|
|
109
|
+
timeoutMS = this.queryDefaults.timeoutMS;
|
|
110
|
+
}
|
|
111
|
+
await this.changeNextQueryTransactionIsolationLevel(transactionIsolationLevel);
|
|
112
|
+
const result = await PromiseRatchet.timeout(this.innerExecutePreparedAsPromiseWithRetryCloseConnection(query, fields, undefined), 'Query:' + query, timeoutMS);
|
|
113
|
+
if (TimeoutToken.isTimeoutToken(result)) {
|
|
114
|
+
Logger.warn('Timed out (after %s): %j', DurationRatchet.colonFormatMsDuration(timeoutMS), result);
|
|
115
|
+
const duration = DurationRatchet.colonFormatMsDuration(timeoutMS);
|
|
116
|
+
throw new Error(`Timed out (after ${duration}) waiting for query : ${query}`);
|
|
117
|
+
}
|
|
118
|
+
const rval = result;
|
|
119
|
+
if (!rval.results) {
|
|
120
|
+
Logger.error('DB:executeQueryWithMeta:Failure: %j', rval);
|
|
121
|
+
}
|
|
122
|
+
if (debugComment && sw.elapsedMS() > NamedParameterMariaDbService.LONG_QUERY_TIME_MS) {
|
|
123
|
+
Logger.info('NamedParameterMariaDbService long query: %s, %s', debugComment, sw.dump());
|
|
124
|
+
}
|
|
125
|
+
return rval;
|
|
126
|
+
}
|
|
127
|
+
async shutdown() {
|
|
128
|
+
Logger.info('Shutting down %s service', this.serviceName);
|
|
129
|
+
let rval;
|
|
130
|
+
try {
|
|
131
|
+
rval = await this.connectionProvider.clearConnectionCache();
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
Logger.error('Failure trying to shutdown : %s', err, err);
|
|
135
|
+
rval = false;
|
|
136
|
+
}
|
|
137
|
+
return rval;
|
|
138
|
+
}
|
|
139
|
+
async testConnection(quietMode = false) {
|
|
140
|
+
if (!quietMode) {
|
|
141
|
+
Logger.info('Running connection test');
|
|
142
|
+
}
|
|
143
|
+
const res = await this.executeQueryWithMeta(TransactionIsolationLevel.Default, 'SELECT UNIX_TIMESTAMP(now())*1000 AS test');
|
|
144
|
+
const rows = res.results;
|
|
145
|
+
const timestamp = rows.length === 1 ? rows[0].test : null;
|
|
146
|
+
if (!quietMode) {
|
|
147
|
+
Logger.info('Test returned : %j', timestamp);
|
|
148
|
+
}
|
|
149
|
+
return timestamp;
|
|
150
|
+
}
|
|
151
|
+
async testDbFailure() {
|
|
152
|
+
await this.executeQueryWithMeta(TransactionIsolationLevel.Default, 'this is a bad query');
|
|
153
|
+
}
|
|
154
|
+
async changeNextQueryTransactionIsolationLevel(tx) {
|
|
155
|
+
if (tx && tx !== TransactionIsolationLevel.Default) {
|
|
156
|
+
Logger.debug('Setting tx to %s', tx);
|
|
157
|
+
return await this.innerExecutePreparedAsPromiseWithRetryCloseConnection('SET TRANSACTION ISOLATION LEVEL ' + tx, {});
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
async forceCloseConnectionForTesting() {
|
|
162
|
+
Logger.warn('Forcing connection closed for testing');
|
|
163
|
+
const conn = await this.getDB();
|
|
164
|
+
try {
|
|
165
|
+
await conn.end();
|
|
166
|
+
Logger.info('Connection has been ended, but not set to null');
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
Logger.error('Error closing connection : %s', err, err);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async innerExecutePreparedAsPromiseWithRetryCloseConnection(query, fields = {}, retryCount = 1) {
|
|
175
|
+
try {
|
|
176
|
+
const result = await this.innerExecutePreparedAsPromise(query, fields);
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
catch (errIn) {
|
|
180
|
+
const err = ErrorRatchet.asErr(errIn);
|
|
181
|
+
if (err.message.includes('closed state') ||
|
|
182
|
+
err.message.includes('This socket has been ended by the other party') ||
|
|
183
|
+
err.message.includes('ETIMEDOUT') ||
|
|
184
|
+
err.message.includes('NeonNoConnection') ||
|
|
185
|
+
err.message.includes('ER_LOCK_WAIT_TIMEOUT')) {
|
|
186
|
+
const wait = Math.min(1000 * retryCount);
|
|
187
|
+
Logger.warn('Found closed connection or lock timeout - clearing and attempting retry after %d (try %d of 3) (%s)', wait, retryCount, err.message);
|
|
188
|
+
if (retryCount < 4) {
|
|
189
|
+
const cleared = await this.connectionProvider.clearConnectionCache();
|
|
190
|
+
Logger.info('Clear connection cache returned %s', cleared);
|
|
191
|
+
await PromiseRatchet.wait(wait);
|
|
192
|
+
return this.innerExecutePreparedAsPromiseWithRetryCloseConnection(query, fields, retryCount + 1);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
Logger.warn('Ran out of retries');
|
|
196
|
+
throw new Error('Connection closed and cannot retry any more - dying horribly');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
Logger.error('Named Param DB Query Failed : Err: %s Query: %s Params: %j', err, query, fields, err);
|
|
201
|
+
try {
|
|
202
|
+
const conn = await this.getDB();
|
|
203
|
+
Logger.error('-----\nFor paste into tooling only: \n\n%s\n\n', QueryUtil.renderQueryStringForPasteIntoTool(query, fields, (v) => conn.escape(v)));
|
|
204
|
+
}
|
|
205
|
+
catch (err2) {
|
|
206
|
+
Logger.error('Really bad - failed trying to get the conn for logging : %s', err2);
|
|
207
|
+
}
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async innerExecutePreparedAsPromise(query, fields = {}) {
|
|
213
|
+
const conn = await this.getDB();
|
|
214
|
+
conn.config.namedPlaceholders = true;
|
|
215
|
+
const sw = new StopWatch();
|
|
216
|
+
try {
|
|
217
|
+
const [rows, outFields] = await conn.query(query, fields);
|
|
218
|
+
const rval = {
|
|
219
|
+
results: rows,
|
|
220
|
+
fields: outFields,
|
|
221
|
+
};
|
|
222
|
+
Logger.debug('Success : Finished query : %s\n%s\n\nParams : %j', sw.dump(), QueryUtil.reformatQueryForLogging(query), fields);
|
|
223
|
+
Logger.debug('-----\nFor paste into tooling only : \n\n%s\n\n', QueryUtil.renderQueryStringForPasteIntoTool(query, fields, (v) => conn.escape(v)));
|
|
224
|
+
return rval;
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
conn.config.namedPlaceholders = false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async getDB() {
|
|
231
|
+
const conn = await this.connectionProvider.getConnection(this.queryDefaults.databaseName);
|
|
232
|
+
if (!conn) {
|
|
233
|
+
throw new Error('NeonNoConnection : getConnection returned null - likely failed to get connection from db');
|
|
234
|
+
}
|
|
235
|
+
return conn;
|
|
236
|
+
}
|
|
237
|
+
async resetConnection() {
|
|
238
|
+
let rval = false;
|
|
239
|
+
Logger.info('Resetting connection');
|
|
240
|
+
try {
|
|
241
|
+
await this.connectionProvider.clearConnectionCache();
|
|
242
|
+
const tmpValue = await this.testConnection();
|
|
243
|
+
rval = !!tmpValue;
|
|
244
|
+
Logger.info('Reset connection returning %s', rval);
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
Logger.error('Failed to reset connection : %s', err);
|
|
248
|
+
}
|
|
249
|
+
return rval;
|
|
250
|
+
}
|
|
251
|
+
}
|