diff --git a/sql-bricks.js b/sql-bricks.js index 59f330f..ab65f31 100644 --- a/sql-bricks.js +++ b/sql-bricks.js @@ -63,11 +63,16 @@ Select.prototype.fullJoin = Select.prototype.fullOuterJoin = function join() { Select.prototype.crossJoin = function join() { return this._addJoins(arguments, 'CROSS'); }; -Select.prototype.on = function on() { +Select.prototype.on = function on(on) { var last_join = this.joins[this.joins.length - 1]; - if (!last_join.on) - last_join.on = {}; - _.extend(last_join.on, argsToObject(arguments)); + if (isExpr(on)) { + last_join.on = on; + } + else { + if (!last_join.on) + last_join.on = {}; + _.extend(last_join.on, argsToObject(arguments)); + } return this; }; @@ -95,6 +100,13 @@ Select.prototype.offset = function offset(count) { this._offset = count; return this; }; +Select.prototype.as = function as(tbl_name) { + if (tbl_name == null) + return this._as; + + this._as = tbl_name; + return this; +}; var compounds = { 'union': 'UNION', 'unionAll': 'UNION ALL', @@ -364,6 +376,14 @@ Statement.prototype.toParams = function toParams(opts) { opts = {}; _.extend(opts, {'parameterized': true, 'values': [], 'value_ix': 1}); var sql = this._toString(opts); + + // convert arrays (& other objects?) + opts.values = opts.values.map(function(val) { + if (val != null && typeof val == 'object') + return val.toString(); + else + return val; + }); return {'text': sql, 'values': opts.values}; }; @@ -448,15 +468,27 @@ Join.prototype.autoGenerateOn = function autoGenerateOn(tbl, left_tbl) { }; Join.prototype.toString = function toString(opts) { var on = this.on, tbl = this.tbl, left_tbl = this.left_tbl; - if (!on || _.isEmpty(on)) { + if ((!on || _.isEmpty(on)) && !(tbl instanceof Statement)) { if (sql._joinCriteria) on = this.autoGenerateOn(tbl, left_tbl); else throw new Error('No join criteria supplied for "' + getAlias(tbl) + '" join'); } - return this.type + ' JOIN ' + tbl + ' ON ' + _.map(_.keys(on), function(key) { - return handleColumn(key, opts) + ' = ' + handleColumn(on[key], opts); - }).join(', '); + + if (tbl instanceof Statement) + tbl = '(' + tbl._toString(opts) + ')' + (tbl.as() ? ' AS ' + tbl.as() : ''); + + if (isExpr(on)) { + on = on.toString(opts); + } + else { + if (on) { + on = _.map(_.keys(on), function(key) { + return handleColumn(key, opts) + ' = ' + handleColumn(on[key], opts); + }).join(' AND ') + } + } + return this.type + ' JOIN ' + tbl + (on ? ' ON ' + on : ''); }; @@ -707,9 +739,15 @@ function handleValue(val, opts) { var prefix = opts.placeholder || '$'; return prefix + opts.value_ix++; } - else { - return (typeof val == 'string') ? "'" + val.replace(/'/g, "''") + "'" : val; - } + + // handle arrays (& other objects?) + if (val != null && typeof val == 'object') + val = val.toString(); + + if (typeof val == 'string') + return "'" + val.replace(/'/g, "''") + "'" + + return val; } // Table C-1 of http://www.postgresql.org/docs/9.3/static/sql-keywords-appendix.html diff --git a/tests/tests.js b/tests/tests.js index 2ec8754..d231ce1 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -21,7 +21,7 @@ else { var select = sql.select, insertInto = sql.insertInto, insert = sql.insert, update = sql.update, del = sql.delete; var and = sql.and, or = sql.or, like = sql.like, not = sql.not, $in = sql.in, - isNull = sql.isNull, isNotNull = sql.isNotNull, equal = sql.equal, + isNull = sql.isNull, isNotNull = sql.isNotNull, equal = sql.equal, eq = sql.eq, lt = sql.lt, lte = sql.lte, gt = sql.gt, gte = sql.gte, between = sql.between, exists = sql.exists, eqAny = sql.eqAny, notEqAny = sql.notEqAny, union = sql.union; @@ -61,6 +61,15 @@ describe('SQL Bricks', function() { 'UPDATE user SET name = $1', ["Muad'Dib"]); }); + it('should call .toString() on arrays in parameterized sql', function() { + checkParams(update('user', {'name': ["Paul", "Muad'Dib"]}), + 'UPDATE user SET name = $1', + ["Paul,Muad'Dib"]); + }); + it('should call .toString() on arrays in non-parameterized sql', function() { + check(update('user', {'name': ["Paul", "Muad'Dib"]}), + "UPDATE user SET name = 'Paul,Muad''Dib'"); + }); it('should generate node-sqlite3 style params', function() { var values = {'first_name': 'Fred', 'last_name': 'Flintstone'}; var result = insert('user', values).toParams({'placeholder': '?'}); @@ -348,6 +357,18 @@ describe('SQL Bricks', function() { 'SELECT * FROM user usr ' + 'CROSS JOIN address addr ON usr.addr_fk = addr.pk'); }); + it('join() should accept an expression for the on argument', function() { + check(select().from('usr').join('addr', eq('usr.addr_id', sql('addr.id'))), + 'SELECT * FROM user usr INNER JOIN address addr ON usr.addr_id = addr.id'); + }); + it('join() should accept a subquery for the table', function() { + check(select().from('user').join(select().from('user')), + 'SELECT * FROM user INNER JOIN (SELECT * FROM user)'); + }); + it('join() should accept a subquery for the table w/ an AS', function() { + check(select().from('user').join(select().from('user').as('new_users')), + 'SELECT * FROM user INNER JOIN (SELECT * FROM user) AS new_users'); + }); }); describe('on()', function() { @@ -373,6 +394,10 @@ describe('SQL Bricks', function() { 'SELECT * FROM user usr, person psn ' + 'INNER JOIN address addr ON usr.addr_id = addr.id, psn.addr_id = addr.id'); }); + it('should accept an expression', function() { + check(select().from('usr').join('addr').on(eq('usr.addr_id', sql('addr.id'))), + 'SELECT * FROM user usr INNER JOIN address addr ON usr.addr_id = addr.id'); + }); }); describe('WHERE clauses', function() {