UNPKG

mariadb

Version:
533 lines (474 loc) 13.2 kB
// 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;