UNPKG

sharp-db

Version:

Classes for running SQL and building select queries for MySQL in Node

902 lines (848 loc) 56.1 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>Db/Db.js - Documentation</title> <script src="scripts/prettify/prettify.js"></script> <script src="scripts/prettify/lang-css.js"></script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger" /> <label for="nav-trigger" class="navicon-button x"> <div class="navicon"></div> </label> <label for="nav-trigger" class="overlay"></label> <nav> <li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="DataBroker.html">DataBroker</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="DataBroker.html#cleanup">cleanup</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="DataBroker.html#created_and_modified">created_and_modified</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="DataBroker.html#createdAndModified">createdAndModified</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="DataBroker.html#delete">delete</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="DataBroker.html#insert">insert</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Db.html">Db</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#.destroyAll">destroyAll</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#.endAll">endAll</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#.factory">factory</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#.withInstance">withInstance</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#beginTransaction">beginTransaction</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#bindArgs">bindArgs</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#commit">commit</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#connect">connect</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#connectOnce">connectOnce</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#delete">delete</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#deleteFrom">deleteFrom</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#destroy">destroy</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#emit">emit</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#emitError">emitError</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#end">end</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#escape">escape</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#escapeLike">escapeLike</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#escapeQuoteless">escapeQuoteless</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#exportAsSql">exportAsSql</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#insert">insert</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#insertExtended">insertExtended</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#insertInto">insertInto</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#insertIntoOnDuplicateKeyUpdate">insertIntoOnDuplicateKeyUpdate</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#multiQuery">multiQuery</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#query">query</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#quote">quote</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#rollback">rollback</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#select">select</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectByKey">selectByKey</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectExists">selectExists</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectFirst">selectFirst</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectFrom">selectFrom</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectGrouped">selectGrouped</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectHash">selectHash</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectId">selectId</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectIndexed">selectIndexed</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectList">selectList</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectOrCreate">selectOrCreate</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectOrCreateId">selectOrCreateId</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectUuid">selectUuid</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#selectValue">selectValue</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#startTransaction">startTransaction</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#tpl">tpl</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#update">update</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Db.html#updateTable">updateTable</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="DbEvent.html">DbEvent</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Parser.html">Parser</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_extractSubqueries">_extractSubqueries</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleConditions">_handleConditions</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleCrossJoin">_handleCrossJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleFrom">_handleFrom</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleFullJoin">_handleFullJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleFullOuterJoin">_handleFullOuterJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleGroupBy">_handleGroupBy</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleHaving">_handleHaving</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleInnerJoin">_handleInnerJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleLeftJoin">_handleLeftJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleLeftOuterJoin">_handleLeftOuterJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleLimit">_handleLimit</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleOffset">_handleOffset</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleOrderBy">_handleOrderBy</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleRightJoin">_handleRightJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleRightOuterJoin">_handleRightOuterJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleSelect">_handleSelect</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_handleWhere">_handleWhere</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_injectSubqueries">_injectSubqueries</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_split">_split</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#_stripComments">_stripComments</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Parser.html#parse">parse</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Select.html">Select</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#._extractBindingName">_extractBindingName</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#.init">init</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#.parse">parse</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#_conditions">_conditions</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#_isEntirelyDigits">_isEntirelyDigits</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#_isEntirelyDigitsNoZeros">_isEntirelyDigitsNoZeros</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#_isPlaceholder">_isPlaceholder</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#_spliceChildData">_spliceChildData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#_spliceSiblingData">_spliceSiblingData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#bind">bind</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#column">column</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#columns">columns</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#crossJoin">crossJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#escape">escape</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#escapeLike">escapeLike</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#escapeQuoteless">escapeQuoteless</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fetch">fetch</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fetchFirst">fetchFirst</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fetchGrouped">fetchGrouped</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fetchHash">fetchHash</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fetchIndexed">fetchIndexed</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fetchList">fetchList</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fetchValue">fetchValue</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#foundRows">foundRows</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#from">from</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fullJoin">fullJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#fullOuterJoin">fullOuterJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#getClone">getClone</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#getFoundRowsQuery">getFoundRowsQuery</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#getFoundRowsSql">getFoundRowsSql</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#groupBy">groupBy</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#having">having</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#innerJoin">innerJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#join">join</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#leftJoin">leftJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#leftOuterJoin">leftOuterJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#limit">limit</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#normalized">normalized</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#offset">offset</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#option">option</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#orderBy">orderBy</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#orHaving">orHaving</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#orWhere">orWhere</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#page">page</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#parse">parse</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#reset">reset</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#rightJoin">rightJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#rightOuterJoin">rightOuterJoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#sortField">sortField</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#table">table</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#tables">tables</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#toBoundSql">toBoundSql</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#toString">toString</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#unbind">unbind</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#unjoin">unjoin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#where">where</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#whereBetween">whereBetween</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#withChildData">withChildData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Select.html#withSiblingData">withSiblingData</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Ssh.html">Ssh</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Ssh.html#end">end</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Ssh.html#tunnelTo">tunnelTo</a></span></li> </nav> <div id="main"> <h1 class="page-title">Db/Db.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>const { EventEmitter } = require('events'); const mysql = require('mysql2'); const Ssh = require('../Ssh/Ssh.js'); const { isPlainObject } = require('is-plain-object'); const decorateError = require('../decorateError/decorateError.js'); const SqlBuilder = require('../SqlBuilder/SqlBuilder.js'); const DbEvent = require('../DbEvent/DbEvent.js'); const noop = () => {}; /** * Simple database class for mysql */ class Db extends EventEmitter { /** * Specify connection details for MySQL and optionally SSH * @param {Object} [config] MySQL connection details such as host, login, password, encoding, database * @see https://github.com/mysqljs/mysql#connection-options * @param {Object} [sshConfig] SSH connection details including host, port, user, privateKey * @see https://github.com/mscdex/ssh2#client-methods */ constructor(config = {}, sshConfig = null) { super(); const env = process.env; /** * The config used for this instance * @type {Object} */ this.config = { ...config, host: config.host || env.DB_HOST || env.RDS_HOSTNAME || '127.0.0.1', user: config.user || env.DB_USER || env.RDS_USERNAME || 'root', password: config.password || env.DB_PASSWORD || env.RDS_PASSWORD || '', database: config.database || env.DB_DATABASE || env.RDS_DATABASE || undefined, port: config.port || env.DB_PORT || env.RDS_PORT || 3306, charset: config.charset || env.DB_CHARSET || env.RDS_CHARSET || 'utf8mb4', }; if (sshConfig) { /** * The Ssh instance for tunnelling * @type {Ssh} */ this.ssh = new Ssh(sshConfig); } Db.instances.push(this); } /** * Emit an event and associated data * @param {String} type The event name * @param {Object} data Data to send with event * @return {DbEvent} */ emit(type, data = {}) { const evt = new DbEvent({ type, subtype: null, target: this, error: null, data, }); super.emit(type, evt); return evt; } /** * Emit a dbError event and associated data * @param {String} subtype The name of the event that would have been called in a success case * @param {Error} error The error that was raised * @param {Object} data Data to send with event * @return {DbEvent} */ emitError(subtype, error, data = {}) { const type = 'dbError'; const evt = new DbEvent({ type, subtype, target: this, error, data, }); super.emit(type, evt); return evt; } /** * Create a new QuickDb instance or return the last used one. * Specify connection details for MySQL and optionally SSH * @param {Object} [config] MySQL connection details such as host, login, password, encoding, database * @see https://github.com/mysqljs/mysql#connection-options * @param {Object} [sshConfig] SSH connection details including host, port, user, privateKey * @see https://github.com/mscdex/ssh2#client-methods * @return {Db} */ static factory(config = {}, sshConfig = null) { if (Db.instances.length === 0) { return new Db(config, sshConfig); } return Db.instances[Db.instances.length - 1]; } /** * Make a new connection to MySQL * @param {Object} [overrides] Additional connection params * @return {Promise&lt;Object>} The mysql connection object * @see https://github.com/mysqljs/mysql#connection-options */ async connect(overrides = {}) { if (this.ssh) { await this.ssh.tunnelTo(this); this.emit('sshConnect', this.ssh); } /** * The mysql2 library connection object * @type {Object} */ this.connection = mysql.createConnection({ ...this.config, ...overrides }); return new Promise((resolve, reject) => { this.connection.connect(error => { if (error) { decorateError(error); this.emitError('connect', error); reject(error); } else { this.emit('connect', { connection: this.connection }); resolve(this.connection); } }); }); } /** * Make a new connection to MySQL if not already connected */ async connectOnce() { if (!this.connection) { await this.connect(); } } /** * Close this connection to the database * @return {Promise} Resolves when connection has been closed */ end() { if (this.ssh) { this.ssh.end(); this.emit('sshDisconnect'); } return new Promise((resolve, reject) => { if (this.connection) { const idx = Db.instances.indexOf(this); if (idx > -1) { Db.instances.splice(idx, 1); } this.connection.end(error => { if (error) { decorateError(error); this.emitError('disconnect', error); reject(error); } else { this.emit('disconnect'); resolve(); } }); } else { resolve(); } }); } /** * Destroy the connection to the database * @return {Db} */ destroy() { if (this.ssh) { this.ssh.end(); this.emit('sshDisconnect'); } if (this.connection &amp;&amp; this.connection.destroy) { const idx = Db.instances.indexOf(this); if (idx > -1) { Db.instances.splice(idx, 1); } this.connection.destroy(); this.emit('disconnect'); } return this; } /** * Close all connections to the database * @return {Promise} Resolves when all connections have been closed */ static endAll() { return Promise.all(Db.instances.map(db => db.end())); } /** * Destroy all connections to the database * @return {Db} */ static destroyAll() { Db.instances.forEach(db => db.destroy()); return Db; } /** * Run a statement of any type * @param {String|Object} sql The sql to run * @param {*} ...bindVars Values to bind to the sql placeholders * @returns {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Array} results The result rows * @property {Object[]} fields Info about the selected fields */ async query(sql, ...bindVars) { await this.connectOnce(); const options = this.bindArgs(sql, bindVars); return new Promise((resolve, reject) => { const query = this.connection.query(options, (error, results, fields) => { if (error) { decorateError(error, options); this.emitError('query', error); reject(error); } else { const result = { query, results, fields }; const evt = this.emit('query', result); resolve(evt.data); } }); }); } /** * Run multiple statements separated by semicolon * @param {String|Object} sql The sql to run * @param {*} ...bindVars Values to bind to the sql placeholders * @returns {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Array} results One element for every statement in the query * @property {Object[]} fields Info about the selected fields */ async multiQuery(sql, ...bindVars) { await this.connectOnce(); const options = this.bindArgs(sql, bindVars); options.multipleStatements = true; return this.query(options); } /** * Return result rows for the given SELECT statement * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Array} results The result rows * @property {Object[]} fields Info about the selected fields */ async select(sql, ...bindVars) { await this.connectOnce(); const options = this.bindArgs(sql, bindVars); return new Promise((resolve, reject) => { const query = this.connection.query(options, (error, results, fields) => { if (error) { decorateError(error, options); this.emitError('select', error); reject(error); } else { const result = { query, results, fields }; const evt = this.emit('select', result); resolve(evt.data); } }); }); } /** * Return result array as col1 => col2 pairs for the given SELECT statement * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object} results The result object with key-value pairs * @property {Object[]} fields Info about the selected fields */ async selectHash(sql, ...bindVars) { const { query, results, fields } = await this.select(sql, ...bindVars); const key = fields[0].name; const val = fields[1].name; const hash = {}; results.forEach(result => { hash[result[key]] = result[val]; }); return { query, results: hash, fields }; } /** * Return result array as col1 for the given SELECT statement * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Array} results The result list * @property {Object[]} fields Info about the selected fields */ async selectList(sql, ...bindVars) { const { query, results, fields } = await this.select(sql, ...bindVars); const name = fields[0].name; const list = results.map(result => result[name]); return { query, results: list, fields, }; } /** * Return records all grouped by one of the column's values * @param {String} groupField The name of the field to group by * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object} results Result rows grouped by groupField * @property {Object[]} fields Info about the selected fields */ async selectGrouped(groupField, sql, ...bindVars) { const { query, results, fields } = await this.select(sql, ...bindVars); const groups = {}; results.forEach(result => { if (!groups[result[groupField]]) { groups[result[groupField]] = []; } groups[result[groupField]].push(result); }); return { query, results: groups, fields, }; } /** * Return records all indexed by one of the column's values * @param {String} indexField The name of the field to index by * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object} results The results indexed by indexField * @property {Object[]} fields Info about the selected fields */ async selectIndexed(indexField, sql, ...bindVars) { const { query, results, fields } = await this.select(sql, ...bindVars); const hash = {}; results.forEach(result => { hash[result[indexField]] = result; }); return { query, results: hash, fields, }; } /** * Return first result row for the given SELECT statement * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object|undefined} results The result row or undefined * @property {Object[]} fields Info about the selected fields */ async selectFirst(sql, ...bindVars) { const { query, results, fields } = await this.select(sql, ...bindVars); return { query, results: results[0], fields, }; } /** * Return first column value for the first result row for the given SELECT statement * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {*} results The value returned in the first field of the first row * @property {Object[]} fields Info about the selected fields */ async selectValue(sql, ...bindVars) { const { query, results, fields } = await this.select(sql, ...bindVars); let value = undefined; if (results.length > 0) { const name = fields[0].name; value = results[0][name]; } return { query, results: value, fields, }; } /** * Run the given SELECT statement wrapped in a SELECT EXISTS query * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Boolean} results True if any records match query * @property {Object[]} fields Info about the selected fields */ async selectExists(sql, ...bindVars) { const options = typeof sql === 'object' ? sql : { sql }; options.sql = `SELECT EXISTS (${options.sql}) AS does_it_exist`; const { query, results, fields } = await this.select(options, ...bindVars); const doesItExist = results[0] ? Boolean(results[0].does_it_exist) : false; return { query, results: doesItExist, fields, }; } /** * Run the given INSERT statement * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Number} insertId The id of the last inserted record */ async insert(sql, ...bindVars) { await this.connectOnce(); const options = this.bindArgs(sql, bindVars); return new Promise((resolve, reject) => { const query = this.connection.query(options, (error, results) => { if (error) { decorateError(error, options); this.emitError('insert', error); reject(error); } else { const result = { query, insertId: results.insertId, affectedRows: results.affectedRows, changedRows: results.changedRows, }; const evt = this.emit('insert', result); resolve(evt.data); } }); }); } /** * Run the given UPDATE statement * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Number} affectedRows The number of rows matching the WHERE criteria * @property {Number} changedRows The number of rows affected by the statement */ async update(sql, ...bindVars) { await this.connectOnce(); const options = this.bindArgs(sql, bindVars); return new Promise((resolve, reject) => { const query = this.connection.query(options, (error, results) => { if (error) { decorateError(error, options); this.emitError('update', error); reject(error); } else { const result = { query, affectedRows: results.affectedRows, changedRows: results.changedRows, }; const evt = this.emit('update', result); resolve(evt.data); } }); }); } /** * Run the given DELETE statement * @param {String|Object} sql The SQL to run * @param {*} ...bindVars The values to bind to the each question mark or named binding * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Number} changedRows The number of rows affected by the statement */ async delete(sql, ...bindVars) { await this.connectOnce(); const options = this.bindArgs(sql, bindVars); return new Promise((resolve, reject) => { const query = this.connection.query(options, (error, results) => { if (error) { decorateError(error, options); this.emitError('delete', error); reject(error); } else { const result = { query, affectedRows: results.affectedRows, changedRows: results.changedRows, }; const evt = this.emit('delete', result); resolve(evt.data); } }); }); } /** * Build a SELECT statement and return result rows * @param {String} table The name of the table * @param {Array} fields An array of field names to select * @param {Object} criteria Params to construct the WHERE clause - see SqlBuilder#buildWhere * @param {String} extra Additional raw SQL such as GROUP BY, ORDER BY, or LIMIT * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Array} results The result rows * @property {Object[]} fields Info about the selected fields * @see SqlBuilder#buildWhere */ selectFrom(table, fields = [], criteria = {}, extra = '') { const sql = SqlBuilder.selectFrom(table, fields, criteria, extra); return this.select(sql); } /** * Select the record with the given column value * @param {String} table The name of the table from which to select * @param {String} column The name of the column from which to select * @param {String} value The value of the record for that column * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object|undefined} results The result row or undefined * @property {Object[]} fields Info about the selected fields */ selectByKey(table, column, value) { const sql = SqlBuilder.selectBy(table, column, value); return this.selectFirst(sql); } /** * Select the record with the given id * @param {String} table The name of the table from which to select * @param {String} id The value of the id column * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object|undefined} results The result row or undefined * @property {Object[]} fields Info about the selected fields */ selectId(table, id) { return this.selectByKey(table, 'id', id); } /** * Select the record with the given UUID * @param {String} table The name of the table from which to select * @param {String} uuid The value of the uuid column * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object|undefined} results The result row or undefined * @property {Object[]} fields Info about the selected fields */ selectUuid(table, uuid) { return this.selectByKey(table, 'uuid', uuid); } /** * Find a record or add a new one * @param {String} table The name of the table from which to select * @param {Object} criteria Criteria by which to find the row * @param {Object} newValues The values to use to insert if the record doesn't exist * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object|undefined} results The result row or undefined * @property {Object[]} fields Info about the selected fields * @property {Number} insertId The id of the last inserted record */ selectOrCreate(table, criteria, newValues) { return this.selectFrom(table, ['*'], criteria).then( async ({ query, results, fields }) => { if (results.length > 0) { return { query, results: results[0], insertId: null, affectedRows: 0, changedRows: 0, fields, }; } try { const { query, insertId, affectedRows, changedRows } = await this.insertInto(table, newValues); if (!insertId) { throw new Error(`Unknown error getting insertId from ${query}`); } const { results: newRows, fields } = await this.selectFrom( table, ['*'], criteria ); if (!newRows || !newRows[0]) { throw new Error( `Error fetching newly created record from ${table}` ); } return { query, results: newRows[0], insertId, affectedRows, changedRows, fields, }; } catch (e) { return Promise.reject(e); } }, /* istanbul ignore next */ err => err ); } /** * Find a record's id or add a new one * @param {String} table The name of the table from which to select * @param {Object} criteria Criteria by which to find the row * @param {Object} newValues The values to use to insert if the record doesn't exist * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Object} results The result row or new values * @property {Object[]} fields Info about the selected fields * @property {Number} insertId The id of the last inserted record */ selectOrCreateId(table, criteria, newValues) { return this.selectFrom(table, ['id'], criteria).then( async ({ query, results, fields }) => { if (results.length > 0) { return { query, results: results[0].id, fields, }; } else { return this.insertInto(table, newValues).then( ({ query, insertId, fields }) => { return { query, results: insertId, fields, }; } ); } }, /* istanbul ignore next */ err => err ); } /** * Build an INSERT statement and run it * @param {String} table The name of the table * @param {Object} insert column-value pairs to insert * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Number} insertId The id of the last inserted record */ insertInto(table, insert) { const sql = SqlBuilder.insertInto(table, insert); return this.insert(sql); } /** * Run an "INSERT INTO ... ON DUPLICATE KEY UPDATE" query where * if a key conflicts, update the given fields * @param {String} table The name of the table * @param {Object} insert An array with column => value pairs for insertion * @param {Object} update An array with column => value pairs for update * @return {Promise&lt;Object>} * @property {Number} insertId The id of the last inserted or updated record * @property {Number} affectedRows The number of rows matching the WHERE criteria * @property {Number} changedRows The number of rows affected by the statement */ async insertIntoOnDuplicateKeyUpdate(table, insert, update) { const sql = SqlBuilder.insertIntoOnDuplicateKeyUpdate( table, insert, update ); return this.insert(sql); } /** * Build an INSERT statement and run it * @param {String} table The name of the table * @param {Array} rows An Array of objects, each with column-value pairs to insert * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Number} insertId The id of the last inserted record */ insertExtended(table, rows) { const sql = SqlBuilder.insertExtended(table, rows); return this.insert(sql); } /** * Build an UPDATE statement and run it * @param {String} table The name of the table * @param {Object} set An array of column-value pairs to update * @param {Object} where Params to construct the WHERE clause - see SqlBuilder#buildWheres * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Number} affectedRows The number of rows matching the WHERE criteria * @property {Number} changedRows The number of rows affected by the statement * @see SqlBuilder#buildWheres */ updateTable(table, set, where = {}) { const sql = SqlBuilder.updateTable(table, set, where); return this.update(sql, set); } /** * Construct a DELETE query and run * @param {String} table The name of the table from which to delete * @param {Object} where WHERE conditions on which to delete - see SqlBuilder#buildWheres * @param {Number} limit Limit deletion to this many records * @return {Promise&lt;Object>} * @property {String} query The final SQL that was executed * @property {Number} affectedRows The number of rows matching the WHERE criteria * @property {Number} changedRows The number of rows affected by the statement * @see SqlBuilder#buildWheres */ async deleteFrom(table, where, limit = null) { const sql = SqlBuilder.deleteFrom(table, where, limit); return this.delete(sql); } /** * Construct INSERT statements suitable for a backup * @param {String} table The name of the table from which to fetch records * @param {Object} where WHERE conditions on which to fetch records - see SqlBuilder#buildWheres * @see SqlBuilder#buildWheres * @param {Object} options Additional options * @property {Number} [limit=0] Limit export to this many records * @property {Number} [chunkSize=250] If > 0, restrict INSERT STATEMENTS to a maximum of this many records * @property {Boolean} [discardIds=false] If true, columns selected as "id" will have a NULL value * @property {Boolean} [disableForeignKeyChecks=false] If true, add statements to disable and re-enable foreign key checks * @property {Boolean} [lockTables=false] If true, add statements to lock and unlock tables * @return {Promise&lt;Object>} * @property {String} results The exported SQL * @property {String} query The SQL that was executed to fetch records * @property {Object[]} fields Details on the fields fetched * @property {Number} affectedRows The number of rows matching the WHERE criteria * @property {Number} chunks The number of chunks of rows */ async exportAsSql( table, where = {}, { limit = 0, chunkSize = 250, discardIds = false, truncateTable = false, disableForeignKeyChecks = false, lockTables = false, } = {} ) { // get results const additional = limit > 0 ? `LIMIT ${limit}` : ''; const { results: rows, fields, query, } = await this.selectFrom(table, [], where, additional); if (rows.length === 0) { return { results: '', fields, query, affectedRows: 0, chunks: 0, }; } const sql = SqlBuilder.exportRows(table, rows, { fields, chunkSize, discardIds, truncateTable, disableForeignKeyChecks, lockTables, }); return { results: sql, fields, query, affectedRows: rows.length, chunks: Math.ceil(rows.length / chunkSize), }; } /** * run START TRANSACTION * @alias Db#beginTransaction * @return {Promise&lt;Object>} */ startTransaction() { this.emit('startTransaction'); return this.query('START TRANSACTION'); } /** * run START TRANSACTION * @alias Db#startTransaction * @return {Promise&lt;Object>} */ beginTransaction() { return this.startTransaction(); } /** * run COMMIT * @return {Promise&lt;Object>} */ commit() { this.emit('commit'); return this.query('COMMIT'); } /** * run ROLLBACK * @return {Promise&lt;Object>} */ rollback() { this.emit('rollback'); return this.query('ROLLBACK'); } /** * Bind an arguments to a query * @param {String|Object} sql The base SQL query * @param {*} args A value, an object with key/value paris, or an array of values to bind * @return {Object} * @property {String} sql The final SQL with bound values replaced * @example * db.select('SELECT * FROM users WHERE id = ?', 100); * db.bindA