520 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			520 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| //  SPDX-License-Identifier: LGPL-2.1-or-later
 | |
| //  Copyright (c) 2015-2024 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 to change 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);
 | |
|   }
 | |
| 
 | |
|   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)
 | |
|       .then(([rows, meta]) => {
 | |
|         if (cmdParam.callback) {
 | |
|           cmdParam.callback(null, rows, meta);
 | |
|         }
 | |
|       })
 | |
|       .catch((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 default option.
 | |
|    *                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);
 | |
|     return ConnectionCallback._BATCH_CMD(this.#conn, cmdParam);
 | |
|   }
 | |
| 
 | |
|   static _BATCH_CMD(conn, cmdParam) {
 | |
|     conn
 | |
|       .batch(cmdParam)
 | |
|       .then((res) => {
 | |
|         if (cmdParam.callback) cmdParam.callback(null, res);
 | |
|       })
 | |
|       .catch((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;
 |