Queryize.js

A no-frills chainable interface for constructing mutable MySQL queries procedurally in Node.js

npm install queryize View on GitHub Report an Issue

NPM Version License: MIT Node 8.12 or Later Downloads/Week Build Status

var insert = queryize.insert;
var q = insert()
  .into('users', 'u')
  .set({name: 'John Doe', email: 'user@example.com'})
  .compile();

//q.query contains INSERT INTO users SET u.name = ?, u.email = ?
//q.data contains  ['John Doe', 'user@example.com']

Supports extension for node-mysql and node-mysql2 for inline evaluation with promises.

var queryize = require('queryize');
const mysql = require('mysql2/promise');
const pool = mysql.createPool({database: test});
queryize.evalFunction = (sql, data) => pool.query(sql, data);

queryize()
  .from('employees').select()
  .where({city: ['Chicago', 'Miami', 'San Francisco']})
  .orderBy('lastname', 'firstname')
  .then((results) => {
    console.log(results);
  })
  .catch((err) => {
  	console.error(err);
  });

Creates a query object which encapsulates the state of the query to be generated
and provides the methods to manipulate that query.

Usage:

queryize([original])
  1. original query, Object Optional

    An existing query object to duplicate.

var queryize = require('queryize');
var query = queryize();
View Code
var queryize = function (original) {
  var query = {};

  // bind all mutators directly to query
  for (const [ key, fn ] of Object.entries(mutators)) {
    query[key] = (typeof fn === 'function' ? fn.bind(query) : fn);
  }

  query._constructor = queryize;
  query._isQueryizeObject = true;
  query._evalFunction = queryize.evalFunction;
  query._attributes = {
    debugEnabled: false,
    database: false,
    tableName: false,
    fromSubquery: false,
    alias: false,
    dataBindings: {},
    useBoundParameters: queryize.useBoundParameters,
    where: [],
    whereBoolean: null,
    set: [],
    setKeys: {},
    columns: [ '*' ],
    joins: [],
    orderBy: false,
    groupBy: false,
    distinct: false,
    limit: false,
    asName: false,
    builder: false,
  };

  if (original) {
    if (original._isQueryizeObject) {
      Object.assign(query._attributes, original.export());
    } else if (typeof original === 'object') {
      Object.assign(query._attributes, original);
    }
  }

  return query;
};

Shortcut for creating a new select query

See query.select() for details.

Usage:

queryize.select()
View Code
queryize.select = function (...args) {
  var q = queryize();
  q.select(...args);
  return q;
};

Shortcut for creating a new update query

See query.update() for details.

Usage:

queryize.update()
View Code
queryize.update = function (...args) {
  var q = queryize();
  q.update(...args);
  return q;
};

Shortcut for creating a new insert query

See query.insert() for details.

Usage:

queryize.insert()
View Code
queryize.insert = function (...args) {
  var q = queryize();
  q.insert(...args);
  return q;
};

Shortcut for creating a new replace query

See query.replace() for details.

Usage:

queryize.replace()
View Code
queryize.replace = function (...args) {
  var q = queryize();
  q.replace(...args);
  return q;
};

Shortcut for creating a new delete query

See query.select() for details.

Usage:

queryize.delete()
View Code
queryize.delete = function (...args) {
  var q = queryize();
  q.deleteFrom(...args);
  return q;
};

queryize.deleteFrom = queryize.delete;

Globally controls if new queries will use bound params by default.

Usage:

//disable bound params. This is not recommended
queryize.useBoundParameters = false;

Globally defines the function to evoke when calling query.then() or query.eval()
The function on this property will be copied to query._evalFunction at instantiation.
Receives two parameters, the query string and the data array, plus anything passed to query.eval()
The return value will be passed on the promise produced by query.then() and query.eval()

Usage:

Public access point for adding functions to query objects

Usage:

queryize.fn.someNewFunction = function () {
  //`this` is the current query, use `this._attributes` to store query parameters
};

If passed a truthy value, query.eval() will output the compiled query to the console.

Usage:

query.debug(enable)
  1. enable boolean
View Code
query.debug = function debug (enable) {
  this._attributes.debugEnabled = isDefined(enable) ? enable : true;

  return this;
};

Stores the passed value under a data binding with the key name.
This allows for explicit usage of bindings within query strings.

Usage:

query.insertBinding(key, value)
  1. key string

    The binding name to store the value under

  2. value mixed

    The data to be stored

View Code
query.insertBinding = function insertBinding (key, value) {
  if (typeof value === 'object') {
    // if we've got a date, convert it into a mysql ready datestamp
    if (value instanceof Date) {
      value = mysqlDate(value);
    } else {
      throw new TypeError('Received unparsable object as field value.');
    }
  }

  if (key.substr(0, 2) !== '{{') key = '{{' + key + '}}';

  this._attributes.dataBindings[key] = value;

  return this;
};

Stores the passed data into the data bindings collection and returns a
unique string representation of that data for use in the query.

Data bindings in queryize are represented by a key name surrounded by
double brackets. These values are then converted to question marks after
the query is compiled, with the values appended to the data array.

If a modifier function name is provided, the returned binding will be wrapped with that MySQL function.

Usage:

query.createBinding(value, [modifier])
  1. value mixed

    The data to be stored

  2. modifier string Optional

    A MySQL function to wrap the binding in when it is inserted into SQL.

query.createBinding('name@example.com');
// => "{{binding0d}}"

query.createBinding('2013-06-18', 'DATE');
// => "DATE({{binding4f}})"
View Code
query.createBinding = function createBinding (value, modifier) {
  if (value === true) return 'TRUE';
  if (value === false) return 'FALSE';
  if (value === null) return 'NULL';

  var key = '{{' + _uniqueId('binding') + '}}';

  this.insertBinding(key, value);

  return modifier ? [ modifier, '(', key, ')' ].join('') : key;
};

Marks the query as being a SELECT statement.

One or more columns or an array of columns to select from may be passed
in as arguments. See query.columns() for more details.

Usage:

