sequel-builder
Advanced tools
Comparing version 0.0.2 to 0.0.3
@@ -18,2 +18,6 @@ 'use strict'; | ||
var _strtr = require('phpjs/strtr'); | ||
var _strtr2 = _interopRequireDefault(_strtr); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -27,5 +31,15 @@ | ||
/** | ||
* Connection the database connection. | ||
*/ | ||
this._db = db; | ||
/** | ||
* The separator between different fragments of a SQL statement. | ||
* Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. | ||
*/ | ||
this._separator = " "; | ||
this._typeMap = []; | ||
/** | ||
* Array map of query condition to builder methods. | ||
* These methods are used by [[buildCondition]] to build SQL conditions from array syntax. | ||
*/ | ||
this._conditionBuilders = { | ||
@@ -48,2 +62,13 @@ 'NOT': 'buildNotCondition', | ||
/** | ||
* Generates a SELECT SQL statement from a [[Query]] object. | ||
* @param {Query} query the [[Query]] object from which the SQL statement will be generated. | ||
* @param {Object} params the parameters to be bound to the generated SQL statement. These parameters will | ||
* be included in the result with the additional parameters generated during the query building process. | ||
* @return {Array} the generated SQL statement (the first array element) and the corresponding | ||
* parameters to be bound to the SQL statement (the second array element). The parameters returned | ||
* include those provided in `$params`. | ||
*/ | ||
_createClass(QueryBuilder, [{ | ||
@@ -73,2 +98,11 @@ key: 'build', | ||
} | ||
/** | ||
* @param {Array|Object} columns | ||
* @param {Object} params the binding parameters to be populated | ||
* @param {boolean} distinct | ||
* @param {string} selectOption | ||
* @return {string} the SELECT clause built from [[Query::select]]. | ||
*/ | ||
}, { | ||
@@ -119,2 +153,9 @@ key: 'buildSelect', | ||
} | ||
/** | ||
* @param {Array|Object} tables | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the FROM clause built from [[Query::from]]. | ||
*/ | ||
}, { | ||
@@ -133,2 +174,10 @@ key: 'buildFrom', | ||
} | ||
/** | ||
* @param {Array} joins | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the JOIN clause built from [[Query::join]]. | ||
* @throws Exception if the joins parameter is not in proper format | ||
*/ | ||
}, { | ||
@@ -152,3 +201,3 @@ key: 'buildJoin', | ||
var tables = _this2._quoteTableNames(table, params); | ||
//table = reset($tables); @todo figure out conversion. | ||
table = tables.shift(); //@todo figure out conversion. | ||
joins[i] = joinType + ' ' + table; | ||
@@ -165,2 +214,11 @@ if (join[2] !== undefined) { | ||
} | ||
/** | ||
* Quotes table names passed | ||
* | ||
* @param {Array|Object} tables | ||
* @param {Object} params | ||
* @return {Array|Object} | ||
*/ | ||
}, { | ||
@@ -201,2 +259,9 @@ key: '_quoteTableNames', | ||
} | ||
/** | ||
* @param {String|Array} condition | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the WHERE clause built from [[Query::where]]. | ||
*/ | ||
}, { | ||
@@ -208,2 +273,8 @@ key: 'buildWhere', | ||
} | ||
/** | ||
* @param {Array|Object} columns | ||
* @return {String} the GROUP BY clause | ||
*/ | ||
}, { | ||
@@ -214,2 +285,9 @@ key: 'buildGroupBy', | ||
} | ||
/** | ||
* @param {String|Array|Object} condition | ||
* @param {Object} params the binding parameters to be populated | ||
* @return string the HAVING clause built from [[Query::having]]. | ||
*/ | ||
}, { | ||
@@ -221,3 +299,163 @@ key: 'buildHaving', | ||
} | ||
/** | ||
* Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL. | ||
* @param {String} sql the existing SQL (without ORDER BY/LIMIT/OFFSET) | ||
* @param {Array|Object} orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter. | ||
* @param {int} limit the limit number. See [[Query::limit]] for more details. | ||
* @param {int} offset the offset number. See [[Query::offset]] for more details. | ||
* @return {String} the SQL completed with ORDER BY/LIMIT/OFFSET (if any) | ||
*/ | ||
}, { | ||
key: 'buildOrderByAndLimit', | ||
value: function buildOrderByAndLimit(sql, orderBy, limit, offset) { | ||
orderBy = this.buildOrderBy(orderBy); | ||
if (orderBy !== '') { | ||
sql += this._separator + orderBy; | ||
} | ||
limit = this.buildLimit(limit, offset); | ||
if (limit !== '') { | ||
sql += this._separator + limit; | ||
} | ||
return sql; | ||
} | ||
/** | ||
* @param {Array|Object} columns | ||
* @return {String} the ORDER BY clause built from [[Query::$orderBy]]. | ||
*/ | ||
}, { | ||
key: 'buildOrderBy', | ||
value: function buildOrderBy(columns) { | ||
var _this4 = this; | ||
if (_lodash2.default.isEmpty(columns)) { | ||
return ''; | ||
} | ||
var orders = []; | ||
Object.keys(columns).forEach(function (name) { | ||
orders.push(_this4._db.quoteColumnName(name) + (columns[name] === _constants.SORT_DESC ? ' DESC' : '')); | ||
}); | ||
return 'ORDER BY ' + orders.join(', '); | ||
} | ||
/** | ||
* @param {int} limit | ||
* @param {int} offset | ||
* @return {String} the LIMIT and OFFSET clauses | ||
*/ | ||
}, { | ||
key: 'buildLimit', | ||
value: function buildLimit(limit, offset) { | ||
var sql = ''; | ||
if (this.hasLimit(limit)) { | ||
sql = 'LIMIT ' + limit; | ||
} | ||
if (this.hasOffset(offset)) { | ||
sql += ' OFFSET ' + offset; | ||
} | ||
return _lodash2.default.trimStart(sql); | ||
} | ||
/** | ||
* Checks to see if the given limit is effective. | ||
* @param {String} limit the given limit | ||
* @return {boolean} whether the limit is effective | ||
*/ | ||
}, { | ||
key: 'hasLimit', | ||
value: function hasLimit(limit) { | ||
return (/^\d+$/.test(limit) | ||
); | ||
} | ||
/** | ||
* Checks to see if the given offset is effective. | ||
* @param {String} offset the given offset | ||
* @return {boolean} whether the offset is effective | ||
*/ | ||
}, { | ||
key: 'hasOffset', | ||
value: function hasOffset(offset) { | ||
return (/^\d+$/.test(offset) && offset != 0 | ||
); | ||
} | ||
/** | ||
* @param {Array|Object} unions | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the UNION clause built from [[Query::union]]. | ||
*/ | ||
}, { | ||
key: 'buildUnion', | ||
value: function buildUnion(unions, params) { | ||
var _this5 = this; | ||
if (_lodash2.default.isEmpty(unions)) { | ||
return ''; | ||
} | ||
var result = ''; | ||
unions.forEach(function (union, i) { | ||
var query = union['query']; | ||
if (query instanceof _query.Query) { | ||
var data = _this5.build(query, params); | ||
unions[i]['query'] = data[0]; | ||
params = data[1]; | ||
} | ||
result += 'UNION ' + (union['all'] ? 'ALL ' : '') + '( ' + unions[i]['query'] + ' ) '; | ||
}); | ||
return result.trim(); | ||
} | ||
/** | ||
* Processes columns and properly quotes them if necessary. | ||
* It will join all columns into a string with comma as separators. | ||
* @param {String|Array|Object} columns the columns to be processed | ||
* @return {String} the processing result | ||
*/ | ||
}, { | ||
key: 'buildColumns', | ||
value: function buildColumns(columns) { | ||
var _this6 = this; | ||
if (!_lodash2.default.isArray(columns)) { | ||
if (columns.indexOf(columns, '(') !== -1) { | ||
return columns; | ||
} else { | ||
columns = columns.split('/\s*,\s*/').filter(function (value) { | ||
return value.length > 0; | ||
}); | ||
} | ||
} | ||
columns.forEach(function (column, i) { | ||
if (column.indexOf('(') === -1) { | ||
columns[i] = _this6._db.quoteColumnName(column); | ||
} | ||
}); | ||
return _lodash2.default.isArray(columns) ? columns.join(', ') : columns; | ||
} | ||
/** | ||
* Parses the condition specification and generates the corresponding SQL expression. | ||
* @param {String|Array|Object} condition the condition specification. Please refer to [[Query::where()]] | ||
* on how to specify a condition. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
*/ | ||
}, { | ||
key: 'buildCondition', | ||
@@ -252,28 +490,14 @@ value: function buildCondition(condition, params) { | ||
} | ||
}, { | ||
key: 'buildColumns', | ||
value: function buildColumns(columns) { | ||
var _this4 = this; | ||
if (!_lodash2.default.isArray(columns)) { | ||
if (columns.indexOf(columns, '(') !== -1) { | ||
return columns; | ||
} else { | ||
columns = columns.split('/\s*,\s*/').filter(function (value) { | ||
return value.length > 0; | ||
}); | ||
} | ||
} | ||
columns.forEach(function (column, i) { | ||
if (column.indexOf('(') === -1) { | ||
columns[i] = _this4._db.quoteColumnName(column); | ||
} | ||
}); | ||
/** | ||
* Creates a condition based on column-value pairs. | ||
* @param {Array|Object} condition the condition specification. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
*/ | ||
return _lodash2.default.isArray(columns) ? columns.join(', ') : columns; | ||
} | ||
}, { | ||
key: 'buildHashCondition', | ||
value: function buildHashCondition(condition, params) { | ||
var _this5 = this; | ||
var _this7 = this; | ||
@@ -285,6 +509,6 @@ var parts = []; | ||
// IN condition | ||
parts.push(_this5.buildInCondition('IN', [column, value], params)); | ||
parts.push(_this7.buildInCondition('IN', [column, value], params)); | ||
} else { | ||
if (column.indexOf('(') === -1) { | ||
column = _this5._db.quoteColumnName(column); | ||
column = _this7._db.quoteColumnName(column); | ||
} | ||
@@ -303,6 +527,15 @@ if (value === null) { | ||
} | ||
/** | ||
* Connects two or more SQL expressions with the `AND` or `OR` operator. | ||
* @param {String} operator the operator to use for connecting the given operands | ||
* @param {Array} operands the SQL expressions to connect. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
*/ | ||
}, { | ||
key: 'buildAndCondition', | ||
value: function buildAndCondition(operator, operands, params) { | ||
var _this6 = this; | ||
var _this8 = this; | ||
@@ -312,3 +545,3 @@ var parts = []; | ||
if (_lodash2.default.isObject(operand)) { | ||
operand = _this6.buildCondition(operand, params); | ||
operand = _this8.buildCondition(operand, params); | ||
} | ||
@@ -325,2 +558,12 @@ if (operand !== '') { | ||
} | ||
/** | ||
* Inverts an SQL expressions with `NOT` operator. | ||
* @param {String} operator the operator to use for connecting the given operands | ||
* @param {Array} operands the SQL expressions to connect. | ||
* @param {Array} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws Error if wrong number of operands have been given. | ||
*/ | ||
}, { | ||
@@ -343,2 +586,13 @@ key: 'buildNotCondition', | ||
} | ||
/** | ||
* Creates an SQL expressions with the `BETWEEN` operator. | ||
* @param {String} operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`) | ||
* @param {Array} operands the first operand is the column name. The second and third operands | ||
* describe the interval that column value should be in. | ||
* @param {Object|Array} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws InvalidParamException if wrong number of operands have been given. | ||
*/ | ||
}, { | ||
@@ -369,2 +623,16 @@ key: 'buildBetweenCondition', | ||
} | ||
/** | ||
* Creates an SQL expressions with the `IN` operator. | ||
* @param {String} operator the operator to use (e.g. `IN` or `NOT IN`) | ||
* @param {Array} operands the first operand is the column name. If it is an array | ||
* a composite IN condition will be generated. | ||
* The second operand is an array of values that column value should be among. | ||
* If it is an empty array the generated expression will be a `false` value if | ||
* operator is `IN` and empty if operator is `NOT IN`. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws Error if wrong number of operands have been given. | ||
*/ | ||
}, { | ||
@@ -420,6 +688,17 @@ key: 'buildInCondition', | ||
} | ||
/** | ||
* Builds SQL for IN condition | ||
* | ||
* @param {String} operator | ||
* @param {Array} columns | ||
* @param {Query} values | ||
* @param {Array} params | ||
* @return {String} SQL | ||
*/ | ||
}, { | ||
key: 'buildSubqueryInCondition', | ||
value: function buildSubqueryInCondition(operator, columns, values, params) { | ||
var _this7 = this; | ||
var _this9 = this; | ||
@@ -433,3 +712,3 @@ var _build3 = this.build(values, params); | ||
if (col.indexOf(col, '(') === -1) { | ||
columns[i] = _this7._db.quoteColumnName(col); | ||
columns[i] = _this9._db.quoteColumnName(col); | ||
} | ||
@@ -445,6 +724,17 @@ }); | ||
} | ||
/** | ||
* Builds SQL for IN condition | ||
* | ||
* @param {String} operator | ||
* @param {Array} columns | ||
* @param {Query} values | ||
* @param {Array} params | ||
* @return {String} SQL | ||
*/ | ||
}, { | ||
key: 'buildCompositeInCondition', | ||
value: function buildCompositeInCondition(operator, columns, values, params) { | ||
var _this8 = this; | ||
var _this10 = this; | ||
@@ -468,3 +758,3 @@ var vss = []; | ||
if (column.indexOf('(') === -1) { | ||
columns[i] = _this8._db.quoteColumnName(column); | ||
columns[i] = _this10._db.quoteColumnName(column); | ||
} | ||
@@ -475,80 +765,102 @@ }); | ||
} | ||
/** | ||
* Creates an SQL expressions with the `LIKE` operator. | ||
* @param {String} operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) | ||
* @param {Array} operands an array of two or three operands | ||
* | ||
* - The first operand is the column name. | ||
* - The second operand is a single value or an array of values that column value | ||
* should be compared with. If it is an empty array the generated expression will | ||
* be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator | ||
* is `NOT LIKE` or `OR NOT LIKE`. | ||
* - An optional third operand can also be provided to specify how to escape special characters | ||
* in the value(s). The operand should be an array of mappings from the special characters to their | ||
* escaped counterparts. If this operand is not provided, a default escape mapping will be used. | ||
* You may use `false` or an empty array to indicate the values are already escaped and no escape | ||
* should be applied. Note that when using an escape mapping (or the third operand is not provided), | ||
* the values will be automatically enclosed within a pair of percentage characters. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws Error if wrong number of operands have been given. | ||
*/ | ||
}, { | ||
key: 'buildOrderByAndLimit', | ||
value: function buildOrderByAndLimit(sql, orderBy, limit, offset) { | ||
orderBy = this.buildOrderBy(orderBy); | ||
if (orderBy !== '') { | ||
sql += this._separator + orderBy; | ||
key: 'buildLikeCondition', | ||
value: function buildLikeCondition(operator, operands, params) { | ||
if (operands[0] === undefined || operands[1] === undefined) { | ||
throw new Error('Operator \'' + operator + '\' requires two operands.'); | ||
} | ||
limit = this.buildLimit(limit, offset); | ||
if (limit !== '') { | ||
sql += this._separator + limit; | ||
var escape = operands[2] !== undefined ? operands[2] : { '%': '\%', '_': '\_', '\\': '\\\\' }; | ||
delete operands[2]; | ||
var matches = operator.match(/^(AND |OR |)(((NOT |))I?LIKE)/); | ||
if (!matches) { | ||
throw new Error('Invalid operator \'' + operator + '\'.'); | ||
} | ||
return sql; | ||
} | ||
}, { | ||
key: 'buildUnion', | ||
value: function buildUnion(unions, params) { | ||
var _this9 = this; | ||
var andor = ' ' + (!_lodash2.default.isEmpty(matches[1]) ? matches[1] : 'AND '); | ||
var not = !_lodash2.default.isEmpty(matches[3]); | ||
operator = matches[2]; | ||
if (_lodash2.default.isEmpty(unions)) { | ||
return ''; | ||
var column = operands[0]; | ||
var values = operands[1]; | ||
if (!_lodash2.default.isArray(values)) { | ||
values = [values]; | ||
} | ||
var result = ''; | ||
if (_lodash2.default.isEmpty(values)) { | ||
return not ? '' : '0=1'; | ||
} | ||
unions.forEach(function (union, i) { | ||
var query = union['query']; | ||
if (query instanceof _query.Query) { | ||
var data = _this9.build(query, params); | ||
unions[i]['query'] = data[0]; | ||
params = data[1]; | ||
} | ||
if (column.indexOf('(') === -1) { | ||
column = this._db.quoteColumnName(column); | ||
} | ||
result += 'UNION ' + (union['all'] ? 'ALL ' : '') + '( ' + unions[i]['query'] + ' ) '; | ||
var parts = []; | ||
values.forEach(function (value) { | ||
var index = _lodash2.default.isArray(params) ? params.length : Object.keys(params).length; | ||
var phName = '' + QueryBuilder.PARAM_PREFIX + index; | ||
params[phName] = _lodash2.default.isEmpty(escape) ? value : '%' + (0, _strtr2.default)(value, escape) + '%'; | ||
parts.push(column + ' ' + operator + ' ' + phName); | ||
}); | ||
return trim(result); | ||
return parts.join(andor); | ||
} | ||
/** | ||
* Creates an SQL expressions with the `EXISTS` operator. | ||
* @param {String} operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`) | ||
* @param {Array} operands contains only one element which is a [[Query]] object representing the sub-query. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws Error if the operand is not a [[Query]] object. | ||
*/ | ||
}, { | ||
key: 'buildOrderBy', | ||
value: function buildOrderBy(columns) { | ||
var _this10 = this; | ||
key: 'buildExistsCondition', | ||
value: function buildExistsCondition(operator, operands, params) { | ||
if (operands[0] instanceof _query.Query) { | ||
var _build4 = this.build(operands[0], params); | ||
if (_lodash2.default.isEmpty(columns)) { | ||
return ''; | ||
var sql = _build4[0]; | ||
return operator + ' (' + sql + ')'; | ||
} else { | ||
throw new Error('Subquery for EXISTS operator must be a Query object.'); | ||
} | ||
var orders = []; | ||
Object.keys(columns).forEach(function (name) { | ||
orders.push(_this10._db.quoteColumnName(name) + (columns[name] === _constants.SORT_DESC ? ' DESC' : '')); | ||
}); | ||
return 'ORDER BY ' + orders.join(', '); | ||
} | ||
}, { | ||
key: 'buildLimit', | ||
value: function buildLimit(limit, offset) { | ||
var sql = ''; | ||
if (this.hasLimit(limit)) { | ||
sql = 'LIMIT ' + limit; | ||
} | ||
if (this.hasOffset(offset)) { | ||
sql += ' OFFSET ' + offset; | ||
} | ||
return _lodash2.default.trimStart(sql); | ||
} | ||
/** | ||
* Creates an SQL expressions like `"column" operator value`. | ||
* @param {String} operator the operator to use. Anything could be used e.g. `>`, `<=`, etc. | ||
* @param {Array} operands contains two column names. | ||
* @param {Array} params the binding parameters to be populated | ||
* @return string the generated SQL expression | ||
* @throws InvalidParamException if wrong number of operands have been given. | ||
*/ | ||
}, { | ||
key: 'hasLimit', | ||
value: function hasLimit(limit) { | ||
return (/^\d+$/.test(limit) | ||
); | ||
} | ||
}, { | ||
key: 'hasOffset', | ||
value: function hasOffset(offset) { | ||
return (/^\d+$/.test(offset) && offset != 0 | ||
); | ||
} | ||
}, { | ||
key: 'buildSimpleCondition', | ||
@@ -571,5 +883,5 @@ value: function buildSimpleCondition(operator, operands, params) { | ||
} else if (value instanceof _query.Query) { | ||
var _build4 = this.build(value, params); | ||
var _build5 = this.build(value, params); | ||
var sql = _build4.sql; | ||
var sql = _build5.sql; | ||
@@ -576,0 +888,0 @@ return column + ' ' + operator + ' (' + sql + ')'; |
{ | ||
"name": "sequel-builder", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "Ported query library from php yii2 to nodejs. currently only supporting mysql.", | ||
@@ -14,3 +14,4 @@ "main": "index.js", | ||
"lodash": "^4.7.0", | ||
"mysql": "^2.10.2" | ||
"mysql": "^2.10.2", | ||
"phpjs": "^1.3.2" | ||
}, | ||
@@ -17,0 +18,0 @@ "devDependencies": { |
import _ from 'lodash'; | ||
import {Query} from './query.class'; | ||
import {SORT_DESC} from '../constants'; | ||
import strtr from 'phpjs/strtr'; | ||
@@ -11,5 +12,15 @@ export class QueryBuilder { | ||
/** | ||
* Connection the database connection. | ||
*/ | ||
this._db = db; | ||
/** | ||
* The separator between different fragments of a SQL statement. | ||
* Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. | ||
*/ | ||
this._separator = " "; | ||
this._typeMap = []; | ||
/** | ||
* Array map of query condition to builder methods. | ||
* These methods are used by [[buildCondition]] to build SQL conditions from array syntax. | ||
*/ | ||
this._conditionBuilders = { | ||
@@ -32,2 +43,11 @@ 'NOT': 'buildNotCondition', | ||
/** | ||
* Generates a SELECT SQL statement from a [[Query]] object. | ||
* @param {Query} query the [[Query]] object from which the SQL statement will be generated. | ||
* @param {Object} params the parameters to be bound to the generated SQL statement. These parameters will | ||
* be included in the result with the additional parameters generated during the query building process. | ||
* @return {Array} the generated SQL statement (the first array element) and the corresponding | ||
* parameters to be bound to the SQL statement (the second array element). The parameters returned | ||
* include those provided in `$params`. | ||
*/ | ||
build(query, params={}) { | ||
@@ -59,2 +79,9 @@ | ||
/** | ||
* @param {Array|Object} columns | ||
* @param {Object} params the binding parameters to be populated | ||
* @param {boolean} distinct | ||
* @param {string} selectOption | ||
* @return {string} the SELECT clause built from [[Query::select]]. | ||
*/ | ||
buildSelect(columns, params, distinct, selectOption) { | ||
@@ -98,2 +125,7 @@ | ||
/** | ||
* @param {Array|Object} tables | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the FROM clause built from [[Query::from]]. | ||
*/ | ||
buildFrom(tables, params) { | ||
@@ -109,2 +141,8 @@ if (_.isEmpty(tables)) { | ||
/** | ||
* @param {Array} joins | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the JOIN clause built from [[Query::join]]. | ||
* @throws Exception if the joins parameter is not in proper format | ||
*/ | ||
buildJoin(joins, params) { | ||
@@ -122,3 +160,3 @@ if (_.isEmpty(joins)) { | ||
var tables = this._quoteTableNames(table, params); | ||
//table = reset($tables); @todo figure out conversion. | ||
table = tables.shift(); //@todo figure out conversion. | ||
joins[i] = `${joinType} ${table}`; | ||
@@ -136,2 +174,9 @@ if (join[2] !== undefined) { | ||
/** | ||
* Quotes table names passed | ||
* | ||
* @param {Array|Object} tables | ||
* @param {Object} params | ||
* @return {Array|Object} | ||
*/ | ||
_quoteTableNames(tables, params) { | ||
@@ -164,2 +209,7 @@ Object.keys(tables).forEach ((i) => { | ||
/** | ||
* @param {String|Array} condition | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the WHERE clause built from [[Query::where]]. | ||
*/ | ||
buildWhere(condition, params) { | ||
@@ -170,2 +220,6 @@ var where = this.buildCondition(condition, params); | ||
/** | ||
* @param {Array|Object} columns | ||
* @return {String} the GROUP BY clause | ||
*/ | ||
buildGroupBy(columns) { | ||
@@ -175,2 +229,7 @@ return _.isEmpty(columns) ? '' : 'GROUP BY ' + this.buildColumns(columns); | ||
/** | ||
* @param {String|Array|Object} condition | ||
* @param {Object} params the binding parameters to be populated | ||
* @return string the HAVING clause built from [[Query::having]]. | ||
*/ | ||
buildHaving(condition, params) { | ||
@@ -181,2 +240,129 @@ var having = this.buildCondition(condition, params); | ||
/** | ||
* Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL. | ||
* @param {String} sql the existing SQL (without ORDER BY/LIMIT/OFFSET) | ||
* @param {Array|Object} orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter. | ||
* @param {int} limit the limit number. See [[Query::limit]] for more details. | ||
* @param {int} offset the offset number. See [[Query::offset]] for more details. | ||
* @return {String} the SQL completed with ORDER BY/LIMIT/OFFSET (if any) | ||
*/ | ||
buildOrderByAndLimit(sql, orderBy, limit, offset) { | ||
orderBy = this.buildOrderBy(orderBy); | ||
if (orderBy !== '') { | ||
sql += this._separator + orderBy; | ||
} | ||
limit = this.buildLimit(limit, offset); | ||
if (limit !== '') { | ||
sql += this._separator + limit; | ||
} | ||
return sql; | ||
} | ||
/** | ||
* @param {Array|Object} columns | ||
* @return {String} the ORDER BY clause built from [[Query::$orderBy]]. | ||
*/ | ||
buildOrderBy(columns) { | ||
if (_.isEmpty(columns)) { | ||
return ''; | ||
} | ||
var orders = []; | ||
Object.keys(columns).forEach((name) => { | ||
orders.push(this._db.quoteColumnName(name) + (columns[name] === SORT_DESC ? ' DESC' : '')); | ||
}); | ||
return 'ORDER BY ' + orders.join(', '); | ||
} | ||
/** | ||
* @param {int} limit | ||
* @param {int} offset | ||
* @return {String} the LIMIT and OFFSET clauses | ||
*/ | ||
buildLimit(limit, offset) { | ||
var sql = ''; | ||
if (this.hasLimit(limit)) { | ||
sql = 'LIMIT ' + limit; | ||
} | ||
if (this.hasOffset(offset)) { | ||
sql += ' OFFSET ' + offset; | ||
} | ||
return _.trimStart(sql); | ||
} | ||
/** | ||
* Checks to see if the given limit is effective. | ||
* @param {String} limit the given limit | ||
* @return {boolean} whether the limit is effective | ||
*/ | ||
hasLimit(limit) { | ||
return /^\d+$/.test(limit); | ||
} | ||
/** | ||
* Checks to see if the given offset is effective. | ||
* @param {String} offset the given offset | ||
* @return {boolean} whether the offset is effective | ||
*/ | ||
hasOffset(offset) { | ||
return /^\d+$/.test(offset) && offset != 0; | ||
} | ||
/** | ||
* @param {Array|Object} unions | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the UNION clause built from [[Query::union]]. | ||
*/ | ||
buildUnion(unions, params) { | ||
if (_.isEmpty(unions)) { | ||
return ''; | ||
} | ||
var result = ''; | ||
unions.forEach((union, i) => { | ||
var query = union['query']; | ||
if (query instanceof Query) { | ||
var data = this.build(query, params); | ||
unions[i]['query'] = data[0]; | ||
params = data[1]; | ||
} | ||
result += 'UNION ' + (union['all'] ? 'ALL ' : '') + '( ' + unions[i]['query'] + ' ) '; | ||
}); | ||
return result.trim(); | ||
} | ||
/** | ||
* Processes columns and properly quotes them if necessary. | ||
* It will join all columns into a string with comma as separators. | ||
* @param {String|Array|Object} columns the columns to be processed | ||
* @return {String} the processing result | ||
*/ | ||
buildColumns(columns) { | ||
if (!_.isArray(columns)) { | ||
if (columns.indexOf(columns, '(') !== -1) { | ||
return columns; | ||
} else { | ||
columns = columns.split('/\s*,\s*/').filter((value) => value.length > 0); | ||
} | ||
} | ||
columns.forEach((column, i) => { | ||
if (column.indexOf('(') === -1) { | ||
columns[i] = this._db.quoteColumnName(column); | ||
} | ||
}); | ||
return _.isArray(columns) ? columns.join(', ') : columns; | ||
} | ||
/** | ||
* Parses the condition specification and generates the corresponding SQL expression. | ||
* @param {String|Array|Object} condition the condition specification. Please refer to [[Query::where()]] | ||
* on how to specify a condition. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
*/ | ||
buildCondition(condition, params) { | ||
@@ -209,19 +395,8 @@ | ||
buildColumns(columns) { | ||
if (!_.isArray(columns)) { | ||
if (columns.indexOf(columns, '(') !== -1) { | ||
return columns; | ||
} else { | ||
columns = columns.split('/\s*,\s*/').filter((value) => value.length > 0); | ||
} | ||
} | ||
columns.forEach((column, i) => { | ||
if (column.indexOf('(') === -1) { | ||
columns[i] = this._db.quoteColumnName(column); | ||
} | ||
}); | ||
return _.isArray(columns) ? columns.join(', ') : columns; | ||
} | ||
/** | ||
* Creates a condition based on column-value pairs. | ||
* @param {Array|Object} condition the condition specification. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
*/ | ||
buildHashCondition(condition, params) { | ||
@@ -251,2 +426,9 @@ var parts = []; | ||
/** | ||
* Connects two or more SQL expressions with the `AND` or `OR` operator. | ||
* @param {String} operator the operator to use for connecting the given operands | ||
* @param {Array} operands the SQL expressions to connect. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
*/ | ||
buildAndCondition(operator, operands, params) | ||
@@ -270,2 +452,10 @@ { | ||
/** | ||
* Inverts an SQL expressions with `NOT` operator. | ||
* @param {String} operator the operator to use for connecting the given operands | ||
* @param {Array} operands the SQL expressions to connect. | ||
* @param {Array} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws Error if wrong number of operands have been given. | ||
*/ | ||
buildNotCondition(operator, operands, params) { | ||
@@ -287,2 +477,11 @@ if (operands.length != 1) { | ||
/** | ||
* Creates an SQL expressions with the `BETWEEN` operator. | ||
* @param {String} operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`) | ||
* @param {Array} operands the first operand is the column name. The second and third operands | ||
* describe the interval that column value should be in. | ||
* @param {Object|Array} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws InvalidParamException if wrong number of operands have been given. | ||
*/ | ||
buildBetweenCondition(operator, operands, params) { | ||
@@ -309,2 +508,14 @@ if (operands[0] === undefined || operands[1] === undefined || operands[2] == undefined) { | ||
/** | ||
* Creates an SQL expressions with the `IN` operator. | ||
* @param {String} operator the operator to use (e.g. `IN` or `NOT IN`) | ||
* @param {Array} operands the first operand is the column name. If it is an array | ||
* a composite IN condition will be generated. | ||
* The second operand is an array of values that column value should be among. | ||
* If it is an empty array the generated expression will be a `false` value if | ||
* operator is `IN` and empty if operator is `NOT IN`. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws Error if wrong number of operands have been given. | ||
*/ | ||
buildInCondition(operator, operands, params) { | ||
@@ -357,2 +568,11 @@ if (operands[0] === undefined || operands[1] === undefined) { | ||
/** | ||
* Builds SQL for IN condition | ||
* | ||
* @param {String} operator | ||
* @param {Array} columns | ||
* @param {Query} values | ||
* @param {Array} params | ||
* @return {String} SQL | ||
*/ | ||
buildSubqueryInCondition(operator, columns, values, params) { | ||
@@ -375,2 +595,11 @@ var {0: sql} = this.build(values, params); | ||
/** | ||
* Builds SQL for IN condition | ||
* | ||
* @param {String} operator | ||
* @param {Array} columns | ||
* @param {Query} values | ||
* @param {Array} params | ||
* @return {String} SQL | ||
*/ | ||
buildCompositeInCondition(operator, columns, values, params) { | ||
@@ -401,67 +630,90 @@ var vss = []; | ||
buildOrderByAndLimit(sql, orderBy, limit, offset) { | ||
orderBy = this.buildOrderBy(orderBy); | ||
if (orderBy !== '') { | ||
sql += this._separator + orderBy; | ||
/** | ||
* Creates an SQL expressions with the `LIKE` operator. | ||
* @param {String} operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) | ||
* @param {Array} operands an array of two or three operands | ||
* | ||
* - The first operand is the column name. | ||
* - The second operand is a single value or an array of values that column value | ||
* should be compared with. If it is an empty array the generated expression will | ||
* be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator | ||
* is `NOT LIKE` or `OR NOT LIKE`. | ||
* - An optional third operand can also be provided to specify how to escape special characters | ||
* in the value(s). The operand should be an array of mappings from the special characters to their | ||
* escaped counterparts. If this operand is not provided, a default escape mapping will be used. | ||
* You may use `false` or an empty array to indicate the values are already escaped and no escape | ||
* should be applied. Note that when using an escape mapping (or the third operand is not provided), | ||
* the values will be automatically enclosed within a pair of percentage characters. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws Error if wrong number of operands have been given. | ||
*/ | ||
buildLikeCondition(operator, operands, params) | ||
{ | ||
if (operands[0] === undefined || operands[1] === undefined) { | ||
throw new Error(`Operator '${operator}' requires two operands.`); | ||
} | ||
limit = this.buildLimit(limit, offset); | ||
if (limit !== '') { | ||
sql += this._separator + limit; | ||
} | ||
return sql; | ||
} | ||
buildUnion(unions, params) { | ||
if (_.isEmpty(unions)) { | ||
return ''; | ||
var escape = operands[2] !== undefined ? operands[2] : {'%': '\%', '_': '\_', '\\': '\\\\'}; | ||
delete operands[2]; | ||
var matches = operator.match(/^(AND |OR |)(((NOT |))I?LIKE)/); | ||
if (!matches) { | ||
throw new Error(`Invalid operator '${operator}'.`); | ||
} | ||
var andor = ' ' + (!_.isEmpty(matches[1]) ? matches[1] : 'AND '); | ||
var not = !_.isEmpty(matches[3]); | ||
operator = matches[2]; | ||
var result = ''; | ||
var {0: column, 1: values} = operands; | ||
unions.forEach((union, i) => { | ||
var query = union['query']; | ||
if (query instanceof Query) { | ||
var data = this.build(query, params); | ||
unions[i]['query'] = data[0]; | ||
params = data[1]; | ||
} | ||
if (!_.isArray(values)) { | ||
values = [values]; | ||
} | ||
result += 'UNION ' + (union['all'] ? 'ALL ' : '') + '( ' + unions[i]['query'] + ' ) '; | ||
}); | ||
if (_.isEmpty(values)) { | ||
return not ? '' : '0=1'; | ||
} | ||
return trim(result); | ||
} | ||
if (column.indexOf('(') === -1) { | ||
column = this._db.quoteColumnName(column); | ||
} | ||
buildOrderBy(columns) { | ||
if (_.isEmpty(columns)) { | ||
return ''; | ||
} | ||
var orders = []; | ||
Object.keys(columns).forEach((name) => { | ||
orders.push(this._db.quoteColumnName(name) + (columns[name] === SORT_DESC ? ' DESC' : '')); | ||
var parts = []; | ||
values.forEach((value) => { | ||
var index = _.isArray(params) ? params.length : Object.keys(params).length; | ||
var phName = `${QueryBuilder.PARAM_PREFIX}${index}`; | ||
params[phName] = _.isEmpty(escape) ? value : ('%' + strtr(value, escape) + '%'); | ||
parts.push(`${column} ${operator} ${phName}`); | ||
}); | ||
return 'ORDER BY ' + orders.join(', '); | ||
return parts.join(andor); | ||
} | ||
buildLimit(limit, offset) { | ||
var sql = ''; | ||
if (this.hasLimit(limit)) { | ||
sql = 'LIMIT ' + limit; | ||
/** | ||
* Creates an SQL expressions with the `EXISTS` operator. | ||
* @param {String} operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`) | ||
* @param {Array} operands contains only one element which is a [[Query]] object representing the sub-query. | ||
* @param {Object} params the binding parameters to be populated | ||
* @return {String} the generated SQL expression | ||
* @throws Error if the operand is not a [[Query]] object. | ||
*/ | ||
buildExistsCondition(operator, operands, params) | ||
{ | ||
if (operands[0] instanceof Query) { | ||
var {0: sql} = this.build(operands[0], params); | ||
return `${operator} (${sql})`; | ||
} else { | ||
throw new Error('Subquery for EXISTS operator must be a Query object.'); | ||
} | ||
if (this.hasOffset(offset)) { | ||
sql += ' OFFSET ' + offset; | ||
} | ||
return _.trimStart(sql); | ||
} | ||
hasLimit(limit) { | ||
return /^\d+$/.test(limit); | ||
} | ||
hasOffset(offset) { | ||
return /^\d+$/.test(offset) && offset != 0; | ||
} | ||
/** | ||
* Creates an SQL expressions like `"column" operator value`. | ||
* @param {String} operator the operator to use. Anything could be used e.g. `>`, `<=`, etc. | ||
* @param {Array} operands contains two column names. | ||
* @param {Array} params the binding parameters to be populated | ||
* @return string the generated SQL expression | ||
* @throws InvalidParamException if wrong number of operands have been given. | ||
*/ | ||
buildSimpleCondition(operator, operands, params) | ||
@@ -468,0 +720,0 @@ { |
96820
2190
3
+ Addedphpjs@^1.3.2
+ Addedabbrev@1.1.1(transitive)
+ Addedcli@0.4.4-2(transitive)
+ Addedcommander@0.6.12.0.0(transitive)
+ Addedconfig-chain@1.1.13(transitive)
+ Addeddebug@4.3.5(transitive)
+ Addeddeep-equal@0.1.2(transitive)
+ Addeddiff@1.0.7(transitive)
+ Addedfresh@0.1.0(transitive)
+ Addedglob@3.2.13.2.3(transitive)
+ Addedgraceful-fs@1.2.32.0.3(transitive)
+ Addedgrowl@1.7.0(transitive)
+ Addedinherits@1.0.2(transitive)
+ Addedini@1.3.8(transitive)
+ Addedjade@0.26.3(transitive)
+ Addedjs-beautify@1.4.2(transitive)
+ Addedlru-cache@2.7.3(transitive)
+ Addedmime@1.2.6(transitive)
+ Addedminimatch@0.2.14(transitive)
+ Addedmkdirp@0.3.00.3.5(transitive)
+ Addedmocha@1.17.0(transitive)
+ Addedms@2.1.2(transitive)
+ Addednopt@2.1.2(transitive)
+ Addedphpjs@1.3.2(transitive)
+ Addedproto-list@1.2.4(transitive)
+ Addedrange-parser@0.0.4(transitive)
+ Addedsend@0.1.0(transitive)
+ Addedsigmund@1.0.1(transitive)
+ Addedunderscore@1.5.2(transitive)