From 244aa3b04049f59964915a28bca7f9a35359cfdb Mon Sep 17 00:00:00 2001 From: Di Wu Date: Wed, 26 Jun 2013 15:43:35 -0700 Subject: [PATCH 1/8] refactor out subquery condition check, added test for subquery specific functions --- lib/dialect/postgres.js | 8 ++--- lib/node/query.js | 80 +++++++++++++++++++++++++---------------- test/query-tests.js | 31 ++++++++++++++++ test/select-tests.js | 1 - 4 files changed, 84 insertions(+), 36 deletions(-) create mode 100644 test/query-tests.js diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index ab375e5b..3e3b4abf 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -130,10 +130,10 @@ Postgres.prototype.visit = function(node) { case 'DROP INDEX' : return this.visitDropIndex(node); case 'FUNCTION CALL' : return this.visitFunctionCall(node); - case 'POSTFIX UNARY' : return this.visitPostfixUnary(node); - case 'PREFIX UNARY' : return this.visitPrefixUnary(node); - case 'BINARY' : return this.visitBinary(node); - case 'TERNARY' : return this.visitTernary(node); + case 'POSTFIX UNARY' : return this.visitPostfixUnary(node); + case 'PREFIX UNARY' : return this.visitPrefixUnary(node); + case 'BINARY' : return this.visitBinary(node); + case 'TERNARY' : return this.visitTernary(node); case 'LIMIT' : case 'OFFSET': diff --git a/lib/node/query.js b/lib/node/query.js index d640acf3..ee06fa5e 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -1,36 +1,35 @@ 'use strict'; var assert = require('assert'); -var util = require('util'); - var sliced = require('sliced'); +var util = require('util'); -var Node = require(__dirname); -var Select = require(__dirname + '/select'); -var From = require(__dirname + '/from'); -var Where = require(__dirname + '/where'); -var OrderBy = require(__dirname + '/orderBy'); -var GroupBy = require(__dirname + '/groupBy'); -var Having = require(__dirname + '/having'); -var Insert = require(__dirname + '/insert'); -var Update = require(__dirname + '/update'); -var Delete = require(__dirname + '/delete'); -var Returning = require(__dirname + '/returning'); -var Create = require(__dirname + '/create'); -var Drop = require(__dirname + '/drop'); -var Alter = require(__dirname + '/alter'); -var AddColumn = require(__dirname + '/addColumn'); -var DropColumn = require(__dirname + '/dropColumn'); -var RenameColumn = require(__dirname + '/renameColumn'); -var Rename = require(__dirname + '/rename'); -var Column = require(__dirname + '/../column'); -var ParameterNode = require(__dirname + '/parameter'); -var PrefixUnaryNode = require(__dirname + '/prefixUnary'); -var IfExists = require(__dirname + '/ifExists'); -var IfNotExists = require(__dirname + '/ifNotExists'); -var Indexes = require(__dirname + '/indexes'); -var CreateIndex = require(__dirname + '/createIndex'); -var DropIndex = require(__dirname + '/dropIndex'); +var Node = require('./'); +var Select = require('./select'); +var From = require('./from'); +var Where = require('./where'); +var OrderBy = require('./orderBy'); +var GroupBy = require('./groupBy'); +var Having = require('./having'); +var Insert = require('./insert'); +var Update = require('./update'); +var Delete = require('./delete'); +var Returning = require('./returning'); +var Create = require('./create'); +var Drop = require('./drop'); +var Alter = require('./alter'); +var AddColumn = require('./addColumn'); +var DropColumn = require('./dropColumn'); +var RenameColumn = require('./renameColumn'); +var Rename = require('./rename'); +var Column = require('../column'); +var ParameterNode = require('./parameter'); +var PrefixUnaryNode = require('./prefixUnary'); +var IfExists = require('./ifExists'); +var IfNotExists = require('./ifNotExists'); +var Indexes = require('./indexes'); +var CreateIndex = require('./createIndex'); +var DropIndex = require('./dropIndex'); var Modifier = Node.define({ constructor: function(table, type, count) { @@ -40,6 +39,10 @@ var Modifier = Node.define({ } }); +var ensureSubquery = function(node, methodName) { + assert(node.type === 'SUBQUERY', methodName + ' can only be used on a subQuery'); +}; + var Query = Node.define({ type: 'QUERY', constructor: function(table) { @@ -47,6 +50,7 @@ var Query = Node.define({ this.table = table; if (table) this.sql = table.sql; }, + select: function() { var select; if(this._select) { @@ -74,14 +78,17 @@ 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 @@ -97,14 +104,17 @@ var Query = Node.define({ this.whereClause.add(node); return this.add(this.whereClause); }, + or: function(node) { this.whereClause.or(node); return this; }, + and: function(node) { this.whereClause.and(node); return this; }, + order: function() { var args = sliced(arguments); if(util.isArray(args[0])) { @@ -113,6 +123,7 @@ var Query = Node.define({ var orderBy = new OrderBy().addAll(args); return this.add(orderBy); }, + group: function() { var args = sliced(arguments); if(util.isArray(args[0])) { @@ -121,6 +132,7 @@ var Query = Node.define({ var groupBy = new GroupBy().addAll(args); return this.add(groupBy); }, + having: function() { var args = sliced(arguments); if (util.isArray(args[0])) { @@ -129,6 +141,7 @@ var Query = Node.define({ var having = new Having().addAll(args); return this.add(having); }, + insert: function(o) { var self = this; @@ -156,6 +169,7 @@ var Query = Node.define({ } }, + update: function(o) { var self = this; var update = new Update(); @@ -165,6 +179,7 @@ var Query = Node.define({ }); return this.add(update); }, + delete: function(params) { var result = this.add(new Delete()); if(params) { @@ -172,6 +187,7 @@ var Query = Node.define({ } return result; }, + returning: function() { var returning = new Returning(); var args = sliced(arguments); @@ -215,6 +231,7 @@ var Query = Node.define({ this.nodes[0].add(renameClause); return this; }, + addColumn: function(column, dataType) { var addClause = new AddColumn(); if (!column.toNode) @@ -225,6 +242,7 @@ var Query = Node.define({ this.nodes[0].add(addClause); return this; }, + dropColumn: function(column) { var dropClause = new DropColumn(); if (!column.toNode) @@ -255,7 +273,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" @@ -263,7 +281,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/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 af8db6d2..b743b2b7 100644 --- a/test/select-tests.js +++ b/test/select-tests.js @@ -10,7 +10,6 @@ test('has SELECT type', function() { assert.equal(select.type, 'SELECT'); }); - test('can go toQuery', function() { assert.equal(select.toQuery().text, 'SELECT '); }); From e12da4c5cac957946f6f6ade4ccaa1f2a4244c02 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Wed, 26 Jun 2013 16:38:50 -0700 Subject: [PATCH 2/8] add Unary node back in, make PrefixUnary and PostfixUnary inherit from vanilla Unary --- lib/node/postfixUnary.js | 30 +++++++++------------------- lib/node/prefixUnary.js | 30 +++++++++------------------- lib/node/unary.js | 30 ++++++++++++++++++++++++++++ lib/node/valueExpression.js | 13 ++++++------ test/dialects/unary-clause-tests.js | 7 ++++--- test/unary-clause-tests.js | 31 +++++++++++++++++++++++++++-- 6 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 lib/node/unary.js 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/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 cc8bad4e..74a75c1d 100644 --- a/lib/node/valueExpression.js +++ b/lib/node/valueExpression.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var ParameterNode = require(__dirname + '/parameter'); +var Node = require('./'); +var ParameterNode = require('./parameter'); // Process values, wrapping them in ParameterNode if necessary. var processParams = function(val) { @@ -13,13 +13,12 @@ var processParams = function(val) { // ValueExpressionMixin is evaluated at runtime, hence the // "thunk" around it. var ValueExpressionMixin = module.exports = function() { - var PostfixUnaryNode = require(__dirname + '/postfixUnary'); - var BinaryNode = require(__dirname + '/binary'); - var TernaryNode = require(__dirname + '/ternary'); + var PostfixUnaryNode = require('./postfixUnary'); + var BinaryNode = require('./binary'); + 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/test/dialects/unary-clause-tests.js b/test/dialects/unary-clause-tests.js index 8c26c624..95ded8ab 100644 --- a/test/dialects/unary-clause-tests.js +++ b/test/dialects/unary-clause-tests.js @@ -1,9 +1,10 @@ 'use strict'; var Harness = require('./support'); +var Table = require('../../lib/table'); + var customer = Harness.defineCustomerTable(); -var post = Harness.definePostTable(); -var Table = require(__dirname + '/../../lib/table'); +var post = Harness.definePostTable(); Harness.test({ query: customer.select().where(customer.age.isNotNull()), @@ -23,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/unary-clause-tests.js b/test/unary-clause-tests.js index 745eb20f..ad64166f 100644 --- a/test/unary-clause-tests.js +++ b/test/unary-clause-tests.js @@ -1,8 +1,11 @@ /* global test */ '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', @@ -13,3 +16,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'); +}); From 0ba3f6b055b262e1893d25919ece7b12ec3e4e92 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Sun, 30 Jun 2013 19:22:05 -0700 Subject: [PATCH 3/8] making ColumnNode immutable --- lib/node/alias.js | 4 ++-- lib/node/binary.js | 4 ++-- lib/node/column.js | 28 ++++++++++++++++------------ test/immutability-tests.js | 13 +++++++++++++ 4 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 test/immutability-tests.js 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/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 9216d1bc..8fa0909d 100644 --- a/lib/node/column.js +++ b/lib/node/column.js @@ -1,23 +1,27 @@ '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; }, 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/test/immutability-tests.js b/test/immutability-tests.js new file mode 100644 index 00000000..409d0a88 --- /dev/null +++ b/test/immutability-tests.js @@ -0,0 +1,13 @@ +/* global test */ +'use strict'; +var assert = require('assert'); + +var ColumnNode = require('../lib/node/column'); + +test('ColumNode immutable', function() { + var original = new ColumnNode({}); + var aliased = original.as('something else'); + + assert.notEqual(original, aliased); + assert.equal(aliased.alias, 'something else'); +}); From 5f9413d26a432f1754fce88d762ec6fdd3e218b8 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Sun, 30 Jun 2013 20:43:50 -0700 Subject: [PATCH 4/8] making CreateIndexNode immutable --- lib/node/createIndex.js | 56 ++++++++++++++++++++++++++++---------- test/immutability-tests.js | 54 ++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/lib/node/createIndex.js b/lib/node/createIndex.js index 2384ab8e..50f00bf7 100644 --- a/lib/node/createIndex.js +++ b/lib/node/createIndex.js @@ -1,47 +1,73 @@ '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, + 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/test/immutability-tests.js b/test/immutability-tests.js index 409d0a88..734ae543 100644 --- a/test/immutability-tests.js +++ b/test/immutability-tests.js @@ -1,13 +1,57 @@ -/* global test */ +/* global describe, it */ 'use strict'; var assert = require('assert'); var ColumnNode = require('../lib/node/column'); +var CreateIndexNode = require('../lib/node/createIndex'); -test('ColumNode immutable', function() { +describe('ColumnNode', function() { var original = new ColumnNode({}); - var aliased = original.as('something else'); + var originalJSON = JSON.stringify(original); + var checkUnchanged = function() { + assert.equal(JSON.stringify(original), originalJSON); + }; - assert.notEqual(original, aliased); - assert.equal(aliased.alias, 'something else'); + 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(); + }); }); From 60805c1e5f13965cf5eeb9bbd9f82d93411de97b Mon Sep 17 00:00:00 2001 From: Di Wu Date: Sun, 30 Jun 2013 21:01:44 -0700 Subject: [PATCH 5/8] refactor away some of the __dirname --- lib/node/addColumn.js | 2 +- lib/node/alter.js | 2 +- lib/node/create.js | 2 +- lib/node/default.js | 4 +++- lib/node/delete.js | 2 +- lib/node/drop.js | 2 +- lib/node/from.js | 2 +- lib/node/functionCall.js | 8 ++++---- lib/node/groupBy.js | 2 +- lib/node/having.js | 2 +- lib/node/ifExists.js | 2 +- lib/node/ifNotExists.js | 2 +- 12 files changed, 17 insertions(+), 15 deletions(-) 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/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/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/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/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' From b449d2313f2d9cfaa8fa8230fbecc128687ea6ae Mon Sep 17 00:00:00 2001 From: Di Wu Date: Sat, 31 Aug 2013 15:42:02 -0700 Subject: [PATCH 6/8] make insert clause immutable --- lib/node/createIndex.js | 1 + lib/node/dropColumn.js | 2 +- lib/node/index.js | 4 ++-- lib/node/insert.js | 42 ++++++++++++++++++++++++----------------- lib/node/query.js | 5 +++-- lib/table.js | 4 ++-- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/lib/node/createIndex.js b/lib/node/createIndex.js index 50f00bf7..197ad81a 100644 --- a/lib/node/createIndex.js +++ b/lib/node/createIndex.js @@ -16,6 +16,7 @@ var CreateIndexNode = module.exports = Node.define({ 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, 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/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/query.js b/lib/node/query.js index 66fd8f92..943ea3c7 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -171,11 +171,12 @@ 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); } 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; }; From 9f97a705bc7ec51952896ddfdf09750224df4320 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Sat, 31 Aug 2013 15:46:44 -0700 Subject: [PATCH 7/8] make join node immutable --- lib/node/join.js | 24 +++++++++++++++++++----- lib/node/orderBy.js | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) 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' From e938ea3743b97da944b504e1063324580a5d4a52 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Sat, 31 Aug 2013 16:16:39 -0700 Subject: [PATCH 8/8] make where clause immutable --- lib/node/orderByValue.js | 2 +- lib/node/parameter.js | 2 +- lib/node/query.js | 11 +++++++--- lib/node/select.js | 8 +++---- lib/node/table.js | 2 +- lib/node/ternary.js | 6 ++--- lib/node/text.js | 2 +- lib/node/where.js | 47 ++++++++++++++++++++++++++-------------- 8 files changed, 50 insertions(+), 30 deletions(-) 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/query.js b/lib/node/query.js index 943ea3c7..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'); @@ -115,17 +116,21 @@ 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; }, 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/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;