@breautek/storm
Version:
Object-Oriented REST API framework
244 lines (241 loc) • 10.3 kB
JavaScript
;
/*
Copyright 2017-2021 Norman Breau
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MySQLConnection = void 0;
const tslib_1 = require("tslib");
const DatabaseConnection_1 = require("./DatabaseConnection");
const DatabaseQueryError_1 = require("./DatabaseQueryError");
const instance_1 = require("./instance");
const StartTransactionQuery_1 = require("./private/StartTransactionQuery");
const CommitQuery_1 = require("./private/CommitQuery");
const RollbackQuery_1 = require("./private/RollbackQuery");
const SQLFormatter = tslib_1.__importStar(require("sql-formatter"));
const interfaces_1 = require("@arashi/interfaces");
const DeadLockError_1 = require("./DeadLockError");
const SetIsolationLevelQuery_1 = require("./private/SetIsolationLevelQuery");
const LockWaitTimeoutError_1 = require("./LockWaitTimeoutError");
const GetSlavePositionQuery_1 = require("./private/GetSlavePositionQuery");
const GetMasterPositionQuery_1 = require("./private/GetMasterPositionQuery");
const queryFormatter_1 = require("./mysql/queryFormatter");
const DEFAULT_HIGH_WATERMARK = 512; // in number of result objects
const TAG = 'MySQLConnection';
const SQL_FORMATTING_OPTIONS = {
tabWidth: 4,
keywordCase: 'upper',
identifierCase: 'preserve',
useTabs: false,
indentStyle: 'standard',
logicalOperatorNewline: 'after',
linesBetweenQueries: 1,
denseOperators: false,
newlineBeforeSemicolon: false,
expressionWidth: 4,
dataTypeCase: 'upper',
functionCase: 'upper'
};
let startTransactionQuery = new StartTransactionQuery_1.StartTransactionQuery();
let commitQuery = new CommitQuery_1.CommitQuery();
let rollbackQuery = new RollbackQuery_1.RollbackQuery();
class MySQLConnection extends DatabaseConnection_1.DatabaseConnection {
constructor(connection, instantiationStack, isReadOnly = true) {
super(connection, isReadOnly, instantiationStack);
this.$opened = true;
this.$transaction = false;
this.$isMasterConnection = null;
connection.config.queryFormat = queryFormatter_1.queryFormatter.bind(this);
// connection.config.queryFormat = function(query: string, values: any) {
// if (!values) return query;
// return query.replace(/:(\w+)/g, function(this: any, txt: string, key: string): string {
// // eslint-disable-next-line no-prototype-builtins
// if (values.hasOwnProperty(key)) {
// return this.escape(values[key]);
// }
// return txt;
// }.bind(this));
// };
}
/**
* @internal - Do not use in application code
*/
async __internal_init() {
let result = await new GetSlavePositionQuery_1.GetSlavePositionQuery().execute(this);
this.$isMasterConnection = result === null;
}
formatQuery(query) {
return this.getAPI().config.queryFormat(query.getQuery(this), query.getParametersForQuery());
}
isMaster() {
return this.$isMasterConnection;
}
isReplication() {
return !this.isMaster();
}
isTransaction() {
return this.$transaction;
}
isOpen() {
return this.$opened;
}
async getCurrentDatabasePosition() {
let statusQuery = this.isReplication() ? new GetSlavePositionQuery_1.GetSlavePositionQuery() : new GetMasterPositionQuery_1.GetMasterPositionQuery();
return await statusQuery.execute(this);
}
_query(query, params) {
let logger = (0, instance_1.getInstance)().getLogger();
return new Promise((resolve, reject) => {
let queryObject = this.getAPI().query({
sql: query,
timeout: this.getTimeout()
}, params, (error, results) => {
if (error) {
let sql = queryObject.sql;
// Formatting queries can be an expensive task, so only do it if the log level is actually silly.
if (logger.getLogLevel() === interfaces_1.LogLevel.SILLY) {
try {
// SQLFormatter doesn't understand all MySQL syntaxes, so this is to prevent
// potentially valid queries from becoming errors simply because we couldn't
// log them.
sql = SQLFormatter.formatDialect(queryObject.sql, {
...SQL_FORMATTING_OPTIONS,
dialect: SQLFormatter.mysql
});
}
catch (ex) {
logger.warn(TAG, 'Unable to format query...');
logger.warn(TAG, ex);
}
}
let e = null;
if (error.code === 'ER_LOCK_DEADLOCK') {
e = new DeadLockError_1.DeadLockError(sql, error);
// When deadlocks occur, the transaction is automatically rollback, so we can clear transanction status.
this.$transaction = false;
}
else if (error.code === 'ER_LOCK_WAIT_TIMEOUT') {
// lock wait may not rollback the transaction (depends on how the server is configured)
// however the transaction is retryable. The user shall configure
// if they want to retry on timeouts.
e = new LockWaitTimeoutError_1.LockWaitTimeoutError(sql, error);
}
else {
e = new DatabaseQueryError_1.DatabaseQueryError(sql, error);
}
return reject(e);
}
return resolve(results);
});
// Formatting queries can be an expensive task, so only do it if the log level is actually silly.
let sql = queryObject.sql;
if (logger.getLogLevel() === interfaces_1.LogLevel.SILLY) {
sql = SQLFormatter.format(queryObject.sql, SQL_FORMATTING_OPTIONS);
}
(0, instance_1.getInstance)().getLogger().trace(TAG, sql);
});
}
_stream(query, params, streamOptions) {
if (!streamOptions) {
streamOptions = {};
}
if (!streamOptions.highWatermark) {
streamOptions.highWatermark = DEFAULT_HIGH_WATERMARK;
}
let queryObject = this.getAPI().query({
sql: query,
timeout: this.getTimeout()
}, params);
(0, instance_1.getInstance)().getLogger().trace(TAG, SQLFormatter.format(queryObject.sql, SQL_FORMATTING_OPTIONS));
return queryObject.stream(streamOptions);
}
async startTransaction(isolationLevel) {
if (this.isReadOnly()) {
throw new Error('A readonly connection cannot start a transaction.');
}
if (this.isTransaction()) {
throw new Error('Connection is already in a transaction.');
}
this.$transaction = true;
try {
if (isolationLevel) {
await new SetIsolationLevelQuery_1.SetIsolationLevelQuery(isolationLevel).execute(this);
}
await startTransactionQuery.execute(this);
}
catch (ex) {
this.$transaction = false;
(0, instance_1.getInstance)().getLogger().error(TAG, ex);
throw ex;
}
}
endTransaction(requiresRollback = false) {
return (requiresRollback) ? this.rollback() : this.commit();
}
rollback() {
if (!this.isTransaction()) {
return Promise.reject(new Error('Cannot rollback when there is no active transaction.'));
}
return new Promise((resolve, reject) => {
this.query(rollbackQuery).then(() => {
this.$transaction = false;
resolve();
}).catch((ex) => {
(0, instance_1.getInstance)().getLogger().error(TAG, ex);
reject(ex);
});
});
}
commit() {
if (!this.isTransaction()) {
return Promise.reject(new Error('Cannot commit when there is no active transaction.'));
}
return new Promise((resolve, reject) => {
this.query(commitQuery).then(() => {
this.$transaction = false;
resolve();
}).catch((ex) => {
(0, instance_1.getInstance)().getLogger().error(TAG, ex);
reject(ex);
});
});
}
_close(forceClose) {
if (!forceClose && this.isTransaction()) {
return Promise.reject(new Error('Cannot close a connection while there is an active transaction. Use commit or rollback first.'));
}
this.$opened = false;
return new Promise((resolve, reject) => {
let rollbackPromise = null;
if (forceClose) {
if (this.isTransaction()) {
rollbackPromise = this.rollback();
}
else {
rollbackPromise = Promise.resolve();
}
}
else {
rollbackPromise = Promise.resolve();
}
rollbackPromise.then(() => {
this.getAPI().release();
resolve();
}).catch((error) => {
(0, instance_1.getInstance)().getLogger().error(TAG, error);
this.getAPI().release();
resolve();
});
});
}
}
exports.MySQLConnection = MySQLConnection;
//# sourceMappingURL=MySQLConnection.js.map