sharp-db
Version:
Classes for running SQL and building select queries for MySQL in Node
902 lines (848 loc) • 56.1 kB
HTML
<!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<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 && 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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<Object>}
*/
startTransaction() {
this.emit('startTransaction');
return this.query('START TRANSACTION');
}
/**
* run START TRANSACTION
* @alias Db#startTransaction
* @return {Promise<Object>}
*/
beginTransaction() {
return this.startTransaction();
}
/**
* run COMMIT
* @return {Promise<Object>}
*/
commit() {
this.emit('commit');
return this.query('COMMIT');
}
/**
* run ROLLBACK
* @return {Promise<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