mariadb
Version:
fast mariadb or mysql connector.
533 lines (474 loc) • 13.2 kB
JavaScript
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2015-2025 MariaDB Corporation Ab
'use strict';
const Errors = require('./misc/errors');
const { Status } = require('./const/connection_status');
const Query = require('./cmd/query');
class ConnectionCallback {
#conn;
constructor(conn) {
this.#conn = conn;
}
get threadId() {
return this.#conn.info ? this.#conn.info.threadId : null;
}
get info() {
return this.#conn.info;
}
#noop = () => {};
release = (cb) => {
this.#conn.release(() => {
if (cb) cb();
});
};
/**
* Permit changing user during connection.
* All user variables will be reset, Prepare commands will be released.
* !!! mysql has a bug when CONNECT_ATTRS capability is set, that is default !!!!
*
* @param options connection options
* @param callback callback function
*/
changeUser(options, callback) {
let _options, _cb;
if (typeof options === 'function') {
_cb = options;
_options = undefined;
} else {
_options = options;
_cb = callback;
}
const cmdParam = {
opts: _options,
callback: _cb
};
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam);
new Promise(this.#conn.changeUser.bind(this.#conn, cmdParam))
.then(() => {
if (cmdParam.callback) cmdParam.callback(null, null, null);
})
.catch(cmdParam.callback || this.#noop);
}
/**
* Start transaction
*
* @param callback callback function
*/
beginTransaction(callback) {
this.query('START TRANSACTION', null, callback);
}
/**
* Commit a transaction.
*
* @param callback callback function
*/
commit(callback) {
this.#conn.changeTransaction(
{ sql: 'COMMIT' },
() => {
if (callback) callback(null, null, null);
},
callback || this.#noop
);
}
/**
* Roll back a transaction.
*
* @param callback callback function
*/
rollback(callback) {
this.#conn.changeTransaction(
{ sql: 'ROLLBACK' },
() => {
if (callback) callback(null, null, null);
},
callback || this.#noop
);
}
/**
* Execute query using text protocol with callback emit columns/data/end/error
* events to permit streaming big result-set
*
* @param sql sql parameter Object can be used to supersede default option.
* Object must then have sql property.
* @param values object / array of placeholder values (not mandatory)
* @param callback callback function
*/
query(sql, values, callback) {
const cmdParam = ConnectionCallback._PARAM(this.#conn.opts, sql, values, callback);
return ConnectionCallback._QUERY_CMD(this.#conn, cmdParam);
}
/**
* Execute a query returning a Readable Object that will emit columns/data/end/error events
* to permit streaming big result-set
*
* @param sql sql parameter Object can be used to supersede the default option.
* Object must then have `sql` property.
* @param values object / array of placeholder values (not mandatory)
* @returns {Readable}
*/
queryStream(sql, values) {
const cmdParam = ConnectionCallback._PARAM(this.#conn.opts, sql, values);
const cmd = ConnectionCallback._QUERY_CMD(this.#conn, cmdParam);
return cmd.stream();
}
static _QUERY_CMD(conn, cmdParam) {
let cmd;
if (cmdParam.callback) {
cmdParam.opts = cmdParam.opts ? Object.assign(cmdParam.opts, { metaAsArray: true }) : { metaAsArray: true };
cmd = new Query(
([rows, meta]) => {
cmdParam.callback(null, rows, meta);
},
cmdParam.callback,
conn.opts,
cmdParam
);
} else {
cmd = new Query(
() => {},
() => {},
conn.opts,
cmdParam
);
}
cmd.handleNewRows = (row) => {
cmd._rows[cmd._responseIndex].push(row);
cmd.emit('data', row);
};
conn.addCommand(cmd, true);
cmd.stream = (opt) => cmd._stream(conn.socket, opt);
return cmd;
}
execute(sql, values, callback) {
const cmdParam = ConnectionCallback._PARAM(this.#conn.opts, sql, values, callback);
cmdParam.opts = cmdParam.opts ? Object.assign(cmdParam.opts, { metaAsArray: true }) : { metaAsArray: true };
this.#conn.prepareExecute(
cmdParam,
([rows, meta]) => {
if (cmdParam.callback) {
cmdParam.callback(null, rows, meta);
}
},
(err) => {
if (cmdParam.callback) {
cmdParam.callback(err);
}
}
);
}
static _PARAM(options, sql, values, callback) {
let _cmdOpt,
_sql,
_values = values,
_cb = callback;
if (typeof values === 'function') {
_cb = values;
_values = undefined;
}
if (typeof sql === 'object') {
_cmdOpt = sql;
_sql = _cmdOpt.sql;
if (_cmdOpt.values) _values = _cmdOpt.values;
} else {
_sql = sql;
}
const cmdParam = {
sql: _sql,
values: _values,
opts: _cmdOpt,
callback: _cb
};
if (options.trace) Error.captureStackTrace(cmdParam, ConnectionCallback._PARAM);
return cmdParam;
}
static _EXECUTE_CMD(conn, cmdParam) {
new Promise(conn.prepare.bind(conn, cmdParam))
.then((prepare) => {
const opts = cmdParam.opts ? Object.assign(cmdParam.opts, { metaAsArray: true }) : { metaAsArray: true };
return prepare
.execute(cmdParam.values, opts, null, cmdParam.stack)
.then(([rows, meta]) => {
if (cmdParam.callback) {
cmdParam.callback(null, rows, meta);
}
})
.finally(() => prepare.close());
})
.catch((err) => {
if (conn.opts.logger.error) conn.opts.logger.error(err);
if (cmdParam.callback) cmdParam.callback(err);
});
}
prepare(sql, callback) {
let _cmdOpt, _sql;
if (typeof sql === 'object') {
_cmdOpt = sql;
_sql = _cmdOpt.sql;
} else {
_sql = sql;
}
const cmdParam = {
sql: _sql,
opts: _cmdOpt,
callback: callback
};
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam);
return new Promise(this.#conn.prepare.bind(this.#conn, cmdParam))
.then((prepare) => {
if (callback) callback(null, prepare, null);
})
.catch(callback || this.#noop);
}
/**
* Execute a batch
* events to permit streaming big result-set
*
* @param sql sql parameter Object can be used to supersede the default options.
* Object must then have `sql` property.
* @param values object / array of placeholder values (not mandatory)
* @param callback callback
*/
batch(sql, values, callback) {
const cmdParam = ConnectionCallback._PARAM(this.#conn.opts, sql, values, callback);
this.#conn.batch(
cmdParam,
(res) => {
if (cmdParam.callback) cmdParam.callback(null, res);
},
(err) => {
if (cmdParam.callback) cmdParam.callback(err);
}
);
}
/**
* Import sql file.
*
* @param opts JSON array with 2 possible fields: file and database
* @param cb callback
*/
importFile(opts, cb) {
if (!opts || !opts.file) {
if (cb)
cb(
Errors.createError(
'SQL file parameter is mandatory',
Errors.ER_MISSING_SQL_PARAMETER,
this.#conn.info,
'HY000',
null,
false,
null
)
);
return;
}
new Promise(this.#conn.importFile.bind(this.#conn, { file: opts.file, database: opts.database }))
.then(() => {
if (cb) cb();
})
.catch((err) => {
if (cb) cb(err);
});
}
/**
* Send an empty MySQL packet to ensure connection is active, and reset @@wait_timeout
* @param timeout (optional) timeout value in ms. If reached, throw error and close connection
* @param callback callback
*/
ping(timeout, callback) {
let _cmdOpt = {},
_cb;
if (typeof timeout === 'function') {
_cb = timeout;
} else {
_cmdOpt.timeout = timeout;
_cb = callback;
}
const cmdParam = {
opts: _cmdOpt,
callback: _cb
};
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam);
new Promise(this.#conn.ping.bind(this.#conn, cmdParam)).then(_cb || this.#noop).catch(_cb || this.#noop);
}
/**
* Send a reset command that will
* - rollback any open transaction
* - reset transaction isolation level
* - reset session variables
* - delete user variables
* - remove temporary tables
* - remove all PREPARE statement
*
* @param callback callback
*/
reset(callback) {
const cmdParam = {};
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam);
return new Promise(this.#conn.reset.bind(this.#conn, cmdParam))
.then(callback || this.#noop)
.catch(callback || this.#noop);
}
/**
* Indicates the state of the connection as the driver knows it
* @returns {boolean}
*/
isValid() {
return this.#conn.isValid();
}
/**
* Terminate connection gracefully.
*
* @param callback callback
*/
end(callback) {
const cmdParam = {};
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam);
new Promise(this.#conn.end.bind(this.#conn, cmdParam))
.then(() => {
if (callback) callback();
})
.catch(callback || this.#noop);
}
/**
* Alias for destroy.
*/
close() {
this.destroy();
}
/**
* Force connection termination by closing the underlying socket and killing server process if any.
*/
destroy() {
this.#conn.destroy();
}
pause() {
this.#conn.pause();
}
resume() {
this.#conn.resume();
}
format(sql, values) {
this.#conn.format(sql, values);
}
/**
* return current connected server version information.
*
* @returns {*}
*/
serverVersion() {
return this.#conn.serverVersion();
}
/**
* Change option "debug" during connection.
* @param val debug value
*/
debug(val) {
return this.#conn.debug(val);
}
debugCompress(val) {
return this.#conn.debugCompress(val);
}
escape(val) {
return this.#conn.escape(val);
}
escapeId(val) {
return this.#conn.escapeId(val);
}
//*****************************************************************
// internal public testing methods
//*****************************************************************
get __tests() {
return this.#conn.__tests;
}
connect(callback) {
if (!callback) {
throw new Errors.createError(
'missing mandatory callback parameter',
Errors.ER_MISSING_PARAMETER,
this.#conn.info
);
}
switch (this.#conn.status) {
case Status.NOT_CONNECTED:
case Status.CONNECTING:
case Status.AUTHENTICATING:
case Status.INIT_CMD:
this.once('connect', callback);
break;
case Status.CONNECTED:
callback.call(this);
break;
case Status.CLOSING:
case Status.CLOSED:
callback.call(
this,
Errors.createError(
'Connection closed',
Errors.ER_CONNECTION_ALREADY_CLOSED,
this.#conn.info,
'08S01',
null,
true
)
);
break;
}
}
//*****************************************************************
// EventEmitter proxy methods
//*****************************************************************
on(eventName, listener) {
this.#conn.on.call(this.#conn, eventName, listener);
return this;
}
off(eventName, listener) {
this.#conn.off.call(this.#conn, eventName, listener);
return this;
}
once(eventName, listener) {
this.#conn.once.call(this.#conn, eventName, listener);
return this;
}
listeners(eventName) {
return this.#conn.listeners.call(this.#conn, eventName);
}
addListener(eventName, listener) {
this.#conn.addListener.call(this.#conn, eventName, listener);
return this;
}
eventNames() {
return this.#conn.eventNames.call(this.#conn);
}
getMaxListeners() {
return this.#conn.getMaxListeners.call(this.#conn);
}
listenerCount(eventName, listener) {
return this.#conn.listenerCount.call(this.#conn, eventName, listener);
}
prependListener(eventName, listener) {
this.#conn.prependListener.call(this.#conn, eventName, listener);
return this;
}
prependOnceListener(eventName, listener) {
this.#conn.prependOnceListener.call(this.#conn, eventName, listener);
return this;
}
removeAllListeners(eventName, listener) {
this.#conn.removeAllListeners.call(this.#conn, eventName, listener);
return this;
}
removeListener(eventName, listener) {
this.#conn.removeListener.call(this.#conn, eventName, listener);
return this;
}
setMaxListeners(n) {
this.#conn.setMaxListeners.call(this.#conn, n);
return this;
}
rawListeners(eventName) {
return this.#conn.rawListeners.call(this.#conn, eventName);
}
}
module.exports = ConnectionCallback;