query.select([columns])
  1. columns string, Array.<string> Optional

    All arguments received are passed to a query.columns() call

var q = queryize()
  .select('name')
  .from('users')
  .where('user.id', 128)
  .compile()
View Code
query.select = function select (...args) {
  this._attributes.builder = 'select';
  if (arguments.length) {
    this.columns(args);
  }
  return this;
};

Marks the query as being a DELETE statement

Supports passing the target table and alias as syntatic sugar. See query.from() for more details.

Usage:

query.delete([tablename], [alias])
  1. tablename string, Array.<string> Optional

    Table to delete from. If an array is passed, defines the tables that will be deleted from in a multi-table delete.

  2. alias string Optional

    An alias to use for the table

var q = queryize()
  .delete('users')
  .where({'u.id':1})
  .compile();
var q = queryize()
  .from('logs')
  .whereInRange('dts', null, '2014-06-01')
  .deleteFrom()
  .compile()
View Code
exports.delete = query.deleteFrom = function deleteFrom (tablename, alias) {
  this._attributes.builder = 'delete';

  if (isArray(tablename)) {
    if (alias) {
      this.from(tablename.shift(), alias);
      tablename.unshift(alias);
    }
    this.columns(tablename);
  } else if (tablename) {
    this.from(tablename, alias);
  }

  return this;
};

Marks the query as being an INSERT statement

Usage:

query.insert([values])
  1. values mixed Optional

    All arguments received are passed to a query.set() call

queryize()
  .insert({name: 'joe'})
  .into('users')
View Code
query.insert = function insert (...args) {
  this._attributes.builder = 'insert';
  this._attributes.insertMode = 'INSERT';
  if (arguments.length) {
    this.set(...args);
  }
  return this;
};

Marks the query as being an INSERT IGNORE statement

Usage:

query.insertIgnore([values])
  1. values mixed Optional

    All arguments received are passed to a query.set() call

queryize()
  .insertIgnore({name: 'joe'})
  .into('users')
View Code
query.insertIgnore = function replace (...args) {
  this._attributes.builder = 'insert';
  this._attributes.insertMode = 'INSERT IGNORE';
  if (arguments.length) {
    this.set(...args);
  }
  return this;
};

Marks the query as being a REPLACE statement

Usage:

query.replace([values])
  1. values mixed Optional

    All arguments received are passed to a query.set() call

queryize()
  .replace({name: 'joe'})
  .into('users')
View Code
query.replace = function replace (...args) {
  this._attributes.builder = 'insert';
  this._attributes.insertMode = 'REPLACE';
  if (arguments.length) {
    this.set(...args);
  }
  return this;
};

Marks the query as being an UPDATE statement

Usage:

query.update([tablename], [alias])
  1. tablename string Optional

    Table to update

  2. alias string Optional

    An alias to use for the table

queryize()
  .update('users')
  .set('name', 'bob')
  .where('id', 234)
View Code
query.update = function update (tablename, alias) {
  this._attributes.builder = 'update';
  if (tablename) {
    this.table(tablename, alias);
  }
  return this;
};

Defines the table that the query should be performed on.
Can be called directly but is better called via the syntactically correct aliases.

Usage:

query.table(tablename, [alias])
  1. tablename string
  2. alias string Optional

    An alias to use for the table

queryize.update()
  .table('users')
  .set('name', 'bob')
  .where('id', 234)
var q = queryize.select()
  .from('users')
  .compile()
queryize.insert()
  .into('orders')
  .set(order)
query.from(subquery, [as])
  1. subquery query

    A query object to use as a subquery

  2. as string Optional

    Name to use for the subquery (overrides query.as() value)

