diff --git a/lib/node/addColumn.js b/lib/node/addColumn.js index 273d8836..ba78c647 100644 --- a/lib/node/addColumn.js +++ b/lib/node/addColumn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'ADD COLUMN' diff --git a/lib/node/alias.js b/lib/node/alias.js index 78f01323..df8ca772 100644 --- a/lib/node/alias.js +++ b/lib/node/alias.js @@ -1,7 +1,7 @@ 'use strict'; -var _ = require('lodash'); -var Node = require(__dirname); +var _ = require('lodash'); +var Node = require('./'); var AliasNode = Node.define({ type: 'ALIAS', diff --git a/lib/node/alter.js b/lib/node/alter.js index ce53623c..28f3ddea 100644 --- a/lib/node/alter.js +++ b/lib/node/alter.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'ALTER' diff --git a/lib/node/binary.js b/lib/node/binary.js index 7b721650..912ed847 100644 --- a/lib/node/binary.js +++ b/lib/node/binary.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var BinaryNode = Node.define(_.extend({ diff --git a/lib/node/column.js b/lib/node/column.js index 8c4f9c3b..9fd5ff87 100644 --- a/lib/node/column.js +++ b/lib/node/column.js @@ -1,24 +1,28 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); -module.exports = Node.define({ +var ColumnNode = module.exports = Node.define({ type: 'COLUMN', constructor: function(config) { Node.call(this); - this.name = config.name; - this.alias = config.alias; - this.star = config.star; - this.asArray = config.asArray; + + // implement copy constructor functionality + this.name = config.name; + this.alias = config.alias; + this.star = config.star; + this.asArray = config.asArray; this.aggregator = config.aggregator; - this.table = config.table; - this.value = config.getValue(); - this.dataType = config.dataType; - this.distinct = config.distinct; + this.table = config.table; + this.value = config.getValue ? config.getValue() : config.value; + this.dataType = config.dataType; + this.distinct = config.distinct; this.primaryKey = config.primaryKey; }, as: function(alias) { - this.alias = alias; - return this; + // create a new node with the alias + var node = new ColumnNode(this); + node.alias = alias; + return node; } }); diff --git a/lib/node/create.js b/lib/node/create.js index bf535f6a..638367db 100644 --- a/lib/node/create.js +++ b/lib/node/create.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'CREATE' diff --git a/lib/node/createIndex.js b/lib/node/createIndex.js index 2384ab8e..197ad81a 100644 --- a/lib/node/createIndex.js +++ b/lib/node/createIndex.js @@ -1,47 +1,74 @@ 'use strict'; +var _ = require('lodash'); var Node = require('./'); var sliced = require('sliced'); -module.exports = Node.define({ +var CreateIndexNode = module.exports = Node.define({ type: 'CREATE INDEX', constructor: function(table, indexName) { Node.call(this); - this.table = table; - this.options = { indexName: indexName, columns: [] }; + if (table.type === 'CREATE INDEX') { + // implement copy constructor with duck typing + var other = table; + this.table = other.table; + this.options = { + algorithm : other.options.algorithm, + // not deep cloning, because column nodes are immutable + columns : _.clone(other.options.columns), + indexName : other.options.indexName, + parser : other.options.parser, + type : other.options.type + }; + } else { + this.table = table; + this.options = { + algorithm : undefined, + columns : [], + indexName : indexName, + parser : undefined, + type : undefined + }; + } }, unique: function() { - this.options.type = 'unique'; - return this; + var node = new CreateIndexNode(this); + node.options.type = 'unique'; + return node; }, spatial: function() { - this.options.type = 'spatial'; - return this; + var node = new CreateIndexNode(this); + node.options.type = 'spatial'; + return node; }, fulltext: function() { - this.options.type = 'fulltext'; - return this; + var node = new CreateIndexNode(this); + node.options.type = 'fulltext'; + return node; }, using: function(algorithm) { - this.options.algorithm = algorithm; - return this; + var node = new CreateIndexNode(this); + node.options.algorithm = algorithm; + return node; }, on: function() { var args = sliced(arguments); - this.options.columns = this.options.columns.concat(args); - return this; + var node = new CreateIndexNode(this); + node.options.columns = node.options.columns.concat(args); + return node; }, withParser: function(parser) { - this.options.parser = parser; - return this; + var node = new CreateIndexNode(this); + node.options.parser = parser; + return node; }, indexName: function() { diff --git a/lib/node/default.js b/lib/node/default.js index 49bd7ebd..48cb5ea9 100644 --- a/lib/node/default.js +++ b/lib/node/default.js @@ -1,6 +1,8 @@ 'use strict'; -module.exports = require(__dirname).define({ +var Node = require('./'); + +module.exports = Node.define({ type: 'DEFAULT', value: function() { return; diff --git a/lib/node/delete.js b/lib/node/delete.js index 2acb5872..5f020def 100644 --- a/lib/node/delete.js +++ b/lib/node/delete.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'DELETE' diff --git a/lib/node/drop.js b/lib/node/drop.js index 24e19062..54f3f708 100644 --- a/lib/node/drop.js +++ b/lib/node/drop.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'DROP' diff --git a/lib/node/dropColumn.js b/lib/node/dropColumn.js index 60ad4219..653b3879 100644 --- a/lib/node/dropColumn.js +++ b/lib/node/dropColumn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'DROP COLUMN' diff --git a/lib/node/from.js b/lib/node/from.js index 84a6010f..9888fda7 100644 --- a/lib/node/from.js +++ b/lib/node/from.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); var From = Node.define({ type: 'FROM' diff --git a/lib/node/functionCall.js b/lib/node/functionCall.js index 6d43c279..df3edd71 100644 --- a/lib/node/functionCall.js +++ b/lib/node/functionCall.js @@ -1,9 +1,10 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var ParameterNode = require(__dirname + '/parameter'); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var AliasNode = require('./alias'); +var Node = require('./'); +var ParameterNode = require('./parameter'); +var valueExpressionMixin = require('./valueExpression'); var FunctionCallNode = Node.define({ type: 'FUNCTION CALL', @@ -18,7 +19,6 @@ var FunctionCallNode = Node.define({ _.extend(FunctionCallNode.prototype, valueExpressionMixin()); // allow aliasing -var AliasNode = require(__dirname + '/alias'); _.extend(FunctionCallNode.prototype, AliasNode.AliasMixin); module.exports = FunctionCallNode; diff --git a/lib/node/groupBy.js b/lib/node/groupBy.js index 6e5888d1..12287567 100644 --- a/lib/node/groupBy.js +++ b/lib/node/groupBy.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'GROUP BY' diff --git a/lib/node/having.js b/lib/node/having.js index 7dcf3434..5f04c4f7 100644 --- a/lib/node/having.js +++ b/lib/node/having.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'HAVING' diff --git a/lib/node/ifExists.js b/lib/node/ifExists.js index 9b759779..1d77d319 100644 --- a/lib/node/ifExists.js +++ b/lib/node/ifExists.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'IF EXISTS' diff --git a/lib/node/ifNotExists.js b/lib/node/ifNotExists.js index ef414551..bd001ae1 100644 --- a/lib/node/ifNotExists.js +++ b/lib/node/ifNotExists.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'IF NOT EXISTS' diff --git a/lib/node/index.js b/lib/node/index.js index 74e240d0..684624f4 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -14,7 +14,7 @@ Node.prototype.toNode = function() { }; Node.prototype.add = function(node) { - assert(node, 'Error while trying to add a non-existant node to a query'); + assert(node, 'Error while trying to add a non-existent node to a query'); this.nodes.push(typeof node === 'string' ? new TextNode(node) : node.toNode()); return this; }; @@ -50,7 +50,7 @@ Node.prototype.toString = function(dialect) { }; Node.prototype.addAll = function(nodes) { - for(var i = 0, len = nodes.length; i < len; i++) { + for (var i = 0, len = nodes.length; i < len; i++) { this.add(nodes[i]); } return this; diff --git a/lib/node/insert.js b/lib/node/insert.js index 7232a0d7..014af15d 100644 --- a/lib/node/insert.js +++ b/lib/node/insert.js @@ -1,33 +1,42 @@ 'use strict'; +var _ = require('lodash'); var DefaultNode = require('./default'); var Node = require('./'); var ParameterNode = require('./parameter'); var Insert = Node.define({ type: 'INSERT', - constructor: function () { + constructor: function(other) { Node.call(this); - this.names = []; - this.columns = []; - this.valueSets = []; + + if (other && other.type === 'INSERT') { + // copy constructor + this.names = _.clone(other.names); + this.columns = _.clone(other.columns); + this.valueSets = _.clone(other.valueSets); + } else { + this.names = []; + this.columns = []; + this.valueSets = []; + } } }); module.exports = Insert; -Insert.prototype.add = function (nodes) { +Insert.prototype.add = function(nodes) { var hasColumns = false; var hasValues = false; - var self = this; + var mutated = new Insert(this); var values = {}; - nodes.forEach(function (node) { + nodes.forEach(function(node) { var column = node.toNode(); var name = column.name; - var idx = self.names.indexOf(name); + var idx = mutated.names.indexOf(name); if (idx < 0) { - self.names.push(name); - self.columns.push(column); + mutated.names.push(name); + mutated.columns.push(column); } hasColumns = true; hasValues = hasValues || column.value !== undefined; @@ -39,24 +48,23 @@ Insert.prototype.add = function (nodes) { // later. Resolve the ambiguity by assuming that if no columns are specified // it is a row of default values, otherwise a SELECT will be added. if (hasValues || !hasColumns) { - this.valueSets.push(values); + mutated.valueSets.push(values); } - return self; + return mutated; }; /* - * Get parameters for all values to be inserted. This function - * handles handles bulk inserts, where keys may be present + * Get parameters for all values to be inserted. This function * handles handles bulk inserts, where keys may be present * in some objects and not others. When keys are not present, * the insert should refer to the column value as DEFAULT. */ -Insert.prototype.getParameters = function () { +Insert.prototype.getParameters = function() { var self = this; return this.valueSets - .map(function (nodeDict) { + .map(function(nodeDict) { var set = []; - self.names.forEach(function (name) { + self.names.forEach(function(name) { var node = nodeDict[name]; if (node) { set.push(new ParameterNode(node.value)); diff --git a/lib/node/join.js b/lib/node/join.js index a16067e6..b779e582 100644 --- a/lib/node/join.js +++ b/lib/node/join.js @@ -5,17 +5,31 @@ var JoinNode = module.exports = Node.define({ type: 'JOIN', constructor: function(subType, from, to) { Node.call(this); - this.subType = subType; - this.from = from.toNode(); - this.to = to.toNode(); + + if (subType.type === 'JOIN') { + // implement copy constructor + var other = subType; + this.subType = other.subType; + this.from = other.from; + this.to = other.to; + this.on = other.on; + } else { + this.subType = subType; + this.from = from.toNode(); + this.to = to.toNode(); + } }, + on: function(node) { - this.on = node; - return this; + var mutated = new JoinNode(this); + mutated.on = node; + return mutated; }, + join: function(other) { return new JoinNode('INNER', this, other); }, + leftJoin: function(other) { return new JoinNode('LEFT', this, other); } diff --git a/lib/node/orderBy.js b/lib/node/orderBy.js index 6db2a978..620f149e 100644 --- a/lib/node/orderBy.js +++ b/lib/node/orderBy.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'ORDER BY' diff --git a/lib/node/orderByValue.js b/lib/node/orderByValue.js index 4ac7fd8a..99e2f04a 100644 --- a/lib/node/orderByValue.js +++ b/lib/node/orderByValue.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); var OrderByColumn = Node.define({ type: 'ORDER BY VALUE', diff --git a/lib/node/parameter.js b/lib/node/parameter.js index 7b3a8a20..5af1ad72 100644 --- a/lib/node/parameter.js +++ b/lib/node/parameter.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); var ParameterNode = module.exports = Node.define({ type: 'PARAMETER', diff --git a/lib/node/postfixUnary.js b/lib/node/postfixUnary.js index 0c132f8a..d6831740 100644 --- a/lib/node/postfixUnary.js +++ b/lib/node/postfixUnary.js @@ -1,28 +1,16 @@ 'use strict'; -var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var UnaryNode = require('./unary'); +var util = require('util'); -var valueExpressionMixed = false; -var PostfixUnaryNode = Node.define({ - type: 'POSTFIX UNARY', - constructor: function(config) { - Node.call(this); - this.left = config.left; - this.operator = config.operator; +var PostfixUnaryNode = function() { + PostfixUnaryNode.super_.apply(this, arguments); +}; - // Delay mixin to runtime, when all nodes have been defined, and - // mixin only once. ValueExpressionMixin has circular dependencies. - if (!valueExpressionMixed) { - valueExpressionMixed = true; - _.extend(PostfixUnaryNode.prototype, valueExpressionMixin()); - } - } -}); +// inherit from unary node +util.inherits(PostfixUnaryNode, UnaryNode); -// allow aliasing -var AliasNode = require(__dirname + '/alias'); -_.extend(PostfixUnaryNode.prototype, AliasNode.AliasMixin); +// set the type only after inheriting from unary node +PostfixUnaryNode.prototype.type = 'POSTFIX UNARY'; module.exports = PostfixUnaryNode; diff --git a/lib/node/prefixUnary.js b/lib/node/prefixUnary.js index 67a59fa1..32642cfe 100644 --- a/lib/node/prefixUnary.js +++ b/lib/node/prefixUnary.js @@ -1,28 +1,16 @@ 'use strict'; -var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var UnaryNode = require('./unary'); +var util = require('util'); -var valueExpressionMixed = false; -var PrefixUnaryNode = Node.define({ - type: 'PREFIX UNARY', - constructor: function(config) { - Node.call(this); - this.left = config.left; - this.operator = config.operator; +var PrefixUnaryNode = function() { + PrefixUnaryNode.super_.apply(this, arguments); +}; - // Delay mixin to runtime, when all nodes have been defined, and - // mixin only once. ValueExpressionMixin has circular dependencies. - if (!valueExpressionMixed) { - valueExpressionMixed = true; - _.extend(PrefixUnaryNode.prototype, valueExpressionMixin()); - } - } -}); +// inherit from unary node +util.inherits(PrefixUnaryNode, UnaryNode); -// allow aliasing -var AliasNode = require(__dirname + '/alias'); -_.extend(PrefixUnaryNode.prototype, AliasNode.AliasMixin); +// set the type only after inheriting from unary node +PrefixUnaryNode.prototype.type = 'PREFIX UNARY'; module.exports = PrefixUnaryNode; diff --git a/lib/node/query.js b/lib/node/query.js index 6ef0daf0..edc9561e 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -1,5 +1,6 @@ 'use strict'; +var _ = require('lodash'); var assert = require('assert'); var sliced = require('sliced'); var util = require('util'); @@ -39,6 +40,10 @@ var Modifier = Node.define({ } }); +var ensureSubquery = function(node, methodName) { + assert(node.type === 'SUBQUERY', methodName + ' can only be used on a subQuery'); +}; + // get the first element of an arguments if it is an array, else return arguments as an array var getArrayOrArgsAsArray = function(args) { if (util.isArray(args[0])) { @@ -56,6 +61,7 @@ var Query = Node.define({ this.sql = table.sql; } }, + select: function() { var select; if (this._select) { @@ -82,17 +88,20 @@ var Query = Node.define({ } return this; }, + star: function() { - assert(this.type === 'SUBQUERY', 'star() can only be used on a subQuery'); + ensureSubquery(this, 'star()'); return new Column({ table: this, star: true }); }, + from: function(tableNode) { var from = new From().add(tableNode); return this.add(from); }, + where: function(node) { if (arguments.length > 1) { // allow multiple where clause arguments @@ -107,17 +116,24 @@ var Query = Node.define({ return this.and(node); } this.whereClause = new Where(this.table); - this.whereClause.add(node); + this.whereClause = this.whereClause.add(node); return this.add(this.whereClause); }, + or: function(node) { - this.whereClause.or(node); + var index = _.indexOf(this.nodes, this.whereClause); + this.whereClause = this.whereClause.or(node); + this.nodes[index] = this.whereClause; return this; }, + and: function(node) { - this.whereClause.and(node); + var index = _.indexOf(this.nodes, this.whereClause); + this.whereClause = this.whereClause.and(node); + this.nodes[index] = this.whereClause; return this; }, + order: function() { var args = getArrayOrArgsAsArray(arguments); var orderBy; @@ -130,16 +146,19 @@ var Query = Node.define({ orderBy.addAll(args); return this; }, + group: function() { var args = getArrayOrArgsAsArray(arguments); var groupBy = new GroupBy().addAll(args); return this.add(groupBy); }, + having: function() { var args = getArrayOrArgsAsArray(arguments); var having = new Having().addAll(args); return this.add(having); }, + insert: function(o) { var self = this; @@ -157,15 +176,17 @@ var Query = Node.define({ } if (self.insertClause) { - self.insertClause.add(args); + self.insertClause = self.insertClause.add(args); + self.nodes[0] = self.insertClause; return self; } else { self.insertClause = new Insert(); - self.insertClause.add(args); + self.insertClause = self.insertClause.add(args); return self.add(self.insertClause); } }, + update: function(o) { var self = this; var update = new Update(); @@ -175,6 +196,7 @@ var Query = Node.define({ }); return this.add(update); }, + delete: function(params) { var result = this.add(new Delete()); if (params) { @@ -182,6 +204,7 @@ var Query = Node.define({ } return result; }, + returning: function() { var args = getArrayOrArgsAsArray(arguments); var returning = new Returning(); @@ -226,6 +249,7 @@ var Query = Node.define({ this.nodes[0].add(renameClause); return this; }, + addColumn: function(column, dataType) { var addClause = new AddColumn(); if (!column.toNode) { @@ -241,6 +265,7 @@ var Query = Node.define({ this.nodes[0].add(addClause); return this; }, + dropColumn: function(column) { var dropClause = new DropColumn(); if (!column.toNode) { @@ -283,7 +308,7 @@ var Query = Node.define({ }, exists: function() { - assert(this.type === 'SUBQUERY', 'exists() can only be used on a subQuery'); + ensureSubquery(this, 'exists()'); return new PrefixUnaryNode({ left: this, operator: "EXISTS" @@ -291,7 +316,7 @@ var Query = Node.define({ }, notExists: function() { - assert(this.type === 'SUBQUERY', 'notExists() can only be used on a subQuery'); + ensureSubquery(this, 'notExists()'); return new PrefixUnaryNode({ left: this, operator: "NOT EXISTS" diff --git a/lib/node/select.js b/lib/node/select.js index db3f3230..7bef57f4 100644 --- a/lib/node/select.js +++ b/lib/node/select.js @@ -1,13 +1,13 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'SELECT', constructor: function(arg) { Node.call(this); - if (arg && arg.sql) { - this.sql = arg.sql; + if (arg && arg.sql) { + this.sql = arg.sql; } - } + } }); diff --git a/lib/node/table.js b/lib/node/table.js index b1257105..255c8077 100644 --- a/lib/node/table.js +++ b/lib/node/table.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'TABLE', constructor: function(table) { diff --git a/lib/node/ternary.js b/lib/node/ternary.js index 1ef5ce7b..79d46e59 100644 --- a/lib/node/ternary.js +++ b/lib/node/ternary.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var TernaryNode = Node.define(_.extend({ @@ -25,7 +25,7 @@ var TernaryNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(TernaryNode.prototype, AliasNode.AliasMixin); module.exports = TernaryNode; diff --git a/lib/node/text.js b/lib/node/text.js index ef1194b2..25106c7f 100644 --- a/lib/node/text.js +++ b/lib/node/text.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./'); module.exports = Node.define({ type: 'TEXT', diff --git a/lib/node/unary.js b/lib/node/unary.js new file mode 100644 index 00000000..53f44e77 --- /dev/null +++ b/lib/node/unary.js @@ -0,0 +1,30 @@ +'use strict'; + +var _ = require('lodash'); +var Node = require('./'); +var valueExpressionMixin = require('./valueExpression'); + +var valueExpressionMixed = false; + +// parent class of prefix and postfix unary +var UnaryNode = Node.define({ + type: 'UNARY', + constructor: function(config) { + Node.call(this); + this.left = config.left; + this.operator = config.operator; + + // Delay mixin to runtime, when all nodes have been defined, and + // mixin only once. ValueExpressionMixin has circular dependencies. + if (!valueExpressionMixed) { + valueExpressionMixed = true; + _.extend(UnaryNode.prototype, valueExpressionMixin()); + } + } +}); + +// allow aliasing +var AliasNode = require('./alias'); +_.extend(UnaryNode.prototype, AliasNode.AliasMixin); + +module.exports = UnaryNode; diff --git a/lib/node/valueExpression.js b/lib/node/valueExpression.js index afe421ad..27fc7a91 100644 --- a/lib/node/valueExpression.js +++ b/lib/node/valueExpression.js @@ -19,8 +19,7 @@ var ValueExpressionMixin = function() { var TernaryNode = require('./ternary'); var postfixUnaryMethod = function(operator) { - /*jshint unused: false */ - return function(val) { + return function() { return new PostfixUnaryNode({ left: this.toNode(), operator: operator diff --git a/lib/node/where.js b/lib/node/where.js index b7f2be1b..1a698f65 100644 --- a/lib/node/where.js +++ b/lib/node/where.js @@ -1,15 +1,15 @@ 'use strict'; -var Node = require(__dirname); -var BinaryNode = require(__dirname + '/binary'); -var TextNode = require(__dirname + '/text'); +var _ = require('lodash'); +var BinaryNode = require('./binary'); +var Node = require('./'); +var TextNode = require('./text'); var normalizeNode = function(table, node) { var result = node; - if(typeof node === 'string') { + if (typeof node === 'string') { result = new TextNode('(' + node + ')'); - } - else if (!node.toNode && typeof node === 'object'){ + } else if (!node.toNode && typeof node === 'object'){ result = false; for (var colName in node) { if (node.hasOwnProperty(colName)) { @@ -26,34 +26,49 @@ var normalizeNode = function(table, node) { return result; }; -module.exports = Node.define({ +var Where = Node.define({ + type: 'WHERE', constructor: function(table) { Node.call(this); - this.table = table; + + if (table.type === 'WHERE') { + // copy constructor + var other = table; + this.nodes = _.clone(other.nodes); + this.table = other.table; + } else { + this.table = table; + } }, - type: 'WHERE', add: function(node) { node = normalizeNode(this.table, node); - return Node.prototype.add.call(this, node); + var mutated = new Where(this); + return Node.prototype.add.call(mutated, node); }, or: function(other) { var right = normalizeNode(this.table, other); + var mutated = new Where(this); // calling 'or' without an initial 'where' - if(!this.nodes.length) { - return this.add(other); + if (!mutated.nodes.length) { + return mutated.add(other); } - return this.nodes.push(new BinaryNode({ - left: this.nodes.pop(), + mutated.nodes.push(new BinaryNode({ + left: mutated.nodes.pop(), operator: 'OR', right: right })); + return mutated; }, and: function(other) { var right = normalizeNode(this.table, other); - return this.nodes.push(new BinaryNode({ - left: this.nodes.pop(), + var mutated = new Where(this); + mutated.nodes.push(new BinaryNode({ + left: mutated.nodes.pop(), operator: 'AND', right: right })); + return mutated; } }); + +module.exports = Where; diff --git a/lib/table.js b/lib/table.js index 68b801ea..8c78a0dc 100644 --- a/lib/table.js +++ b/lib/table.js @@ -160,9 +160,9 @@ Table.prototype.update = function() { return query; }; -Table.prototype['delete'] = function() { +Table.prototype.delete = function() { var query = new Query(this); - query['delete'].apply(query, arguments); + query.delete.apply(query, arguments); return query; }; diff --git a/test/dialects/unary-clause-tests.js b/test/dialects/unary-clause-tests.js index e7f6e3bd..95ded8ab 100644 --- a/test/dialects/unary-clause-tests.js +++ b/test/dialects/unary-clause-tests.js @@ -1,8 +1,10 @@ 'use strict'; var Harness = require('./support'); +var Table = require('../../lib/table'); + var customer = Harness.defineCustomerTable(); -var post = Harness.definePostTable(); +var post = Harness.definePostTable(); Harness.test({ query: customer.select().where(customer.age.isNotNull()), @@ -22,7 +24,7 @@ Harness.test({ }); Harness.test({ - query: post.select().where(post.userId. in (customer.subQuery().select(customer.id).where(customer.age.isNull()))), + query: post.select().where(post.userId.in(customer.subQuery().select(customer.id).where(customer.age.isNull()))), pg: { text : 'SELECT "post".* FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer" WHERE ("customer"."age" IS NULL)))', string: 'SELECT "post".* FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer" WHERE ("customer"."age" IS NULL)))' diff --git a/test/immutability-tests.js b/test/immutability-tests.js new file mode 100644 index 00000000..734ae543 --- /dev/null +++ b/test/immutability-tests.js @@ -0,0 +1,57 @@ +/* global describe, it */ +'use strict'; +var assert = require('assert'); + +var ColumnNode = require('../lib/node/column'); +var CreateIndexNode = require('../lib/node/createIndex'); + +describe('ColumnNode', function() { + var original = new ColumnNode({}); + var originalJSON = JSON.stringify(original); + var checkUnchanged = function() { + assert.equal(JSON.stringify(original), originalJSON); + }; + + it('#as immutable', function() { + assert.notEqual(original, original.as('')); + checkUnchanged(); + }); +}); + +describe('CreateIndexNode', function() { + var original = new CreateIndexNode('table', 'name'); + var originalJSON = JSON.stringify(original); + var checkUnchanged = function() { + assert.equal(JSON.stringify(original), originalJSON); + }; + + it('#unique immutable', function() { + assert.notEqual(original, original.unique()); + checkUnchanged(); + }); + + it('#spatial immutable', function() { + assert.notEqual(original, original.spatial()); + checkUnchanged(); + }); + + it('#fulltext immutable', function() { + assert.notEqual(original, original.fulltext()); + checkUnchanged(); + }); + + it('#using immutable', function() { + assert.notEqual(original, original.using('')); + checkUnchanged(); + }); + + it('#on immutable', function() { + assert.notEqual(original, original.on('')); + checkUnchanged(); + }); + + it('#withParser immutable', function() { + assert.notEqual(original, original.withParser('')); + checkUnchanged(); + }); +}); diff --git a/test/query-tests.js b/test/query-tests.js new file mode 100644 index 00000000..5918ea7f --- /dev/null +++ b/test/query-tests.js @@ -0,0 +1,31 @@ +/* global test */ +'use strict'; + +var assert = require('assert'); +var Harness = require('./dialects/support'); + +var table = Harness.defineCustomerTable(); +var query = table.select(); +var subQuery = table.subQuery(); + +var subquerySpecificFunctions = [ + 'star', + 'exists', + 'notExists' +]; + +subquerySpecificFunctions.forEach(function(name) { + test('function ' + name + ' exists', function() { + assert.equal(typeof query[name], 'function'); + }); + + test('cannot use ' + name + ' without subQuery', function() { + assert.throws(function() { + query[name](); + }); + }); + + test('can use ' + name + ' with subQuery', function() { + assert(subQuery[name]()); + }); +}); diff --git a/test/select-tests.js b/test/select-tests.js index a0befc4c..cd8c4072 100644 --- a/test/select-tests.js +++ b/test/select-tests.js @@ -9,7 +9,6 @@ test('has SELECT type', function() { assert.equal(select.type, 'SELECT'); }); - test('can go toQuery', function() { assert.equal(select.toQuery().text, 'SELECT '); }); diff --git a/test/unary-clause-tests.js b/test/unary-clause-tests.js index f5b36a1b..fa589bc2 100644 --- a/test/unary-clause-tests.js +++ b/test/unary-clause-tests.js @@ -1,7 +1,10 @@ 'use strict'; -var assert = require('assert'); -var Table = require(__dirname + '/../lib/table'); +var assert = require('assert'); +var PostfixUnary = require('../lib/node/postfixUnary'); +var PrefixUnary = require('../lib/node/prefixUnary'); +var Table = require('../lib/table'); +var Unary = require('../lib/node/unary'); var Foo = Table.define({ name: 'foo', @@ -12,3 +15,27 @@ test('operators', function() { assert.equal(Foo.bar.isNull().operator, 'IS NULL'); assert.equal(Foo.baz.isNotNull().operator, 'IS NOT NULL'); }); + +test('prefix unary', function() { + var preUnary = new PrefixUnary({left: 'hello', operator: 'secret'}); + + assert(preUnary instanceof Unary); + assert(preUnary instanceof PrefixUnary); + assert(!(preUnary instanceof PostfixUnary)); + + assert.equal(preUnary.type, 'PREFIX UNARY'); + assert.equal(preUnary.left, 'hello'); + assert.equal(preUnary.operator, 'secret'); +}); + +test('postfix unary', function() { + var postUnary = new PostfixUnary({left: 'world', operator: 'noop'}); + + assert(postUnary instanceof Unary); + assert(postUnary instanceof PostfixUnary); + assert(!(postUnary instanceof PrefixUnary)); + + assert.equal(postUnary.type, 'POSTFIX UNARY'); + assert.equal(postUnary.left, 'world'); + assert.equal(postUnary.operator, 'noop'); +});