2014-09-01 17:18:45 +02:00
'use strict' ;
2014-04-15 11:43:47 -04:00
// Query Compiler
// -------
2015-05-09 13:58:18 -04:00
var _ = require ( 'lodash' ) ;
2014-04-16 04:29:20 -04:00
var helpers = require ( '../helpers' ) ;
2015-05-09 13:58:18 -04:00
var Raw = require ( '../raw' ) ;
var assign = require ( 'lodash/object/assign' ) ;
var reduce = require ( 'lodash/collection/reduce' ) ;
2015-04-19 16:31:52 -04:00
// The "QueryCompiler" takes all of the query statements which
// have been gathered in the "QueryBuilder" and turns them into a
// properly formatted / bound query string.
function QueryCompiler ( client , builder ) {
2015-05-09 13:58:18 -04:00
this . client = client ;
this . method = builder . _method || 'select' ;
this . options = builder . _options ;
this . single = builder . _single ;
this . grouped = _ . groupBy ( builder . _statements , 'grouping' ) ;
this . formatter = client . formatter ( ) ;
2014-04-27 19:36:40 -04:00
}
2014-04-08 16:25:57 -04:00
2015-05-09 13:58:18 -04:00
var components = [ 'columns' , 'join' , 'where' , 'union' , 'group' , 'having' , 'order' , 'limit' , 'offset' , 'lock' ] ;
2013-12-27 14:44:21 -05:00
2015-04-19 16:31:52 -04:00
assign ( QueryCompiler . prototype , {
// Used when the insert call is empty.
_emptyInsertValue : 'default values' ,
// Collapse the builder into a single object
2015-05-09 13:58:18 -04:00
toSQL : function toSQL ( method ) {
method = method || this . method ;
var val = this [ method ] ( ) ;
2015-04-19 16:31:52 -04:00
var defaults = {
method : method ,
2015-04-30 18:07:16 -04:00
options : reduce ( this . options , assign , { } ) ,
2015-04-19 16:31:52 -04:00
bindings : this . formatter . bindings
} ;
if ( _ . isString ( val ) ) {
2015-05-09 13:58:18 -04:00
val = { sql : val } ;
2015-04-19 16:31:52 -04:00
}
if ( method === 'select' && this . single . as ) {
defaults . as = this . single . as ;
}
return assign ( defaults , val ) ;
} ,
// Compiles the `select` statement, or nested sub-selects
// by calling each of the component compilers, trimming out
// the empties, and returning a generated query string.
2015-05-09 13:58:18 -04:00
select : function select ( ) {
var i = - 1 ,
statements = [ ] ;
2015-04-22 10:34:14 -04:00
while ( ++ i < components . length ) {
statements . push ( this [ components [ i ] ] ( this ) ) ;
2015-04-19 16:31:52 -04:00
}
return _ . compact ( statements ) . join ( ' ' ) ;
} ,
2015-05-09 13:58:18 -04:00
pluck : function pluck ( ) {
2015-04-19 16:31:52 -04:00
return {
sql : this . select ( ) ,
pluck : this . single . pluck
} ;
} ,
// Compiles an "insert" query, allowing for multiple
// inserts using a single query statement.
2015-05-09 13:58:18 -04:00
insert : function insert ( ) {
2015-04-22 10:34:14 -04:00
var insertValues = this . single . insert || [ ] ;
2015-04-19 16:31:52 -04:00
var sql = 'insert into ' + this . tableName + ' ' ;
2015-04-22 10:34:14 -04:00
if ( Array . isArray ( insertValues ) ) {
2015-04-19 16:31:52 -04:00
if ( insertValues . length === 0 ) {
2015-05-09 13:58:18 -04:00
return '' ;
2015-04-19 16:31:52 -04:00
}
2015-04-22 10:34:14 -04:00
} else if ( typeof insertValues === 'object' && _ . isEmpty ( insertValues ) ) {
2015-05-09 13:58:18 -04:00
return sql + this . _emptyInsertValue ;
2015-04-19 16:31:52 -04:00
}
2015-04-22 10:34:14 -04:00
var insertData = this . _prepInsert ( insertValues ) ;
if ( typeof insertData === 'string' ) {
sql += insertData ;
2015-05-09 13:58:18 -04:00
} else {
2015-04-22 10:34:14 -04:00
if ( insertData . columns . length ) {
2015-05-09 13:58:18 -04:00
sql += '(' + this . formatter . columnize ( insertData . columns ) ;
sql += ') values (' ;
var i = - 1 ;
2015-04-22 10:34:14 -04:00
while ( ++ i < insertData . values . length ) {
2015-05-09 13:58:18 -04:00
if ( i !== 0 ) sql += '), (' ;
sql += this . formatter . parameterize ( insertData . values [ i ] ) ;
2015-04-19 16:31:52 -04:00
}
2015-04-22 10:34:14 -04:00
sql += ')' ;
} else if ( insertValues . length === 1 && insertValues [ 0 ] ) {
2015-05-09 13:58:18 -04:00
sql += this . _emptyInsertValue ;
2015-04-22 10:34:14 -04:00
} else {
2015-05-09 13:58:18 -04:00
sql = '' ;
2015-04-19 16:31:52 -04:00
}
}
return sql ;
} ,
// Compiles the "update" query.
2015-05-09 13:58:18 -04:00
update : function update ( ) {
2015-04-19 16:31:52 -04:00
// Make sure tableName is processed by the formatter first.
2015-05-09 13:58:18 -04:00
var tableName = this . tableName ;
2015-04-19 16:31:52 -04:00
var updateData = this . _prepUpdate ( this . single . update ) ;
2015-05-09 13:58:18 -04:00
var wheres = this . where ( ) ;
return 'update ' + tableName + ' set ' + updateData . join ( ', ' ) + ( wheres ? ' ' + wheres : '' ) ;
2015-04-19 16:31:52 -04:00
} ,
// Compiles the columns in the query, specifying if an item was distinct.
2015-05-09 13:58:18 -04:00
columns : function columns ( ) {
2015-04-19 16:31:52 -04:00
var distinct = false ;
2015-05-09 13:58:18 -04:00
if ( this . onlyUnions ( ) ) return '' ;
var columns = this . grouped . columns || [ ] ;
var i = - 1 ,
sql = [ ] ;
2015-04-19 16:31:52 -04:00
if ( columns ) {
2015-04-22 10:34:14 -04:00
while ( ++ i < columns . length ) {
2015-04-19 16:31:52 -04:00
var stmt = columns [ i ] ;
2015-05-09 13:58:18 -04:00
if ( stmt . distinct ) distinct = true ;
2015-04-19 16:31:52 -04:00
if ( stmt . type === 'aggregate' ) {
2015-05-09 13:58:18 -04:00
sql . push ( this . aggregate ( stmt ) ) ;
} else if ( stmt . value && stmt . value . length > 0 ) {
sql . push ( this . formatter . columnize ( stmt . value ) ) ;
2015-04-19 16:31:52 -04:00
}
}
}
2015-04-22 10:34:14 -04:00
if ( sql . length === 0 ) sql = [ '*' ] ;
2015-05-09 13:58:18 -04:00
return 'select ' + ( distinct ? 'distinct ' : '' ) + sql . join ( ', ' ) + ( this . tableName ? ' from ' + this . tableName : '' ) ;
2015-04-19 16:31:52 -04:00
} ,
2015-05-09 13:58:18 -04:00
aggregate : function aggregate ( stmt ) {
2015-04-19 16:31:52 -04:00
var val = stmt . value ;
var splitOn = val . toLowerCase ( ) . indexOf ( ' as ' ) ;
// Allows us to speciy an alias for the aggregate types.
if ( splitOn !== - 1 ) {
var col = val . slice ( 0 , splitOn ) ;
var alias = val . slice ( splitOn + 4 ) ;
return stmt . method + '(' + this . formatter . wrap ( col ) + ') as ' + this . formatter . wrap ( alias ) ;
}
return stmt . method + '(' + this . formatter . wrap ( val ) + ')' ;
} ,
// Compiles all each of the `join` clauses on the query,
// including any nested join queries.
2015-05-09 13:58:18 -04:00
join : function join ( ) {
var sql = '' ,
i = - 1 ,
joins = this . grouped . join ;
2015-04-19 16:31:52 -04:00
if ( ! joins ) return '' ;
2015-04-29 15:13:15 -04:00
while ( ++ i < joins . length ) {
2015-05-09 13:58:18 -04:00
var join = joins [ i ] ;
if ( i > 0 ) sql += ' ' ;
2015-04-19 16:31:52 -04:00
if ( join . joinType === 'raw' ) {
2015-05-09 13:58:18 -04:00
sql += this . formatter . unwrapRaw ( join . table ) ;
2014-09-08 16:02:46 +02:00
} else {
2015-05-09 13:58:18 -04:00
sql += join . joinType + ' join ' + this . formatter . wrap ( join . table ) ;
var ii = - 1 ;
2015-04-29 15:13:15 -04:00
while ( ++ ii < join . clauses . length ) {
2015-05-09 13:58:18 -04:00
var clause = join . clauses [ ii ] ;
sql += ' ' + ( ii > 0 ? clause [ 0 ] : clause [ 1 ] ) + ' ' ;
sql += this . formatter . wrap ( clause [ 2 ] ) ;
if ( clause [ 3 ] ) sql += ' ' + this . formatter . operator ( clause [ 3 ] ) ;
if ( clause [ 4 ] ) sql += ' ' + this . formatter . wrap ( clause [ 4 ] ) ;
2015-04-22 10:34:14 -04:00
}
2014-09-08 16:02:46 +02:00
}
2015-04-29 15:13:15 -04:00
}
return sql ;
2015-04-19 16:31:52 -04:00
} ,
// Compiles all `where` statements on the query.
2015-05-09 13:58:18 -04:00
where : function where ( ) {
2015-04-19 16:31:52 -04:00
var wheres = this . grouped . where ;
if ( ! wheres ) return ;
2015-05-09 13:58:18 -04:00
var i = - 1 ,
sql = [ ] ;
2015-04-24 10:31:23 -04:00
while ( ++ i < wheres . length ) {
2015-05-09 13:58:18 -04:00
var stmt = wheres [ i ] ;
var val = this [ stmt . type ] ( stmt ) ;
2015-04-24 10:31:23 -04:00
if ( val ) {
if ( sql . length === 0 ) {
2015-05-09 13:58:18 -04:00
sql [ 0 ] = 'where' ;
2015-04-24 10:31:23 -04:00
} else {
2015-05-09 13:58:18 -04:00
sql . push ( stmt . bool ) ;
2015-04-24 10:31:23 -04:00
}
2015-05-09 13:58:18 -04:00
sql . push ( val ) ;
2015-04-24 10:31:23 -04:00
}
2014-04-16 01:23:50 -04:00
}
2015-04-19 16:31:52 -04:00
return sql . length > 1 ? sql . join ( ' ' ) : '' ;
} ,
2015-05-09 13:58:18 -04:00
group : function group ( ) {
2015-04-19 16:31:52 -04:00
return this . _groupsOrders ( 'group' ) ;
} ,
2015-05-09 13:58:18 -04:00
order : function order ( ) {
2015-04-19 16:31:52 -04:00
return this . _groupsOrders ( 'order' ) ;
} ,
// Compiles the `having` statements.
2015-05-09 13:58:18 -04:00
having : function having ( ) {
2015-04-19 16:31:52 -04:00
var havings = this . grouped . having ;
if ( ! havings ) return '' ;
var sql = [ 'having' ] ;
for ( var i = 0 , l = havings . length ; i < l ; i ++ ) {
2015-05-09 13:58:18 -04:00
var str = '' ,
s = havings [ i ] ;
2015-04-19 16:31:52 -04:00
if ( i !== 0 ) str = s . bool + ' ' ;
if ( s . type === 'havingBasic' ) {
2015-05-09 13:58:18 -04:00
sql . push ( str + this . formatter . columnize ( s . column ) + ' ' + this . formatter . operator ( s . operator ) + ' ' + this . formatter . parameter ( s . value ) ) ;
2015-04-22 10:34:14 -04:00
} else {
2015-05-09 13:58:18 -04:00
if ( s . type === 'whereWrapped' ) {
var val = this . whereWrapped ( s ) ;
if ( val ) sql . push ( val ) ;
2015-04-19 16:31:52 -04:00
} else {
2015-04-22 10:34:14 -04:00
sql . push ( str + this . formatter . unwrapRaw ( s . value ) ) ;
2015-04-19 16:31:52 -04:00
}
2013-12-27 14:44:21 -05:00
}
2014-04-08 16:25:57 -04:00
}
2015-04-19 16:31:52 -04:00
return sql . length > 1 ? sql . join ( ' ' ) : '' ;
} ,
// Compile the "union" queries attached to the main query.
2015-05-09 13:58:18 -04:00
union : function union ( ) {
2015-04-19 16:31:52 -04:00
var onlyUnions = this . onlyUnions ( ) ;
var unions = this . grouped . union ;
if ( ! unions ) return '' ;
var sql = '' ;
for ( var i = 0 , l = unions . length ; i < l ; i ++ ) {
var union = unions [ i ] ;
if ( i > 0 ) sql += ' ' ;
if ( i > 0 || ! onlyUnions ) sql += union . clause + ' ' ;
var statement = this . formatter . rawOrFn ( union . value ) ;
if ( statement ) {
if ( union . wrap ) sql += '(' ;
sql += statement ;
if ( union . wrap ) sql += ')' ;
}
2013-12-27 14:44:21 -05:00
}
2015-04-19 16:31:52 -04:00
return sql ;
} ,
// If we haven't specified any columns or a `tableName`, we're assuming this
// is only being used for unions.
2015-05-09 13:58:18 -04:00
onlyUnions : function onlyUnions ( ) {
return ! this . grouped . columns && this . grouped . union && ! this . tableName ;
2015-04-19 16:31:52 -04:00
} ,
2015-05-09 13:58:18 -04:00
limit : function limit ( ) {
2015-04-19 16:31:52 -04:00
var noLimit = ! this . single . limit && this . single . limit !== 0 ;
if ( noLimit ) return '' ;
return 'limit ' + this . formatter . parameter ( this . single . limit ) ;
} ,
2015-05-09 13:58:18 -04:00
offset : function offset ( ) {
2015-04-19 16:31:52 -04:00
if ( ! this . single . offset ) return '' ;
return 'offset ' + this . formatter . parameter ( this . single . offset ) ;
} ,
// Compiles a `delete` query.
2015-05-09 13:58:18 -04:00
del : function del ( ) {
2015-04-19 16:31:52 -04:00
// Make sure tableName is processed by the formatter first.
2015-05-09 13:58:18 -04:00
var tableName = this . tableName ;
2015-04-19 16:31:52 -04:00
var wheres = this . where ( ) ;
2015-05-09 13:58:18 -04:00
return 'delete from ' + tableName + ( wheres ? ' ' + wheres : '' ) ;
2015-04-19 16:31:52 -04:00
} ,
// Compiles a `truncate` query.
2015-05-09 13:58:18 -04:00
truncate : function truncate ( ) {
2015-04-19 16:31:52 -04:00
return 'truncate ' + this . tableName ;
} ,
// Compiles the "locks".
2015-05-09 13:58:18 -04:00
lock : function lock ( ) {
2015-04-19 16:31:52 -04:00
if ( this . single . lock ) {
if ( ! this . client . transacting ) {
2015-05-09 13:58:18 -04:00
helpers . warn ( 'You are attempting to perform a "lock" command outside of a transaction.' ) ;
2014-11-20 20:52:18 +01:00
} else {
2015-05-09 13:58:18 -04:00
return this [ this . single . lock ] ( ) ;
2014-11-20 20:52:18 +01:00
}
2014-03-14 16:56:55 -04:00
}
2015-04-19 16:31:52 -04:00
} ,
// Compile the "counter".
2015-05-09 13:58:18 -04:00
counter : function counter ( ) {
2015-04-19 16:31:52 -04:00
var counter = this . single . counter ;
var toUpdate = { } ;
2015-05-09 13:58:18 -04:00
toUpdate [ counter . column ] = this . client . raw ( this . formatter . wrap ( counter . column ) + ' ' + ( counter . symbol || '+' ) + ' ' + counter . amount ) ;
2015-04-19 16:31:52 -04:00
this . single . update = toUpdate ;
return this . update ( ) ;
} ,
// Where Clause
// ------
2015-05-09 13:58:18 -04:00
whereIn : function whereIn ( statement ) {
2015-04-22 10:34:14 -04:00
if ( Array . isArray ( statement . column ) ) return this . multiWhereIn ( statement ) ;
2015-05-09 13:58:18 -04:00
return this . formatter . wrap ( statement . column ) + ' ' + this . _not ( statement , 'in ' ) + this . wrap ( this . formatter . parameterize ( statement . value ) ) ;
2015-04-19 16:31:52 -04:00
} ,
2015-05-09 13:58:18 -04:00
multiWhereIn : function multiWhereIn ( statement ) {
var i = - 1 ,
sql = '(' + this . formatter . columnize ( statement . column ) + ') ' ;
sql += this . _not ( statement , 'in ' ) + '((' ;
2015-04-22 10:34:14 -04:00
while ( ++ i < statement . value . length ) {
2015-05-09 13:58:18 -04:00
if ( i !== 0 ) sql += '),(' ;
sql += this . formatter . parameterize ( statement . value [ i ] ) ;
2015-04-22 10:34:14 -04:00
}
2015-05-09 13:58:18 -04:00
return sql + '))' ;
2015-04-19 16:31:52 -04:00
} ,
2015-05-09 13:58:18 -04:00
whereNull : function whereNull ( statement ) {
2015-04-19 16:31:52 -04:00
return this . formatter . wrap ( statement . column ) + ' is ' + this . _not ( statement , 'null' ) ;
} ,
// Compiles a basic "where" clause.
2015-05-09 13:58:18 -04:00
whereBasic : function whereBasic ( statement ) {
return this . _not ( statement , '' ) + this . formatter . wrap ( statement . column ) + ' ' + this . formatter . operator ( statement . operator ) + ' ' + this . formatter . parameter ( statement . value ) ;
2015-04-19 16:31:52 -04:00
} ,
2015-05-09 13:58:18 -04:00
whereExists : function whereExists ( statement ) {
2015-04-19 16:31:52 -04:00
return this . _not ( statement , 'exists' ) + ' (' + this . formatter . rawOrFn ( statement . value ) + ')' ;
} ,
2015-05-09 13:58:18 -04:00
whereWrapped : function whereWrapped ( statement ) {
var val = this . formatter . rawOrFn ( statement . value , 'where' ) ;
2015-04-24 10:31:23 -04:00
return val && this . _not ( statement , '' ) + '(' + val . slice ( 6 ) + ')' || '' ;
2015-04-19 16:31:52 -04:00
} ,
2015-05-09 13:58:18 -04:00
whereBetween : function whereBetween ( statement ) {
return this . formatter . wrap ( statement . column ) + ' ' + this . _not ( statement , 'between' ) + ' ' + _ . map ( statement . value , this . formatter . parameter , this . formatter ) . join ( ' and ' ) ;
2015-04-19 16:31:52 -04:00
} ,
// Compiles a "whereRaw" query.
2015-05-09 13:58:18 -04:00
whereRaw : function whereRaw ( statement ) {
2015-04-22 10:34:14 -04:00
return this . formatter . unwrapRaw ( statement . value ) ;
2015-04-19 16:31:52 -04:00
} ,
2015-05-09 13:58:18 -04:00
wrap : function wrap ( str ) {
2015-04-19 16:31:52 -04:00
if ( str . charAt ( 0 ) !== '(' ) return '(' + str + ')' ;
return str ;
} ,
// Determines whether to add a "not" prefix to the where clause.
2015-05-09 13:58:18 -04:00
_not : function _not ( statement , str ) {
2015-04-19 16:31:52 -04:00
if ( statement . not ) return 'not ' + str ;
return str ;
} ,
2015-05-09 13:58:18 -04:00
_prepInsert : function _prepInsert ( data ) {
2015-04-19 16:31:52 -04:00
var isRaw = this . formatter . rawOrFn ( data ) ;
if ( isRaw ) return isRaw ;
var columns = [ ] ;
2015-05-09 13:58:18 -04:00
var values = [ ] ;
2015-04-22 10:34:14 -04:00
if ( ! Array . isArray ( data ) ) data = data ? [ data ] : [ ] ;
2015-05-09 13:58:18 -04:00
var i = - 1 ;
2015-04-19 16:31:52 -04:00
while ( ++ i < data . length ) {
2015-04-22 15:39:29 -04:00
if ( data [ i ] == null ) break ;
2015-05-09 13:58:18 -04:00
if ( i === 0 ) columns = Object . keys ( data [ i ] ) . sort ( ) ;
var row = new Array ( columns . length ) ;
var keys = Object . keys ( data [ i ] ) ;
var j = - 1 ;
2015-04-19 16:31:52 -04:00
while ( ++ j < keys . length ) {
var key = keys [ j ] ;
var idx = columns . indexOf ( key ) ;
if ( idx === - 1 ) {
2015-05-09 13:58:18 -04:00
columns = columns . concat ( key ) . sort ( ) ;
idx = columns . indexOf ( key ) ;
var k = - 1 ;
2015-04-19 16:31:52 -04:00
while ( ++ k < values . length ) {
2015-05-09 13:58:18 -04:00
values [ k ] . splice ( idx , 0 , undefined ) ;
2015-04-19 16:31:52 -04:00
}
2015-05-09 13:58:18 -04:00
row . splice ( idx , 0 , undefined ) ;
2015-04-19 16:31:52 -04:00
}
2015-05-09 13:58:18 -04:00
row [ idx ] = data [ i ] [ key ] ;
2015-04-19 16:31:52 -04:00
}
2015-05-09 13:58:18 -04:00
values . push ( row ) ;
2014-06-14 09:58:38 -04:00
}
2015-04-19 16:31:52 -04:00
return {
columns : columns ,
2015-05-09 13:58:18 -04:00
values : values
2015-04-19 16:31:52 -04:00
} ;
} ,
// "Preps" the update.
2015-05-09 13:58:18 -04:00
_prepUpdate : function _prepUpdate ( data ) {
var vals = [ ] ;
var sorted = Object . keys ( data ) . sort ( ) ;
var i = - 1 ;
2015-04-22 10:34:14 -04:00
while ( ++ i < sorted . length ) {
2015-04-19 16:31:52 -04:00
vals . push ( this . formatter . wrap ( sorted [ i ] ) + ' = ' + this . formatter . parameter ( data [ sorted [ i ] ] ) ) ;
2014-04-16 10:41:16 -04:00
}
2015-04-19 16:31:52 -04:00
return vals ;
} ,
// Compiles the `order by` statements.
2015-05-09 13:58:18 -04:00
_groupsOrders : function _groupsOrders ( type ) {
2015-04-19 16:31:52 -04:00
var items = this . grouped [ type ] ;
if ( ! items ) return '' ;
var formatter = this . formatter ;
var sql = items . map ( function ( item ) {
2015-05-09 13:58:18 -04:00
return ( item . value instanceof Raw ? formatter . unwrapRaw ( item . value ) : formatter . columnize ( item . value ) ) + ( type === 'order' && item . type !== 'orderByRaw' ? ' ' + formatter . direction ( item . direction ) : '' ) ;
2015-04-19 16:31:52 -04:00
} ) ;
return sql . length ? type + ' by ' + sql . join ( ', ' ) : '' ;
2014-04-15 13:10:32 -04:00
}
2015-04-19 16:31:52 -04:00
2015-05-09 13:58:18 -04:00
} ) ;
2015-04-19 16:31:52 -04:00
QueryCompiler . prototype . first = QueryCompiler . prototype . select ;
// Get the table name, wrapping it if necessary.
// Implemented as a property to prevent ordering issues as described in #704.
Object . defineProperty ( QueryCompiler . prototype , 'tableName' , {
2015-05-09 13:58:18 -04:00
get : function get ( ) {
if ( ! this . _tableName ) {
2015-04-19 16:31:52 -04:00
// Only call this.formatter.wrap() the first time this property is accessed.
this . _tableName = this . single . table ? this . formatter . wrap ( this . single . table ) : '' ;
2014-06-03 00:47:54 -04:00
}
2015-04-19 16:31:52 -04:00
return this . _tableName ;
2014-04-15 13:10:32 -04:00
}
2015-04-19 16:31:52 -04:00
} ) ;
2015-05-09 13:58:18 -04:00
module . exports = QueryCompiler ;