queryize()
  .select('AVG(total_per_user)')
  .from(queryize()
    .select('SUM(total_invoice) AS total_per_user')
    .from('orders')
    .groupBy('userid')
    .as('order_totals');
View Code
query.table = exports.into = exports.from = function table (tablename, alias) {
  if (isQueryizeObject(tablename)) {
    if (isDefined(alias)) {
      tablename = tablename.clone().as(alias);
    }
    this._attributes.fromSubquery = this._mergeSubquery(tablename);
    return this;
  }

  this._attributes.tableName = tablename;
  if (isDefined(alias)) {
    this._attributes.alias = alias;
  }
  return this;
};

Defines what database that the query should be performed on.

This is only necessary if your connection has not defined a database
to use, or the query needs to act upon a database other than the one
currently in use.

Usage:

query.database(dbname, [tablename], [alias])
  1. dbname string
  2. tablename string Optional
  3. alias string Optional

    An alias to use for the table

View Code
query.database = exports.intoDatabase = exports.fromDatabase = function database (dbname, tablename, alias) {
  this._attributes.database = dbname;
  if (tablename) {
    this._attributes.tableName = tablename;
  }
  if (alias) {
    this._attributes.alias = alias;
  }
  return this;
};

Defines what columns a SELECT statement should return, or what tables
a DELETE statement should delete from.

Accepts either an array of columns, or multiple column arguments.

Column names can be in any format allowed in a MySQL statement.

Calling multiple times will replace the previous columns with the new set.

By default, all queries have * for SELECTs and nothing for DELETEs

Usage:

query.columns(columns, column2)
  1. columns string, Array.<string>
  2. column2 string
var query = queryize.select()
  .columns('users.*', 'passwords.hash as password_hash')
  .from('users')
  .join('passwords', {'users.id':'passwords.user_id'})
query.columns(['username', 'firstname', 'lastname']);
query.columns(
  'users.id',
  queryize()
    .select('SUM(total_invoice)')
    .from('orders')
    .groupBy('userid')
    .as('total_invoiced')
);
View Code
query.columns = function columns (...args) {
  var self = this;

  this._attributes.columns = null;

  flatten(args).forEach((column) => {
    self.addColumn(column);
  });

  return this;
};

Adds a single column to the columns list.

Usage:

query.addColumn(column, [avoidDuplicates])
  1. column string

    Column to add.

  2. avoidDuplicates boolean Optional

    If truthy, duplicate columns will be skipped. This value is ignored for non-string columns.

View Code
query.addColumn = function addColumn (column, avoidDuplicates) {
  // if the current columns list is only a '*' wildcard, reset the array.
  // The expected use case in this scenario is that a new queryize object was made and they want to define columns individually.
  if (this._attributes.columns && this._attributes.columns.length === 1 && this._attributes.columns[0] === '*') {
    this._attributes.columns = null;
  }

  if (typeof column === 'number' || column instanceof Date) {
    avoidDuplicates = false;
    column = this.createBinding(column);
  } else if (isQueryizeObject(column)) {
    avoidDuplicates = false;
    if (column._attributes.columns.length > 1 || column._attributes.columns[0] === '*') {
      throw new Error('Column level subqueries can only return a single column.');
    }
    column = this._mergeSubquery(column);
  } else if (typeof column === 'object' && isDefined(column.data)) {
    avoidDuplicates = false;
    column = this.createBinding(column.data, column.modifier);
  }

  if (typeof column !== 'string') {
    throw new TypeError('Unknown column type: ' + JSON.stringify(column));
  }

  if (Array.isArray(this._attributes.columns)) {
    if (!avoidDuplicates || this._attributes.columns.indexOf(column) === -1) {
      this._attributes.columns.push(column);
    }
  } else {
    this._attributes.columns = [ column ];
  }
};

Controls the boolean operator used to combine multiple WHERE clauses

By default, Queryize will combine all top level WHERE clauses with AND operators.

Usage:

query.comparisonMethod(condition)
  1. condition string

    "AND" or "OR"

View Code
query.comparisonMethod = function comparisonMethod (condition) {
  if (typeof condition === 'boolean') {
    condition = condition ? 'and' : 'or';
  }

  switch (String(condition).trim()) {
  case 'and':
  case 'AND':
  case 'yes':
    this._attributes.whereBoolean = 'AND'; break;

  case 'or':
  case 'OR':
  case 'no':
    this._attributes.whereBoolean = 'OR'; break;
  // no default
  }

  return this;
};

Adds one or more WHERE clauses to the query.

Calling multiple times will append more clauses onto the stack.

Calling without any arguments will empty the stack.

If an operator is provided, it will be used for all comparisons derived from this call.

If a modifier function name is provided, the returned binding will be wrapped with that MySQL function.

Usage:

query.where()
//removes all existing where clauses
query.where()
query.where(clause)
  1. clause string

    A pre-written WHERE statement for direct insertion into the query

query.where('password IS NOT NULL')
// WHERE password IS NOT NULL
query.where(clauses)
  1. clause Array.<string>

    Multiple pre-written WHERE statements for direct insertion into the query as OR conditions unless otherwise defined.

query.where(['account.balance > 0', 'account.gratis IS TRUE'])
// WHERE account.balance > 0 OR account.gratis IS TRUE
query.where(['AND', 'client.active IS TRUE', 'client.paidthru < NOW()'])
// WHERE client.active IS TRUE AND client.paidthru < NOW()
query.where(field, value, [operator], [modifier])
  1. field string, Array.<string>

    The table field(s) to match against

  2. value string, Array.<string>

    The value(s) to match with (if more than one, performs an OR comparison of each)

  3. operator string Optional (Default: '=')

    The operator to use when performing the comparison (e.g. =, !=, >, LIKE, IS NOT, etc)

  4. modifier string Optional

    A MySQL function to wrap the binding in when it is inserted into SQL.

query.where('age', 21, '<')
// WHERE age < ?
// Data: [21]
query.where('created', new Date('2014-01-01'), '>=', 'DATE')
// WHERE created >= DATE(?)
// Data: ['2014-01-01 00:00:00']
query.where(pairs, [operator], [modifier])
  1. pairs Object

    Collection of field/value pairs to match against

  2. operator string Optional (Default: '=')

    The operator to use when performing the comparison (e.g. =, !=, >, LIKE, IS NOT, etc)

  3. modifier string Optional

    A MySQL function to wrap the binding in when it is inserted into SQL.

query.where({studio:'Paramount', franchise: 'Star Trek' });
// WHERE studio = ? AND franchise = ?
// Data: ['Paramount', 'Star Trek']
query.where({'property.ownership': ['Own', 'Rent']})
// WHERE property.ownership IN (?, ?)
// Data: ['Own', 'Rent']
query.where({'user.gender':'M, not: true, 'profile.spouse': null})
// WHERE user.gender = ? AND profile.spouse != NULL
// Data: ['M']
query.where(query)
  1. query query

    A subquery to be used as a compound where clause.

query
  .where('type', [4,5,6])
  .where(queryize()
    .whereLike('address', '%' + search + '%')
    .whereLike('city', '%' + search + '%')
  )
// WHERE type IN (?,?,?) AND (address LIKE ? OR city LIKE ?)
// Data: [4, 5, 6, '%Foo%', '%Foo%']
View Code
query.where = function where (clause, value, operator, modifier) {
  if (!isDefined(clause)) {
    this._attributes.where = [];
    return this;
  }

  var self = this;

  if (isQueryizeObject(clause)) {
    clause = this._mergeCompoundWhere(clause);
  }

  // if a value is defined, then we're performing a field > value comparison
  // and must parse that first.
  if (value !== undefined && (typeof clause === 'string' || isArray(clause))) {
    clause = this._processWhereCondition(clause, value, operator, modifier);

  // if there was no value, check to see if we got an object based where definition
  } else if (typeof clause === 'object' && !isArray(clause)) {
    modifier = operator;
    operator = value;
    clause = this._processWhereObject(clause, operator, modifier);
  }

  // if we've got an array at this point, then we should parse it as if it were
  // a collection of possible conditions in an OR
  if (isArray(clause)) {
    clause = flatten(clause).map((c) => {
      switch (typeof c) {
      case 'string': return c;
      case 'object': return self._processWhereObject(c, operator, modifier);
      default:
        throw new TypeError('Where clause could not be processed. Found ' + (typeof c) + ' instead.');
      }
    });

    var l = clause.length;
    var subBoolean = ' OR ';
    if (l === 1) {
      clause = clause[0];
    } else if (l > 1) {
      // if the first value in the array is "AND", reverse our typical boolean.
      if (clause[0] === 'AND') {
        subBoolean = ' AND ';
        clause.shift();
        l--;
      }
      if (l === 1) {
        clause = clause[0];
      } else {
        clause = '(' + clause.join(subBoolean) + ')';
      }
    } else {
      clause = null;
    }
  }

  // by now the clause should be a string. if it isn't, then someone gave us an unusable clause
  if (typeof clause === 'string') {
    this._attributes.where.push(clause);
  } else {
    throw new TypeError('Where clause could not be processed. Found ' + (typeof clause) + ' instead.');
  }

  return this;
};

Handles basic (field, value) where clauses

Usage:

query._processWhereCondition(field, value, [operator], [modifier])
  1. field string, Array.<string>
  2. value string, Array.<string>
  3. operator string Optional
  4. modifier string Optional
View Code
query._processWhereCondition = function _processWhereCondition (field, value, operator, modifier) {
  var self = this;
  if (!operator) operator = '=';

  if (isArray(field)) {
    return field.map((f) => self._processWhereCondition(f, value, operator, modifier));
  }

  // if value is an array, then we need to compare against multiple values
  if (isArray(value)) {
    // if the operator is a plain equals, we should perform an IN() instead of multiple ORs
    if (operator === '=') {
      // process the values into bindings, and join the bindings inside an IN() clause
      return field + ' IN (' + value.map((v) => self.createBinding(v, modifier)).join(',') + ')';
    } else if (operator === '!=') {
      // process the values into bindings, and join the bindings inside an IN() clause
      return field + ' NOT IN (' + value.map((v) => self.createBinding(v, modifier)).join(',') + ')';
    }
    // process each value individually as a single condition and join the values in an OR
    return value.map((v) => self._processWhereCondition(field, v, operator, modifier));

  }

  return [ field, operator, self.createBinding(value, modifier) ].join(' ');
};

Handles object based where clauses

Usage:

query._processWhereObject(clause, [operator], [modifier])
  1. clause Object
  2. operator string Optional
  3. modifier string Optional
View Code
query._processWhereObject = function _processWhereObject (clause, operator, modifier) {
  if (!operator) operator = '=';

  var self = this;
  clause = Object.keys(clause).map((field) => {
    // if the object contains a 'not' key, all subsequent keys parsed will be negations.
    if (field === 'not' && clause[field] === true) {
      if (operator === '=') operator = '!=';
      return undefined;
    }

    return self._processWhereCondition(field, clause[field], operator, modifier);
  });

  clause = flatten(clause).filter((d) => d);

  if (clause.length === 1) {
    return clause[0];
  } else if (clause.length > 1) {
    return '(' + clause.join(' AND ') + ')';
  }

  return undefined;
};

Adds a where condition for a field between two values.

Usage:

query.whereBetween(field, from, to, modifier)
  1. field string
  2. from string, number
  3. to string, number
  4. modifier string
query.whereBetween('profile.income', 18000, 60000)
// Where profile.income BETWEEN ? AND ?
// Data: [18000, 60000]
View Code
query.whereBetween = function whereBetween (field, from, to, modifier) {
  this.where([ field, 'BETWEEN', this.createBinding(from, modifier), 'AND', this.createBinding(to, modifier) ].join(' '));

  return this;
};

Shortcut for performing a LIKE comparison on a field and value

Usage:

query.whereLike(field, value, [modifier])
  1. field string, Array.<string>, Object
  2. value string, number, Array.<string|number>
  3. modifier string Optional
queryize.select()
  .from('users')
  .whereLike('email', '%gmail.com')
View Code
query.whereLike = function whereLike (field, value, modifier) {
  if (typeof field === 'object') {
    this.where(field, 'LIKE', modifier);
  } else {
    this.where(field, value, 'LIKE', modifier);
  }
  return this;
};

Shortcut for performing an != comparisoon on a field and value

Usage:

query.whereNot(field, value, [modifier])
  1. field string, Array.<string>, Object
  2. value string, number, Array.<string|number>
  3. modifier string Optional
queryize.select()
  .from('users')
  .whereNot('type', 'Admin')
View Code
query.whereNot = function whereNot (field, value, modifier) {
  if (typeof field === 'object') {
    this.where(field, '!=', modifier);
  } else {
    this.where(field, value, '!=', modifier);
  }
  return this;
};

Shortcut for performing a NOT LIKE comparison on a field and value

Usage:

query.whereNotLike(field, value, [modifier])
  1. field string, Array.<string>, Object

    [description]

  2. value string, number, Array.<string|number>

    [description]

  3. modifier string Optional

    [description]

View Code
query.whereNotLike = function whereNotLike (field, value, modifier) {
  if (typeof field === 'object') {
    this.where(field, 'NOT LIKE', modifier);
  } else {
    this.where(field, value, 'NOT LIKE', modifier);
  }
  return this;
};

Creates a where condition for if a field fit within a boundry of values.

Omitting/passing null to the from or to arguments will make the range boundless on that side.

Usage:

query.whereInRange(field, [from], [to], [modifier])
  1. field string, Array.<string>
  2. from string, number, Date Optional
  3. to string, number, Date Optional
  4. modifier string Optional
query.whereInRange('profile.income', 18000, 60000)
// Where profile.income BETWEEN ? AND ?
// Data: [18000, 60000]
query.whereInRange('age', 21)
// WHERE age >= ?
// Data: [21]
query.whereInRange('product.cost', null, 100)
// WHERE product.cost <= ?
// Data: [100]
View Code
query.whereInRange = function whereInRange (field, from, to, modifier) {
  if (isDefined(from) && isDefined(to)) {
    this.whereBetween(field, from, to, modifier);
  } else if (isDefined(from)) {
    this.where(field, from, '>=', modifier);
  } else if (isDefined(to)) {
    this.where(field, to, '<=', modifier);
  }
  return this;
};

Defines what columns a select statement should use to sort the results.
Accepts either an array of columns, or multiple column arguments.

Calling multiple times will replace the previous sort order with the new values.

Usage:

query.orderBy(columns)
  1. columns string, Array.<string>

    Column names can be in any format allowed in a MySQL statement.

query.orderBy('category', 'DATE(date_posted) DESC');
View Code
query.orderBy = function orderBy (...args) {
  this._attributes.orderBy = flatten(args);

  return this;
};

Defines what columns and conditions a select statement should group the results under.

Accepts either an array of columns, or multiple column arguments.

Calling multiple times will replace the previous grouping rules with the new values.

Usage:

query.groupBy(columns)
  1. columns string, Array.<string>

    Column names can be in any format allowed in a MySQL statement.

query.groupBy('user.id', 'DATE(dts)');
View Code
query.groupBy = function groupBy (...args) {
  this._attributes.groupBy = flatten(args);

  return this;
};

Defines if a SELECT statement should return distinct results only

Usage:

query.distinct(enable)
  1. enable boolean
View Code
query.distinct = function distinct (enable) {
  this._attributes.distinct = isDefined(enable) ? enable : true;
  return this;
};

Defines the maximum results the query should return, and the starting offset of the first row within a set.

Usage:

query.limit(max, [offset])
  1. max number

    Total results to return

  2. offset number Optional

    Starting offset of first row within the results

View Code
query.limit = function limit (max, offset) {
  if (!isDefined(max)) max = 0;
  if (!isDefined(offset)) offset = 0;

  if (max) {
    if (offset) {
      this._attributes.limit = 'LIMIT ' + Number(offset) + ', ' + Number(max);
    } else {
      this._attributes.limit = 'LIMIT ' + Number(max);
    }
  } else {
    this._attributes.limit = false;
  }

  return this;
};

Defines what data to set the specified columns to during INSERT and UPDATE queries

Any data provided via query.set is ignored in Multi-Insert Mode. See query.addRow for more details.

Usage:

query.set(statement)
  1. statement string

    A fully written set condition

query.set(column, value, [modifier])
  1. column string
  2. value string, number
query.set(data, [modifier])
  1. data Object

    A plain object collection of column/value pairs

  2. modifier string

    [description]

query.set('user.lastlogin = NOW()')
// SET user.lastlogin = NOW()
query.set('address', '9 Pseudopolis Yard')
// SET address = ?
// Data: ['9 Pseudopolis Yard']
query.set({firstname: 'Susan', lastname: 'Sto Helet'})
// SET firstname = ?, lastname = ?
// DATA: ['Susan', 'Sto Helet']
query.set('lastLogin', {raw:'NOW()'});
// SET lastLogin = NOW()
View Code
query.set = function set (clause, value, modifier) {
  var self = this;

  if (!isDefined(clause)) throw new Error('You must define a field to set.');

  if (typeof clause === 'object') {
    Object.keys(clause).forEach((field) => {
      self.set(field, clause[field], value);
    });
    return this;
  }

  // if we received a value, create a set clause
  if (typeof value === 'undefined') {
    this._attributes.set.push(clause);
  } else {
    var column = clause;
    if (value && typeof value === 'object' && typeof value.raw === 'string') {
      clause = clause + ' = ' + value.raw;
    } else {
      if (!isValidPrimative(value)) {
        throw new TypeError('Unknown data type in set clause');
      }

      clause = clause + ' = ' + this.createBinding(value, modifier);
    }

    // if we've previously created a clause for this column, overwrite it.
    var key = this._attributes.setKeys[column];
    if (isDefined(key)) {
      this._attributes.set[key] = clause;
    } else {
      this._attributes.setKeys[column] = this._attributes.set.length;
      this._attributes.set.push(clause);
    }
  }

  return this;
};

Adds a row of data for a multi-row insert. Activates Multi-Insert Mode for the query.

About Multi-Insert Mode:

Single row inserts are performed using INSERT SET queries, where each column value is defined one at a time.
Example: INSERT INTO user SET firstname=?, lastname=?

This is more efficient for query generation and less prone to mistakes, but can only insert a single row at a time. When in Multi-Insert Mode, Queryize will construct an INSERT VALUES query where all columns are defined up front and preceded by multiple data sets.

Example: INSERT INTO user (firstname, lastname) VALUES (?,?), (?,?)

This format is more performant for inserting multiple uniform data sets, but is less flexible as all the columns must be known upfront. If row contents are provided as arrays, the column order must be defined before calling query.addRow. If keyed objects are provided then Queryize will use the keys to identify the column names to use.

Any rows with missing columns will receive NULL for the column's contents.

Usage:

query.addRow(data)
  1. data Array, Object

    May be an object where the keys represent all columns, or an array that matches the number of columns already defined.

queryize
  .insert()
  .into('users')
  .columns('name', 'email')
  .addRow(['John Smith', 'jsmith@example.com'])
  .addRow(['Jane Doe', 'jdoe@example.com'])
  .compile()
// INSERT INTO users (name, email) VALUES (?, ?), (?, ?)
// DATA: ['John Smith', 'jsmith@example.com', 'Jane Doe', 'jdoe@example.com']
queryize
  .insert()
  .into('users')
  .addRow({name: 'John Smith', email: 'jsmith@example.com'})
  .addRow({name: 'Jane Doe', email: 'jdoe@example.com', date_created: {raw:'NOW()'}})
  .compile()
// INSERT INTO users (name, email, date_created) VALUES (?, ?, NULL), (?, ?, NOW())
// DATA: ['John Smith', 'jsmith@example.com', 'Jane Doe', 'jdoe@example.com']
View Code
query.addRow = function addRow (data) {
  var row, columns;
  var self = this;

  // make sure the rows property exists
  if (!this._attributes.rows) {
    this._attributes.rows = [];
  }

  if (Array.isArray(data)) {
    columns = this._attributes.columns || [];

    // if the current columns list is only a '*' wildcard, then the developer has not defined any columns yet and we can't take array data.
    if (!this._attributes.columns || this._attributes.columns.length === 1 && this._attributes.columns[0] === '*') {
      throw new Error('You must define the insert columns first in order to pass rows as array.');
    }

    if (columns.length !== data.length) {
      throw new Error('Row does not contain the same number of entries (' + data.length + ') as the number of defined columns (' + columns.length + ').');
    }

    row = {};
    columns.forEach((column, i) => {
      row[column] = data[i];
    });

    this._attributes.rows.push(row);
  } else if (typeof data === 'object') {
    columns = Object.keys(data);

    columns.forEach((column) => {
      self.addColumn(column, true);
    });

    this._attributes.rows.push(data);
  } else {
    throw new TypeError('Row is not of the expected data type.');
  }

  return this;
};

Adds a table join to the query.

Queryize will append any missing JOIN command at the beginning of a statement

Usage:

query.join(statement)
  1. statement string

    Fully formed join statement

query.join('orders o ON o.client_id = c.id')
query.join('JOIN orders o ON o.client_id = c.id')
query.join('LEFT JOIN orders o ON o.client_id = c.id')
query.join(tablename, options)
  1. tablename string
  2. options Object

    Plain object containing options for the join

query.join('orders', {alias: 'o', on: 'o.client_id = c.id'})
query.join('orders', {alias: 'o', on: {'o.client_id':'c.id'})
// JOIN orders o ON o.client_id = c.id
query.join('orders', {on: {'o.client_id':'c.id', not:true, o.status: [4,5]})
// JOIN orders ON o.client_id = c.id AND o.status NOT IN (?,?)
// Data: [4, 5]
query.join('orders', {on: {'o.client_id':'c.id', not:true, o.condition: {data: 'A'}})
// JOIN orders ON o.client_id = c.id AND o.status != ?
// Data: ['A']
query.join('orders', {alias: 'o', type: 'LEFT', on: 'o.client_id = c.id'})
// LEFT JOIN orders o ON o.client_id = c.id
query.join(options)
  1. options Object

    Plain object containing options for the join

query.join({table: 'orders', alias: 'o', on: {'o.client_id':'c.id'})
query.join({table: 'orders', alias: 'o', type: 'LEFT' on: {'o.client_id':'c.id'})
// LEFT JOIN orders o ON o.client_id = c.id
query.join('orders', {on: {'o.client_id':'c.id', not:true, o.condition: {data: 'A'}})
// JOIN orders ON o.client_id = c.id AND o.status != ?
// Data: ['A']
query.join(subquery, options)
  1. subquery query

    A query object to use as a subquery

  2. options Object

    Plain object containing options for the join

queryize()
  .select('AVG(total_per_user)')
  .from(queryize()
    .select('SUM(total_invoice) AS total_per_user')
    .from('orders')
    .groupBy('userid')
    .as('order_totals');
View Code
query.join = function join (clause, options) {
  if (!isDefined(clause)) throw new Error('You must define a table to join against.');

  if (isQueryizeObject(clause)) {
    if (typeof options !== 'object') throw new Error('You must define join options when joining against a queryize subquery.');

    options = Object.create(options);
    options.table = clause;
    clause = options;
  } else if (typeof clause === 'object') {
    if (isArray(clause)) throw new TypeError('Join clauses can only be strings or object definitions');
    if (!clause.table) throw new Error('You must define a table to join against');
  } else if (typeof options === 'object') {
    options = Object.create(options);
    options.table = clause;
    clause = options;
  } else if (typeof clause === 'string' && clause.search(joinTest) < 0) {
    clause = 'JOIN ' + clause;
  }

  if (typeof clause === 'object') {
    var stack = [];

    if (clause.type) stack.push(clause.type);
    stack.push('JOIN');
    if (isQueryizeObject(clause.table)) {
      if (clause.alias) {
        clause.table = clause.table.clone().as(clause.alias);
      }
      stack.push(this._mergeSubquery(clause.table));
    } else if (typeof clause.table === 'string') {
      stack.push(clause.table);
      if (clause.alias) stack.push(clause.alias);
    } else {
      throw new TypeError('The join table name is not a string.');
    }

    if (clause.using) {
      stack.push('USING (' + (isArray(clause.using) ? clause.using.join(', ') : clause.using) + ')');
    } else if (clause.on) {
      stack.push('ON (' + this._processJoinOns(clause.on, clause.onBoolean || 'AND') + ')');
    }

    clause = stack.join(' ');
  }

  this._attributes.joins.push(clause);

  return this;
};

Generates the closures for innerJoin, leftJoin and rightJoin

Usage:

_makeJoiner(type)
  1. type string

    Join type

View Code
function _makeJoiner (type) {
  return function (clause, options) {
    if (isQueryizeObject(clause)) {
      if (typeof options !== 'object') throw new Error('You must define join options when joining against a queryize subquery.');

      options = Object.create(options);
      options.table = clause;
      clause = options;
    } else if (typeof clause === 'object') {
      if (isArray(clause)) throw new TypeError('Join clauses can only be strings or object definitions');
      if (!clause.table) throw new Error('You must define a table to join against');
    } else if (typeof options === 'object') {
      options = Object.create(options);
      options.table = clause;
      clause = options;
    }

    if (typeof clause === 'object') {
      clause.type = type;
    } else {
      clause = clause.search(joinTest) > -1 ? clause.replace(joinTest, type + ' JOIN ') : type + ' JOIN ' + clause;
    }

    this.join(clause);

    return this;
  };
}

Shortcut for creating an INNER JOIN

See query.join() for argument details

Usage:

query.innerJoin()
View Code
query.innerJoin = _makeJoiner('INNER');

Shortcut for creating a LEFT JOIN

See query.join() for argument details

Usage:

query.leftJoin()
View Code
query.leftJoin = _makeJoiner('LEFT');

Shortcut for creating a RIGHT JOIN

See query.join() for argument details

Usage:

query.rightJoin()
View Code
query.rightJoin = _makeJoiner('RIGHT');

Processes join conditions into a standardized format that's uniformly parsable

Usage:

query._processJoinOns()
View Code
query._processJoinOns = function _processJoinOns (ons, onBoolean) {
  if (typeof ons === 'string') {
    return ons;
  }

  var self = this;

  if (isArray(ons)) {
    ons = ons.map((d) => self._processJoinOns(d));
  }

  if (typeof ons === 'object' && !isArray(ons)) {
    var not = false;
    ons = Object.keys(ons).map((field) => {
      var value = ons[field];

      // if the object contains a 'not' key, all subsequent keys parsed will be negations.
      if (field === 'not' && value === true) {
        not = true;
        return undefined;
      }

      // if value is an array, perform an IN() on the values
      if (isArray(value)) {
        value = value.map((d) => self.createBinding(d));
        return [ field, not ? 'NOT IN' : 'IN', '(', value.join(', '), ')' ].join(' ');

      // if value is a string or a number, process as if a normal pairing of columns
      } else if (typeof value === 'string') {
        return [ field, not ? '!=' : '=', value ].join(' ');

      // finally, process the value as if it were an actual value for binding
      } else if (isValidPrimative(value)) {
        return [ field, not ? '!=' : '=', self.createBinding(value) ].join(' ');

      // if value is an object, verify if it's a data object, and if so create a binding for the value
      } else if (typeof value === 'object' && value.data !== undefined) {
        return [ field, not ? '!=' : '=', self.createBinding(value.data, value.modifier) ].join(' ');
      }

      // we don't know how to handle the value
      throw new Error('Encountered unexpected value while parsing a JOIN ON condition for ' + field);
    });
  }

  // remove any undefined or empty string values
  ons = ons.filter((d) => d);

  return ons.join(' ' + onBoolean + ' ');
};

Defines the name to be used to identify the results of a subquery

Usage:

query.as(name)
  1. name string
View Code
query.as = function asNamed (name) {
  this._attributes.asName = name;
  return this;
};

Compiles this query for use as a subquery in another query object.

Usage:

query._buildSubquery()
View Code
query._buildSubquery = function _buildSubquery () {
  if (!this._attributes.tableName && !this._attributes.fromSubquery) throw new Error('No table name has been defined for a sub-query.');

  // I couldn't find a single instance of a non-select subquery, so I'm just assuming it here
  var queryString = builders.select.call(this);
  var queryData = this._collectNamedParameters(queryString);

  queryString = '(' + queryString + ') as `' + (this._attributes.asName || _uniqueId('subquery')) + '`';

  return {
    query: queryString,
    data: queryData,
  };
};

Compiles this query for use as a where clause in another query.

Usage:

query._buildCompoundWhere()
View Code
query._buildCompoundWhere = function _buildCompoundWhere () {
  var wheres = this._attributes.where.concat();

  // if the whereBoolean is defined and contains AND, the compound condition was explicitly defined as an AND.
  if (this._attributes.whereBoolean === 'AND') {
    // an array of wheres is normally processed as an OR condition.
    // prefixing it with 'AND' changes it to an AND condition
    wheres.unshift('AND');
  }

  return {
    where: wheres,
    data: this._collectNamedParameters(wheres.join(' ')),
  };
};

Extracts an external query object as a subquery and merges the bound data values into this query

Usage:

query._mergeSubquery(query)
  1. query query
View Code
query._mergeSubquery = function _mergeSubquery (subquery) {
  if (!isQueryizeObject(subquery)) throw new TypeError('Provided subquery is not a queryize object.');

  var q = subquery._buildSubquery();

  Object.assign(this._attributes.dataBindings, q.data);

  return q.query;
};

Extracts the where clauses of an external query object and merges the bound data values into this query

Usage:

query._mergeCompoundWhere(query)
  1. query query
View Code
query._mergeCompoundWhere = function _mergeCompoundWhere (subquery) {
  if (!isQueryizeObject(subquery)) throw new TypeError('Provided compound where is not a queryize object.');

  var q = subquery._buildCompoundWhere();

  Object.assign(this._attributes.dataBindings, q.data);

  return q.where;
};

Causes queryize to insert escaped data directly into the query instead of using data bound placeholders.
This is not recommended and should only be used for debugging purposes.

You can also set queryize.useBoundParameters = false to disable databinding for all queries

Usage:

query.disableBoundParameters(enable)
  1. enable boolean
View Code
query.disableBoundParameters = function disableBoundParameters (bool) {
  this._attributes.useBoundParameters = isDefined(bool) ? !bool : false;
  return this;
};
This method is deprecated: Use disableBoundParameters instead.

Usage:

query.useBoundParameters(bool)
  1. bool Boolean
View Code
query.useBoundParameters = function (bool) {
  this.disableBoundParameters(isDefined(bool) ? !bool : true);
  return this;
};

Constructs the table name to use in queries, with backticks

Usage:

query._buildTableName()
View Code
query._buildTableName = function _buildTableName () {
  if (this._attributes.fromSubquery) {
    return this._attributes.fromSubquery;
  }

  var q = [];

  if (this._attributes.database) {
    q.push('`' + this._attributes.database + '`.');
  }

  q.push('`' + this._attributes.tableName + '`');

  if (this._attributes.alias) {
    q.push(' ' + this._attributes.alias);
  }

  return q.join('');
};

Parses the comparisonMethod value and returns a string suitable as a delimiter.

Usage:

query._buildWhereBoolean()
View Code
query._buildWhereBoolean = function _buildWhereBoolean () {
  return ' ' + (this._attributes.whereBoolean && this._attributes.whereBoolean.trim() || 'AND') + ' ';
};

query._builders = builders;

Compiles the final MySQL query

Usage:

query.compile()
View Code
query.compile = function compile (useBoundParameters) {
  if (!this._attributes.builder || !builders[this._attributes.builder]) throw new Error('Query operation undefined, must identify if performing a select/update/insert/delete query.');
  if (!this._attributes.tableName && !this._attributes.fromSubquery) throw new Error('No table name has been defined');

  if (this._attributes.builder === 'insert' && this._attributes.rows) {
    this._attributes.builder = 'insertMultiple';
  }

  if (typeof useBoundParameters === 'undefined') useBoundParameters = this._attributes.useBoundParameters;

  var q = builders[this._attributes.builder].call(this);

  if (typeof q === 'string') {
    return this._convertNamedParameters(q, useBoundParameters);
  }
  if (typeof q !== 'object' || !q.query) throw new Error('INTERNAL ERROR: Query builder returned unrecognized output.');
  return q;

};

Processes the freshly compiled query string, replacing all data bindings with placeholders and constructs the data array
If useBoundParameters is turned off, it replaces the placeholders with their escaped values.

Usage:

query._convertNamedParameters(queryString)
  1. queryString string

    The query string to be processed

View Code
query._convertNamedParameters = function _convertNamedParameters (queryString, useBoundParameters) {
  var data = [];
  var self = this;

  queryString = queryString.replace(/({{\w*}})/g, (match, name) => {
    if (self._attributes.dataBindings[name] === undefined) throw new Error('The data binding ' + name + ' could not be found.');

    data.push(self._attributes.dataBindings[name]);

    return '?';
  });

  if (!useBoundParameters) {
    queryString = SQLString.format(queryString, data);
    data = [];
  }

Processes the freshly compiled query string, finding all data bindings placeholders and collecting the values for those bindings.

Usage:

query._collectNamedParameters(queryString)
  1. queryString string

    The query string to be processed

View Code
query._collectNamedParameters = function _collectNamedParameters (queryString) {
  var data = {};
  var self = this;

  (queryString.match(/({{\w*}})/g) || []).forEach((name) => {
    // Subqueries might reference data bound to the parent query, so it's ok if a value doesn't exist
    if (self._attributes.dataBindings[name] !== undefined) {
      data[name] = self._attributes.dataBindings[name];
    }
  });

  return data;
};

Compiles the MySQL query and executes it using queryize.evalFunction, returning a promise.
If no eval function has been defined, the promise resolves with a tuple of the query string and data array.

Usage:

query.eval(args)
  1. args mixed

    Any options to be passed to the evalFunction

// pool is a mysql2/promise connection pool
queryize.evalFunction = (q, d, options) => pool.query(q, d, options);
const query = queryize()
  .select()
  .from('employees')
  .orderBy('lastname', 'firstname');
query.eval(options).then((result) => console.log(result))
// using with async/await
queryize.evalFunction = (q, d, options) => pool.query(q, d, options);
const result = await queryize()
  .select()
  .from('employees')
  .orderBy('lastname', 'firstname')
  .eval(options);
View Code
query.eval = async function evaluate (...args) {
  const { query: q, data } = this.compile();
  if (this._attributes.debugEnabled) console.log({ query: q, data }); // eslint-disable-line no-console

  if (typeof this._evalFunction !== 'function') return [ q, data ];
  return await this._evalFunction(q, data, ...args);
};

Compiles the MySQL query and executes it using queryize.evalFunction, returning a promise.
If no eval function has been defined, the promise resolves with a tuple of the query string and data array.

Usage:

query.then(resolved, rejected)
  1. resolved function

    Function to execute when the evaluation completes

  2. rejected function

    Function to execute if the evaluation fails

// pool is a mysql2/promise connection pool
queryize.evalFunction = (q, d) => pool.query(q, d);
const query = queryize()
  .select()
  .from('employees')
  .orderBy('lastname', 'firstname');
query.then((result) => console.log(result))
// using with async/await
queryize.evalFunction = (q, d) => pool.query(q, d);
const result = await queryize()
  .select()
  .from('employees')
  .orderBy('lastname', 'firstname');
View Code
query.then = function then (...args) {
  const p = this.eval();
  if (args.length) return p.then(...args);
  return p;
};

Copies the attribute data for the query object and returns the copy

Usage:

query.export()
View Code
query.export = function exportAttributes () {
  return clonedeep(this._attributes, true);
};

Generates a duplicate of the current query

Usage:

query.clone()
var findCompleted = queryize()
  .from('orders')
  .where('status', 'completed')
  .select();

var archiveCompleted = a.clone()
  .update()
  .set('status', 'archived');
View Code
query.clone = function clone () {
  return this._constructor(this);
};

Compiles the query without bound parameters, escaping data in-line with the query.

Usage:

query.toString()
var q = queryize()
  .select()
  .from('employees')
  .where({ type: 'fulltime'});
 console.log('' + q); // SELECT * FROM `employees` WHERE (type = "fulltime") ORDER BY lastname, firstname
View Code
query.toString = function toString () {
  return this.compile(false).query;
};

creates a string that is unique to this runtime session

Usage:

_uniqueId([prefix])
  1. prefix string Optional
View Code
function _uniqueId (prefix) {
  var id = (++idCounter).toString(16);
  return prefix ? prefix + id : id;
}

Test if a value is defined and not null

Usage:

isDefined(value)
  1. value mixed
View Code
function isDefined (value) {
  return value !== undefined && value !== null;
}

Tests if a value is a valid type for storing in mysql

Usage:

isValidPrimative(value)
  1. value mixed
View Code
function isValidPrimative (value) {
  return typeof value === 'string' ||
    typeof value === 'boolean' ||
    typeof value === 'number' ||
    value === null ||
    (value instanceof Date);
}

Tests if the provided value is a queryize query object

Usage:

isQueryizeObject(value)
  1. value mixed
View Code
function isQueryizeObject (value) {
  return typeof value === 'object' && value._isQueryizeObject;
}

Change Log

3.0.0 / 2019-07-08

2.1.0 / 2017-12-07

2.0.0 / 2016-10-28

1.1.1 / 2015-10-10

1.1.0 / 2015-02-06

1.0.2 / 2015-02-06

1.0.0 / 2014-12-16

0.6.0 / 2014-09-12

0.5.1 / 2014-09-13

0.5.0 / 2014-09-12

0.4.0 / 2014-09-03

0.3.0 / 2014-09-02

0.2.0 / 2014-08-22

0.1.3 / 2014-02-17

0.1.2 / 2014-02-17

0.1.1 / 2014-02-03

0.1.1 / 2014-01-14