From 2ccd60eabb4fa2af89f37cc3752dd7351b4e8844 Mon Sep 17 00:00:00 2001 From: Benjamin Diemert Date: Thu, 16 Apr 2015 18:24:55 +0200 Subject: [PATCH 01/62] Define specific constraint on references column (Postgres only) When you're defining columns in a table that references others columns in you database, you can now add a specific constraint such as "ON DELETE CASCADE". The definition will looks like : references: { table: "another_table", column: "another_table_primary_key", constraint: "ON DELETE CASCADE" } --- lib/dialect/postgres.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index cd560aac..0d4652cb 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -730,8 +730,12 @@ Postgres.prototype.visitColumn = function(columnNode) { columnNode.name + ' (REFERENCES statements within CREATE TABLE and ADD COLUMN statements' + ' require a table and column)'); - txt.push(' REFERENCES ' + columnNode.references.table + '(' + - columnNode.references.column + ')'); + txt.push(columnNode.references.hasOwnProperty('constraint') + ? ' REFERENCES ' + columnNode.references.table + '(' + + columnNode.references.column + ') ' + columnNode.references.constraint + : ' REFERENCES ' + columnNode.references.table + '(' + + columnNode.references.column + ')' + ); } } } From da3975cf67251b6dd180ee02361be59016598698 Mon Sep 17 00:00:00 2001 From: beedi Date: Wed, 30 Dec 2015 22:21:34 +0100 Subject: [PATCH 02/62] Added Tests for specific constraint on references column, e.g. DEFERRABLE --- test/dialects/create-table-tests.js | 79 +++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/dialects/create-table-tests.js b/test/dialects/create-table-tests.js index f9c3e2fa..798a077d 100644 --- a/test/dialects/create-table-tests.js +++ b/test/dialects/create-table-tests.js @@ -461,3 +461,82 @@ Harness.test({ params: [] }); +const users = Table.define({ + name: 'users', + columns: { + id: { + primaryKey: true, + dataType: 'int', + references: { + table: "entity", + column: "id", + constraint: "DEFERRABLE INITIALLY DEFERRED" + } + } + } +}); + +Harness.test({ + query: users.create(), + pg: { + text : 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', + string: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)' + }, + sqlite: { + text : 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', + string: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)' + }, + mysql: { + text : 'CREATE TABLE `users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`) DEFERRABLE INITIALLY DEFERRED)', + string: 'CREATE TABLE `users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`) DEFERRABLE INITIALLY DEFERRED)' + }, + mssql: { + text : 'CREATE TABLE [users] ([id] int PRIMARY KEY REFERENCES [entity]([id]) DEFERRABLE INITIALLY DEFERRED)', + string: 'CREATE TABLE [users] ([id] int PRIMARY KEY REFERENCES [entity]([id]) DEFERRABLE INITIALLY DEFERRED)' + }, + oracle: { + text : 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', + string: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)' + }, + params: [] +}); + +const noUsers = Table.define({ + name: 'no_users', + columns: { + id: { + primaryKey: true, + dataType: 'int', + references: { + table: "entity", + column: "id", + constraint: "" + } + } + } +}); + +Harness.test({ + query: noUsers.create(), + pg: { + text : 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))', + string: 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))' + }, + sqlite: { + text : 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))', + string: 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))' + }, + mysql: { + text : 'CREATE TABLE `no_users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`))', + string: 'CREATE TABLE `no_users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`))' + }, + mssql: { + text : 'CREATE TABLE [no_users] ([id] int PRIMARY KEY REFERENCES [entity]([id]))', + string: 'CREATE TABLE [no_users] ([id] int PRIMARY KEY REFERENCES [entity]([id]))' + }, + oracle: { + text : 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))', + string: 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))' + }, + params: [] +}); \ No newline at end of file From 642592efaa1c1a5738911b353e7ea81dca0af78e Mon Sep 17 00:00:00 2001 From: beedi Date: Wed, 30 Dec 2015 22:37:06 +0100 Subject: [PATCH 03/62] Fix typo in test --- test/dialects/create-table-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dialects/create-table-tests.js b/test/dialects/create-table-tests.js index 798a077d..160e7262 100644 --- a/test/dialects/create-table-tests.js +++ b/test/dialects/create-table-tests.js @@ -461,7 +461,7 @@ Harness.test({ params: [] }); -const users = Table.define({ +var users = Table.define({ name: 'users', columns: { id: { @@ -501,7 +501,7 @@ Harness.test({ params: [] }); -const noUsers = Table.define({ +var noUsers = Table.define({ name: 'no_users', columns: { id: { From 8af5b48679e86d4a8e055208705682ed1d498dcd Mon Sep 17 00:00:00 2001 From: brianc Date: Wed, 27 Jan 2016 11:42:31 -0600 Subject: [PATCH 04/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f63aac7..c6e2766f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.65.0", + "version": "0.66.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From e0bd0630f1fcaf628dec5f8e5c937312750e1e1a Mon Sep 17 00:00:00 2001 From: Eric Perry Date: Thu, 28 Jan 2016 17:22:59 -0500 Subject: [PATCH 05/62] Updated array tests that Barry changed and fixed insertion of arrays of objects in Postgres --- lib/dialect/postgres.js | 22 ++++++++++++++--- test/dialects/insert-tests.js | 45 +++++++++++++++++++++++++++++++++++ test/dialects/update-tests.js | 2 +- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 6dd4f8dc..0efad5bc 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -40,9 +40,25 @@ Postgres.prototype._getParameterValue = function(value) { value = this.quote(value, "'"); } else if ('object' === typeof value) { if (_.isArray(value)) { - // convert each element of the array - value = _.map(value, this._getParameterValue, this); - value = '(' + value.join(', ') + ')'; + if (this._myClass === Postgres) { + // naive check to see if this is an array of objects, which + // is handled differently than an array of primitives + if (value.length && 'object' === typeof value[0] && + !_.isFunction(value[0].toISOString) && + !_.isArray(value[0])) { + value = "'" + JSON.stringify(value) + "'"; + } else { + var self = this; + value = value.map(function (item) { + // In a Postgres array, strings must be double-quoted + return self._getParameterValue(item, '"'); + }); + value = '\'{' + value.join(',') + '}\''; + } + } else { + value = _.map(value, this._getParameterValue, this); + value = '(' + value.join(', ') + ')'; + } } else if (_.isFunction(value.toISOString)) { // Date object's default toString format does not get parsed well // Handle date like objects using toISOString diff --git a/test/dialects/insert-tests.js b/test/dialects/insert-tests.js index b472f51d..f9a08912 100644 --- a/test/dialects/insert-tests.js +++ b/test/dialects/insert-tests.js @@ -4,6 +4,11 @@ var Harness = require('./support'); var post = Harness.definePostTable(); var user = Harness.defineUserTable(); +var arrayTable = Table.define({ + name: 'arraytest', + columns: ['id', 'numbers'] +}); + Harness.test({ query: post.insert(post.content.value('test'), post.userId.value(1)), pg: { @@ -630,3 +635,43 @@ Harness.test({ }, params: [] }); + +Harness.test({ + query: arrayTable.insert(arrayTable.id.value(1), arrayTable.numbers.value([2, 3, 4])), + pg: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, \'{2,3,4}\')' + }, + sqlite: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, \'[2,3,4]\')' + }, + mysql: { + text : 'INSERT INTO `arraytest` (`id`, `numbers`) VALUES (?, ?)', + string: 'INSERT INTO `arraytest` (`id`, `numbers`) VALUES (1, (2, 3, 4))' + }, + oracle: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES (:1, :2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, (2, 3, 4))' + } +}); + +Harness.test({ + query: arrayTable.insert(arrayTable.id.value(1), arrayTable.numbers.value(["one", "two", "three"])), + pg: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, \'{"one","two","three"}\')' + }, + sqlite: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, \'["one","two","three"]\')' + }, + mysql: { + text : 'INSERT INTO `arraytest` (`id`, `numbers`) VALUES (?, ?)', + string: 'INSERT INTO `arraytest` (`id`, `numbers`) VALUES (1, (\'one\', \'two\', \'three\'))' + }, + oracle: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES (:1, :2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, (\'one\', \'two\', \'three\'))' + } +}); diff --git a/test/dialects/update-tests.js b/test/dialects/update-tests.js index decfb4a5..eb3d0b3e 100644 --- a/test/dialects/update-tests.js +++ b/test/dialects/update-tests.js @@ -228,7 +228,7 @@ Harness.test({ }), pg: { text : 'UPDATE "variable" SET "a" = $1, "b" = $2', - string: 'UPDATE "variable" SET "a" = \'{"id":1,"value":2}\', "b" = (\'{"id":2,"value":3}\', \'{"id":3,"value":4}\')' + string: 'UPDATE "variable" SET "a" = \'{"id":1,"value":2}\', "b" = \'[{"id":2,"value":3},{"id":3,"value":4}]\'' }, sqlite: { text : 'UPDATE "variable" SET "a" = $1, "b" = $2', From 91b34eceafc2774c46f2a1a052472d179dc7d70e Mon Sep 17 00:00:00 2001 From: Eric Perry Date: Thu, 28 Jan 2016 17:29:00 -0500 Subject: [PATCH 06/62] Fixed some rebase issues --- lib/dialect/postgres.js | 6 +++--- test/dialects/insert-tests.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 0efad5bc..d92fa060 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -26,7 +26,7 @@ Postgres.prototype._getParameterText = function(index, value) { } }; -Postgres.prototype._getParameterValue = function(value) { +Postgres.prototype._getParameterValue = function(value, quoteChar) { // handle primitives if (null === value) { value = 'NULL'; @@ -36,8 +36,8 @@ Postgres.prototype._getParameterValue = function(value) { // number is just number value = value; } else if ('string' === typeof value) { - // string uses single quote - value = this.quote(value, "'"); + // string uses single quote by default + value = this.quote(value, quoteChar || "'"); } else if ('object' === typeof value) { if (_.isArray(value)) { if (this._myClass === Postgres) { diff --git a/test/dialects/insert-tests.js b/test/dialects/insert-tests.js index f9a08912..804daa06 100644 --- a/test/dialects/insert-tests.js +++ b/test/dialects/insert-tests.js @@ -4,7 +4,7 @@ var Harness = require('./support'); var post = Harness.definePostTable(); var user = Harness.defineUserTable(); -var arrayTable = Table.define({ +var arrayTable = require('../../lib/table').define({ name: 'arraytest', columns: ['id', 'numbers'] }); From d0cc3c7449230dd4b3058b9eba172f8a681365a3 Mon Sep 17 00:00:00 2001 From: brianc Date: Fri, 29 Jan 2016 10:57:44 -0600 Subject: [PATCH 07/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6e2766f..b2d7af1c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.66.0", + "version": "0.67.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From e734df273cfb174d4d88fc2078818297b27855b3 Mon Sep 17 00:00:00 2001 From: tmont Date: Mon, 1 Feb 2016 11:25:54 -0800 Subject: [PATCH 08/62] upgraded to lodash@4.1.x fixed #296 --- lib/dialect/postgres.js | 2 +- lib/table.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index d92fa060..d29894d9 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -56,7 +56,7 @@ Postgres.prototype._getParameterValue = function(value, quoteChar) { value = '\'{' + value.join(',') + '}\''; } } else { - value = _.map(value, this._getParameterValue, this); + value = _.map(value, this._getParameterValue.bind(this)); value = '(' + value.join(', ') + ')'; } } else if (_.isFunction(value.toISOString)) { diff --git a/lib/table.js b/lib/table.js index 86e8adbe..debafa24 100644 --- a/lib/table.js +++ b/lib/table.js @@ -78,7 +78,7 @@ Table.prototype.createColumn = function(col) { name: subfield })]; }, this)) - .object() + .fromPairs() .value(); } } diff --git a/package.json b/package.json index b2d7af1c..941b3329 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "sliced": "0.0.x", - "lodash": "1.3.x" + "lodash": "4.1.x" }, "devDependencies": { "jshint": "*", From 02b6f064c586efba3a4a0435670ccc4bd7ab77f9 Mon Sep 17 00:00:00 2001 From: tmont Date: Mon, 1 Feb 2016 11:31:01 -0800 Subject: [PATCH 09/62] removed dead code --- lib/dialect/postgres.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index d92fa060..520dc7ab 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -1006,7 +1006,6 @@ Postgres.prototype.visitDropIndex = function(node) { }; Postgres.prototype.visitCreateView = function(createView) { - //console.log('createView: ' + createView); var result = ['CREATE VIEW', this.quote(createView.options.viewName), 'AS']; return result; }; From 2e00b11f5c4aab77938ada67472efefab3e25521 Mon Sep 17 00:00:00 2001 From: tmont Date: Mon, 1 Feb 2016 11:31:17 -0800 Subject: [PATCH 10/62] only log stuff to the console if NODE_ENV=debug fixes #275 --- lib/table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/table.js b/lib/table.js index 86e8adbe..725d6666 100644 --- a/lib/table.js +++ b/lib/table.js @@ -98,7 +98,7 @@ Table.prototype.addColumn = function(col, options) { } else { return this; } - } else if(!!this[col.name] && (process.env.NODE_ENV !== 'test')) { + } else if(!!this[col.name] && (process.env.NODE_ENV === 'debug')) { console.log('Please notice that you have just defined the column "' + col.name + '". In order to access it, you need to use "table.getColumn(\'' + col.name + '\');"!'); } this.columns.push(col); From 775e93829a5a0d57c5e0d22302fd04cac9d8764b Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Mon, 8 Feb 2016 13:10:32 -0500 Subject: [PATCH 11/62] Extend query with value expressions to create allow creating select statements that can be joined together with expressions. --- lib/dialect/postgres.js | 3 +- lib/node/index.js | 6 +- lib/node/query.js | 14 +++++ test/dialects/select-tests.js | 104 +++++++++++++++++++++++++++++++++- 4 files changed, 124 insertions(+), 3 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index d29894d9..254511a3 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -644,7 +644,8 @@ Postgres.prototype.visitQuery = function(queryNode) { actions.push(new Select().add('*')); isSelect = true; } - if(missingFrom) { + if(missingFrom && queryNode.table instanceof Table) { + // the instanceof handles the situation where a sql.select(some expression) is used and there should be no FROM clause targets.push(new From().add(queryNode.table)); } if (createView) { diff --git a/lib/node/index.js b/lib/node/index.js index b500ab39..0bff1586 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -15,7 +15,11 @@ Node.prototype.toNode = function() { Node.prototype.add = function(node) { assert(node, 'Error while trying to add a non-existant node to a query'); - this.nodes.push(typeof node === 'string' ? new TextNode(node) : node.toNode()); + var newNode + if (typeof node === 'string') newNode = new TextNode(node) + else if (node.toNode) newNode = node.toNode() + else newNode = node + this.nodes.push(newNode); return this; }; diff --git a/lib/node/query.js b/lib/node/query.js index 4f13ebd1..46ec6cad 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -1,8 +1,10 @@ 'use strict'; +var _ = require('lodash'); var assert = require('assert'); var sliced = require('sliced'); var util = require('util'); +var valueExpressionMixin = require(__dirname + '/valueExpression'); var Node = require('./'); var Select = require('./select'); @@ -478,4 +480,16 @@ var Query = Node.define({ } }); +// Here we are extending query with valueExpressions so that it's possible to write queries like +// var query=sql.select(a.select(a.x.sum()).plus(b.select(b.y.sum())) +// which generates: +// SELECT (SELECT SUM(a.x) FROM a) + (SELECT SUM(b.y) FROM b) +// We need to remove "or" and "and" from here because it conflicts with the already existing functionality of appending +// to the where clause like so: +// var query=a.select().where(a.name.equals("joe")).or(a.name.equals("sam")) +var valueExpressions=valueExpressionMixin(); +delete valueExpressions["or"]; +delete valueExpressions["and"]; +_.extend(Query.prototype, valueExpressions); + module.exports = Query; diff --git a/test/dialects/select-tests.js b/test/dialects/select-tests.js index 59fdd786..0825f95a 100644 --- a/test/dialects/select-tests.js +++ b/test/dialects/select-tests.js @@ -3,6 +3,7 @@ var Harness = require('./support'); var post = Harness.definePostTable(); var customerAlias = Harness.defineCustomerAliasTable(); +var Sql = require('../../lib'); Harness.test({ query: post.select(post.id).select(post.content), @@ -52,4 +53,105 @@ Harness.test({ string: 'SELECT "customer"."id" "id_alias", "customer"."name" "name_alias", "customer"."age" "age_alias", "customer"."income" "income_alias", "customer"."metadata" "metadata_alias" FROM "customer"' }, params: [] -}); \ No newline at end of file +}); + +// Test that we can generate a SELECT claus without a FROM clause +Harness.test({ + query: Sql.select(), + pg: { + text : 'SELECT ', + string: 'SELECT ' + }, + sqlite: { + text : 'SELECT ', + string: 'SELECT ' + }, + mysql: { + text : 'SELECT ', + string: 'SELECT ' + }, + mssql: { + text : 'SELECT ', + string: 'SELECT ' + }, + oracle: { + text : 'SELECT ', + string: 'SELECT ' + }, + params: [] +}); + +Harness.test({ + query: Sql.select("1").where("1=1"), + pg: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + sqlite: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + mysql: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + mssql: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + oracle: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(post.select(post.id)), + pg: { + text : 'SELECT (SELECT "post"."id" FROM "post")', + string: 'SELECT (SELECT "post"."id" FROM "post")' + }, + sqlite: { + text : 'SELECT (SELECT "post"."id" FROM "post")', + string: 'SELECT (SELECT "post"."id" FROM "post")' + }, + mysql: { + text : 'SELECT (SELECT `post`.`id` FROM `post`)', + string: 'SELECT (SELECT `post`.`id` FROM `post`)' + }, + mssql: { + text : 'SELECT (SELECT [post].[id] FROM [post])', + string: 'SELECT (SELECT [post].[id] FROM [post])' + }, + oracle: { + text : 'SELECT (SELECT "post"."id" FROM "post")', + string: 'SELECT (SELECT "post"."id" FROM "post")' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(post.select(post.content).plus(post.select(post.content))), + pg: { + text : 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))', + string: 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))' + }, + sqlite: { + text : 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))', + string: 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))' + }, + mysql: { + text : 'SELECT ((SELECT `post`.`content` FROM `post`) + (SELECT `post`.`content` FROM `post`))', + string: 'SELECT ((SELECT `post`.`content` FROM `post`) + (SELECT `post`.`content` FROM `post`))' + }, + mssql: { + text : 'SELECT ((SELECT [post].[content] FROM [post]) + (SELECT [post].[content] FROM [post]))', + string: 'SELECT ((SELECT [post].[content] FROM [post]) + (SELECT [post].[content] FROM [post]))' + }, + oracle: { + text : 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))', + string: 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))' + }, + params: [] +}); From 69b3173577f09dbd6e534115952c758851349072 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Mon, 8 Feb 2016 13:14:37 -0500 Subject: [PATCH 12/62] Added another test for generating select clauses without from clauses. --- test/dialects/select-tests.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/dialects/select-tests.js b/test/dialects/select-tests.js index 0825f95a..baa3eb40 100644 --- a/test/dialects/select-tests.js +++ b/test/dialects/select-tests.js @@ -81,6 +81,32 @@ Harness.test({ params: [] }); +// Test that we can generate a SELECT claus without a FROM clause +Harness.test({ + query: Sql.select("1"), + pg: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + sqlite: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + mysql: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + mssql: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + oracle: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + params: [] +}); + Harness.test({ query: Sql.select("1").where("1=1"), pg: { From 50e184c62af3e16eb20168fe7f359ece44cca353 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Mon, 8 Feb 2016 18:23:29 -0500 Subject: [PATCH 13/62] Fixed a couple of jshint errors. --- lib/node/index.js | 8 ++++---- lib/node/query.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/node/index.js b/lib/node/index.js index 0bff1586..d519466f 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -15,10 +15,10 @@ Node.prototype.toNode = function() { Node.prototype.add = function(node) { assert(node, 'Error while trying to add a non-existant node to a query'); - var newNode - if (typeof node === 'string') newNode = new TextNode(node) - else if (node.toNode) newNode = node.toNode() - else newNode = node + var newNode; + if (typeof node === 'string') newNode = new TextNode(node); + else if (node.toNode) newNode = node.toNode(); + else newNode = node; this.nodes.push(newNode); return this; }; diff --git a/lib/node/query.js b/lib/node/query.js index 46ec6cad..cec925e5 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -488,8 +488,8 @@ var Query = Node.define({ // to the where clause like so: // var query=a.select().where(a.name.equals("joe")).or(a.name.equals("sam")) var valueExpressions=valueExpressionMixin(); -delete valueExpressions["or"]; -delete valueExpressions["and"]; +delete valueExpressions.or; +delete valueExpressions.and; _.extend(Query.prototype, valueExpressions); module.exports = Query; From 687eecf3504062c4a697b49e1931ebd4bff6bd4a Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 20 Feb 2016 10:36:33 -0500 Subject: [PATCH 14/62] Addresses issue when generating INSERT INTO ... SELECT statements created using the .add() function. --- lib/dialect/postgres.js | 25 ++++++++- test/dialects/insert-tests.js | 101 ++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index cbc7ece6..1b664504 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -596,7 +596,7 @@ Postgres.prototype.visitOverlap = function(overlap) { }; Postgres.prototype.visitQuery = function(queryNode) { - if (this._queryNode) return this.visitSubquery(queryNode); + if (this._queryNode) return this.visitSubquery(queryNode,dontParenthesizeSubQuery(this._queryNode)); this._queryNode = queryNode; // need to sort the top level query nodes on visitation priority // so select/insert/update/delete comes before from comes before where @@ -678,7 +678,7 @@ Postgres.prototype.visitQueryHelper=function(actions,targets,filters){ return this.output; }; -Postgres.prototype.visitSubquery = function(queryNode) { +Postgres.prototype.visitSubquery = function(queryNode,dontParenthesize) { // create another query builder of the current class to build the subquery var subQuery = new this._myClass(this.config); @@ -696,6 +696,9 @@ Postgres.prototype.visitSubquery = function(queryNode) { } var alias = queryNode.alias; + if (dontParenthesize) { + return [subQuery.output.join(' ') + (alias ? ' ' + this.quote(alias) : '')]; + } return ['(' + subQuery.output.join(' ') + ')' + (alias ? ' ' + this.quote(alias) : '')]; }; @@ -1041,4 +1044,22 @@ Postgres.prototype.handleDistinct = function(actions,filters) { selectInfo.node.isDistinct = true; }; +/** + * If the parent of the subquery is an INSERT we don't want to parenthesize. + * This happens when you create the query like so: + * + * var query=post.insert(post.id) + * var select=user.select(user.id) + * query.add(select) + * + * @param parentQuery + * @returns {boolean} + */ +function dontParenthesizeSubQuery(parentQuery){ + if (!parentQuery) return false; + if (parentQuery.nodes.length == 0) return false; + if (parentQuery.nodes[0].type != 'INSERT') return false; + return true; +} + module.exports = Postgres; diff --git a/test/dialects/insert-tests.js b/test/dialects/insert-tests.js index 804daa06..73517b48 100644 --- a/test/dialects/insert-tests.js +++ b/test/dialects/insert-tests.js @@ -675,3 +675,104 @@ Harness.test({ string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, (\'one\', \'two\', \'three\'))' } }); + +Harness.test({ + query: post.insert(post.userId).select(user.id).from(user), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + sqlite: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + text : 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]', + string: 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]' + }, + oracle: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + params: [] +}); + +Harness.test({ + query: post.insert(post.userId).add(user.select(user.id)), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + sqlite: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + text : 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]', + string: 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]' + }, + oracle: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + params: [] +}); + +Harness.test({ + query: post.insert(post.userId).add(user.select(user.id).from(user)), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + sqlite: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + text : 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]', + string: 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]' + }, + oracle: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + params: [] +}); + +Harness.test({ + query: post.insert(post.userId).add(user.select(user.id).order(user.id)), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"' + }, + sqlite: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"' + }, + mysql: { + text : 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user` ORDER BY `user`.`id`', + string: 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user` ORDER BY `user`.`id`' + }, + mssql: { + text : 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user] ORDER BY [user].[id]', + string: 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user] ORDER BY [user].[id]' + }, + oracle: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"' + }, + params: [] +}); + From 046d6dc19f712f1b7f3683ffabf15210d9ee7c5e Mon Sep 17 00:00:00 2001 From: iamcharliegoddard Date: Fri, 18 Mar 2016 22:00:46 -0700 Subject: [PATCH 15/62] jshint-fix: fix for jshint error --- lib/dialect/postgres.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 6539a54f..50f78233 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -1061,7 +1061,7 @@ Postgres.prototype.handleDistinct = function(actions,filters) { */ function dontParenthesizeSubQuery(parentQuery){ if (!parentQuery) return false; - if (parentQuery.nodes.length == 0) return false; + if (parentQuery.nodes.length === 0) return false; if (parentQuery.nodes[0].type != 'INSERT') return false; return true; } From 43b6c350950b70f340cc22ca0ce2668b6626073b Mon Sep 17 00:00:00 2001 From: Kevin Anthoney Date: Fri, 1 Apr 2016 20:43:15 +0100 Subject: [PATCH 16/62] Add table level foreign keys * Add table level foreign keys * Make refColumns optional in foreign keys * Add constraint name to foreign keys * Add on update clause to foreign keys, and also to column references while I was at it * Add actions SET NULL, SET DEFAULT and NO ACTION for onDelete and onUpdate for foreign keys and column references --- lib/dialect/postgres.js | 67 +++++++++++++++++- lib/node/foreignKey.js | 19 ++++++ lib/table.js | 15 +++- test/dialects/create-table-tests.js | 102 +++++++++++++++++++++++++--- 4 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 lib/node/foreignKey.js diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 50f78233..6a7c0dc7 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -153,6 +153,7 @@ Postgres.prototype.visit = function(node) { case 'FOR SHARE' : return this.visitForShare(); case 'TABLE' : return this.visitTable(node); case 'COLUMN' : return this.visitColumn(node); + case 'FOREIGN KEY' : return this.visitForeignKey(node); case 'JOIN' : return this.visitJoin(node); case 'LITERAL' : return this.visitLiteral(node); case 'TEXT' : return node.text; @@ -296,6 +297,7 @@ Postgres.prototype.visitCreate = function(create) { // don't auto-generate from clause var table = this._queryNode.table; var col_nodes = table.columns.map(function(col) { return col.toNode(); }); + var foreign_key_nodes = table.foreignKeys; var result = ['CREATE TABLE']; if (create.options.isTemporary) result=['CREATE TEMPORARY TABLE']; @@ -313,6 +315,9 @@ Postgres.prototype.visitCreate = function(create) { }.bind(this)).join(', '); colspec += ')'; } + if(foreign_key_nodes.length > 0) { + colspec += ', ' + foreign_key_nodes.map(this.visit.bind(this)).join(', '); + } colspec += ')'; result.push(colspec); this._visitCreateCompoundPrimaryKey = false; @@ -836,9 +841,14 @@ Postgres.prototype.visitColumn = function(columnNode) { var onDelete = columnNode.references.onDelete; if (onDelete) onDelete = onDelete.toUpperCase(); - if (onDelete === 'CASCADE' || onDelete === 'RESTRICT') { + if (onDelete === 'CASCADE' || onDelete === 'RESTRICT' || onDelete === 'SET NULL' || onDelete === 'SET DEFAULT' || onDelete === 'NO ACTION') { txt.push(' ON DELETE ' + onDelete); } + var onUpdate = columnNode.references.onUpdate; + if (onUpdate) onUpdate = onUpdate.toUpperCase(); + if (onUpdate === 'CASCADE' || onUpdate === 'RESTRICT' || onUpdate === 'SET NULL' || onUpdate === 'SET DEFAULT' || onUpdate === 'NO ACTION') { + txt.push(' ON UPDATE ' + onUpdate); + } var constraint = columnNode.references.constraint; if (constraint) { constraint = ' ' + constraint.toUpperCase(); @@ -850,6 +860,61 @@ Postgres.prototype.visitColumn = function(columnNode) { return [txt.join('')]; }; +Postgres.prototype.visitForeignKey = function(foreignKeyNode) +{ + var txt = []; + if(this._visitingCreate) { + assert(foreignKeyNode.table, 'Foreign table missing for table reference'); + assert(foreignKeyNode.columns, 'Columns missing for table reference'); + if(foreignKeyNode.refColumns !== undefined) { + assert.equal(foreignKeyNode.columns.length, foreignKeyNode.refColumns.length, 'Number of local columns and foreign columns differ in table reference'); + } + if(foreignKeyNode.name !== undefined) { + txt.push('CONSTRAINT ' + this.quote(foreignKeyNode.name) + ' '); + } + txt.push('FOREIGN KEY ( '); + for(var i = 0; i < foreignKeyNode.columns.length; i++) { + if(i>0) { + txt.push(', '); + } + txt.push(this.quote(foreignKeyNode.columns[i])); + } + txt.push(' ) REFERENCES '); + if(foreignKeyNode.schema !== undefined) { + txt.push(this.quote(foreignKeyNode.schema) + '.'); + } + txt.push(this.quote(foreignKeyNode.table)); + if(foreignKeyNode.refColumns !== undefined) { + txt.push(' ( '); + for(i = 0; i < foreignKeyNode.refColumns.length; i++) { + if(i>0) { + txt.push(', '); + } + txt.push(this.quote(foreignKeyNode.refColumns[i])); + } + txt.push(' )'); + } + var onDelete = foreignKeyNode.onDelete; + if(onDelete) { + onDelete = onDelete.toUpperCase(); + if(onDelete === 'CASCADE' || onDelete === 'RESTRICT' || onDelete === 'SET NULL' || onDelete === 'SET DEFAULT' || onDelete === 'NO ACTION') { + txt.push(' ON DELETE ' + onDelete); + } + } + var onUpdate = foreignKeyNode.onUpdate; + if(onUpdate) { + onUpdate = onUpdate.toUpperCase(); + if(onUpdate === 'CASCADE' || onUpdate === 'RESTRICT' || onUpdate === 'SET NULL' || onUpdate === 'SET DEFAULT' || onUpdate === 'NO ACTION') { + txt.push(' ON UPDATE ' + onUpdate); + } + } + if(foreignKeyNode.constraint) { + txt.push(' ' + foreignKeyNode.constraint.toUpperCase()); + } + } + return [txt.join('')]; +}; + Postgres.prototype.visitFunctionCall = function(functionCall) { this._visitingFunctionCall = true; var txt = functionCall.name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; diff --git a/lib/node/foreignKey.js b/lib/node/foreignKey.js new file mode 100644 index 00000000..c738d842 --- /dev/null +++ b/lib/node/foreignKey.js @@ -0,0 +1,19 @@ +'use strict'; + +var Node = require(__dirname); + +module.exports = Node.define({ + type: 'FOREIGN KEY', + constructor: function(config) { + Node.call(this); + this.name = config.name; + this.columns = config.columns; + this.schema = config.schema; + this.table = config.table; + this.refColumns = config.refColumns; + this.onUpdate = config.onUpdate; + this.onDelete = config.onDelete; + this.constraint = config.constraint; + } +}); + diff --git a/lib/table.js b/lib/table.js index b810283c..21895aea 100644 --- a/lib/table.js +++ b/lib/table.js @@ -9,6 +9,7 @@ var TableNode = require(__dirname + '/node/table'); var JoinNode = require(__dirname + '/node/join'); var LiteralNode = require(__dirname + '/node/literal'); var Joiner = require(__dirname + '/joiner'); +var ForeignKeyNode = require(__dirname + '/node/foreignKey'); var Table = function(config) { this._schema = config.schema; @@ -18,6 +19,7 @@ var Table = function(config) { this.isTemporary=!!config.isTemporary; this.snakeToCamel = !!config.snakeToCamel; this.columns = []; + this.foreignKeys = []; this.table = this; if (!config.sql) { config.sql = require('./index'); @@ -45,6 +47,16 @@ Table.define = function(config) { for (var i = 0; i < config.columns.length; i++) { table.addColumn(config.columns[i]); } + + if(config.foreignKeys !== undefined) { + if(util.isArray(config.foreignKeys)) { + for(i = 0; i < config.foreignKeys.length; i++) { + table.foreignKeys.push(new ForeignKeyNode(config.foreignKeys[i])); + } + } else { + table.foreignKeys.push(new ForeignKeyNode(config.foreignKeys)); + } + } return table; }; @@ -55,7 +67,8 @@ Table.prototype.clone = function(config) { sql: this.sql, columnWhiteList: !!this.columnWhiteList, snakeToCamel: !!this.snakeToCamel, - columns: this.columns + columns: this.columns, + foreignKeys: this.foreignKeys }, config || {})); }; diff --git a/test/dialects/create-table-tests.js b/test/dialects/create-table-tests.js index 872d51fa..97f6a1fd 100644 --- a/test/dialects/create-table-tests.js +++ b/test/dialects/create-table-tests.js @@ -256,25 +256,27 @@ Harness.test({ dataType: 'int', references: { table: 'user', - column: 'id' + column: 'id', + onDelete: 'restrict', + onUpdate: 'set null' } }] }).create(), pg: { - text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))', - string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))' + text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)', + string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)' }, sqlite: { - text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))', - string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))' + text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)', + string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)' }, mysql: { - text : 'CREATE TABLE `post` (`userId` int REFERENCES `user`(`id`))', - string: 'CREATE TABLE `post` (`userId` int REFERENCES `user`(`id`))' + text : 'CREATE TABLE `post` (`userId` int REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE SET NULL)', + string: 'CREATE TABLE `post` (`userId` int REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE SET NULL)' }, oracle: { - text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))', - string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))' + text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)', + string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)' }, params: [] }); @@ -643,4 +645,84 @@ Harness.test({ string: 'CREATE TABLE "post" ("id" int PRIMARY KEY)' }, params: [] -}); \ No newline at end of file +}); + +Harness.test({ + query: Table.define({ + name: 'post', + columns: [{ + name: 'id', + dataType: 'int', + primaryKey: true + }, { + name: 'blog_id', + dataType: 'int' + }, { + name: 'user_id', + dataType: 'int' + }], + foreignKeys: { + table: 'users', + columns: [ 'blog_id', 'user_id' ], + refColumns: [ 'id', 'user_id' ] + } + }).create(), + pg: { + text : 'CREATE TABLE "post" ("id" int PRIMARY KEY, "blog_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ( "id", "user_id" ))', + string: 'CREATE TABLE "post" ("id" int PRIMARY KEY, "blog_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ( "id", "user_id" ))' + }, + sqlite: { + text : 'CREATE TABLE "post" ("id" int PRIMARY KEY, "blog_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ( "id", "user_id" ))', + string: 'CREATE TABLE "post" ("id" int PRIMARY KEY, "blog_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ( "id", "user_id" ))' + }, + mysql: { + text : 'CREATE TABLE `post` (`id` int PRIMARY KEY, `blog_id` int, `user_id` int, FOREIGN KEY ( `blog_id`, `user_id` ) REFERENCES `users` ( `id`, `user_id` ))', + string: 'CREATE TABLE `post` (`id` int PRIMARY KEY, `blog_id` int, `user_id` int, FOREIGN KEY ( `blog_id`, `user_id` ) REFERENCES `users` ( `id`, `user_id` ))' + }, + params: [] +}); + +Harness.test({ + query: Table.define({ + name: 'replies', + columns: [{ + name: 'id', + dataType: 'int', + primaryKey: true + }, { + name: 'blog_id', + dataType: 'int' + }, { + name: 'post_id', + dataType: 'int' + }, { + name: 'user_id', + dataType: 'int' + }], + foreignKeys: [{ + table: 'users', + columns: [ 'blog_id', 'user_id' ], + onDelete: 'no action' + }, { + name: 'posts_idx', + table: 'posts', + columns: [ 'blog_id', 'post_id' ], + refColumns: [ 'blog_id', 'id' ], + onDelete: 'cascade', + onUpdate: 'set default' + }] + }).create(), + pg: { + text : 'CREATE TABLE "replies" ("id" int PRIMARY KEY, "blog_id" int, "post_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ON DELETE NO ACTION, CONSTRAINT "posts_idx" FOREIGN KEY ( "blog_id", "post_id" ) REFERENCES "posts" ( "blog_id", "id" ) ON DELETE CASCADE ON UPDATE SET DEFAULT)', + string: 'CREATE TABLE "replies" ("id" int PRIMARY KEY, "blog_id" int, "post_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ON DELETE NO ACTION, CONSTRAINT "posts_idx" FOREIGN KEY ( "blog_id", "post_id" ) REFERENCES "posts" ( "blog_id", "id" ) ON DELETE CASCADE ON UPDATE SET DEFAULT)' + }, + sqlite: { + text : 'CREATE TABLE "replies" ("id" int PRIMARY KEY, "blog_id" int, "post_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ON DELETE NO ACTION, CONSTRAINT "posts_idx" FOREIGN KEY ( "blog_id", "post_id" ) REFERENCES "posts" ( "blog_id", "id" ) ON DELETE CASCADE ON UPDATE SET DEFAULT)', + string: 'CREATE TABLE "replies" ("id" int PRIMARY KEY, "blog_id" int, "post_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ON DELETE NO ACTION, CONSTRAINT "posts_idx" FOREIGN KEY ( "blog_id", "post_id" ) REFERENCES "posts" ( "blog_id", "id" ) ON DELETE CASCADE ON UPDATE SET DEFAULT)' + }, + mysql: { + text : 'CREATE TABLE `replies` (`id` int PRIMARY KEY, `blog_id` int, `post_id` int, `user_id` int, FOREIGN KEY ( `blog_id`, `user_id` ) REFERENCES `users` ON DELETE NO ACTION, CONSTRAINT `posts_idx` FOREIGN KEY ( `blog_id`, `post_id` ) REFERENCES `posts` ( `blog_id`, `id` ) ON DELETE CASCADE ON UPDATE SET DEFAULT)', + string: 'CREATE TABLE `replies` (`id` int PRIMARY KEY, `blog_id` int, `post_id` int, `user_id` int, FOREIGN KEY ( `blog_id`, `user_id` ) REFERENCES `users` ON DELETE NO ACTION, CONSTRAINT `posts_idx` FOREIGN KEY ( `blog_id`, `post_id` ) REFERENCES `posts` ( `blog_id`, `id` ) ON DELETE CASCADE ON UPDATE SET DEFAULT)' + }, + params: [] +}); From 297daf305f12e14e6053bdef9d1ad17af8df7a0c Mon Sep 17 00:00:00 2001 From: brianc Date: Wed, 18 May 2016 17:52:10 -0400 Subject: [PATCH 17/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 941b3329..0e0cb5cd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.67.0", + "version": "0.68.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From e00acabe5662fa80dc12da6b2581279e992e8e7b Mon Sep 17 00:00:00 2001 From: Barry Hammen Date: Mon, 23 May 2016 15:29:34 -0400 Subject: [PATCH 18/62] Add support for basic date functions (#318) --- lib/dialect/mssql.js | 24 +++++-- lib/dialect/mysql.js | 25 +++++++ lib/dialect/postgres.js | 16 ++++- lib/dialect/sqlite.js | 41 +++++++++--- lib/functions.js | 10 ++- test/dialects/date-tests.js | 130 ++++++++++++++++++++++++++++++++++++ 6 files changed, 232 insertions(+), 14 deletions(-) create mode 100644 test/dialects/date-tests.js diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index 48c1ad45..d8e299e8 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -246,10 +246,26 @@ Mssql.prototype.visitDrop = function(drop) { Mssql.prototype.visitFunctionCall = function(functionCall) { this._visitingFunctionCall = true; - var name=functionCall.name; - // override the LENGTH function since mssql calls it LEN - if (name=="LENGTH") name="LEN"; - var txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + var _this = this; + + function _extract() { + var nodes = functionCall.nodes.map(_this.visit.bind(_this)); + if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function'); + var txt = 'DATEPART(' + functionCall.name.toLowerCase() + ', ' + (nodes[0]+'') + ')'; + return txt; + } + + var txt; + // Override date functions since mssql uses datepart + if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract(); + // Override CURRENT_TIMESTAMP function to remove parens + else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name; + else { + var name = functionCall.name; + // override the LENGTH function since mssql calls it LEN + if (name == "LENGTH") name = "LEN"; + txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + } this._visitingFunctionCall = false; return [txt]; }; diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js index 9d582ce7..225ea6be 100644 --- a/lib/dialect/mysql.js +++ b/lib/dialect/mysql.js @@ -102,4 +102,29 @@ Mysql.prototype.visitBinary = function(binary) { return Mysql.super_.prototype.visitBinary.call(this, binary); }; +Mysql.prototype.visitFunctionCall = function(functionCall) { + var _this=this; + + this._visitingFunctionCall = true; + + function _extract() { + var nodes = functionCall.nodes.map(_this.visit.bind(_this)); + if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function'); + var txt = functionCall.name + '(' + (nodes[0]+'') + ')'; + return txt; + } + + var txt=""; + var name = functionCall.name; + // Override date functions since mysql is different than postgres + if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract(); + // Override CURRENT_TIMESTAMP function to remove parens + else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name; + else txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + + this._visitingFunctionCall = false; + return [txt]; +}; + + module.exports = Mysql; diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 6a7c0dc7..ab12869d 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -917,7 +917,21 @@ Postgres.prototype.visitForeignKey = function(foreignKeyNode) Postgres.prototype.visitFunctionCall = function(functionCall) { this._visitingFunctionCall = true; - var txt = functionCall.name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + var _this = this; + + function _extract() { + var nodes = functionCall.nodes.map(_this.visit.bind(_this)); + if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function'); + var txt = 'EXTRACT(' + functionCall.name + ' FROM ' + (nodes[0]+'') + ')'; + return txt; + } + + var txt = ""; + // Override date functions since postgres (and others) uses extract + if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract(); + // Override CURRENT_TIMESTAMP function to remove parens + else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name; + else txt = functionCall.name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; this._visitingFunctionCall = false; return [txt]; }; diff --git a/lib/dialect/sqlite.js b/lib/dialect/sqlite.js index 81b452c0..d2885c2f 100644 --- a/lib/dialect/sqlite.js +++ b/lib/dialect/sqlite.js @@ -42,16 +42,16 @@ Sqlite.prototype.visitDropColumn = function() { throw new Error('SQLite does not allow dropping columns.'); }; -Sqlite.prototype.visitFunctionCall = function(functionCall) { - var _this=this; +Sqlite.prototype.visitFunctionCall = function (functionCall) { + var _this = this; - this._visitingFunctionCall = true; + this._visitingFunctionCall = true; function _left() { // convert LEFT(column,4) to SUBSTR(column,1,4) var nodes = functionCall.nodes.map(_this.visit.bind(_this)); if (nodes.length != 2) throw new Error('Not enough parameters passed to LEFT function.'); - var txt = "SUBSTR(" + (nodes[0]+'') + ', 1, ' + (nodes[1]+'') + ')'; + var txt = "SUBSTR(" + (nodes[0] + '') + ', 1, ' + (nodes[1] + '') + ')'; return txt; } @@ -59,18 +59,43 @@ Sqlite.prototype.visitFunctionCall = function(functionCall) { // convert RIGHT(column,4) to SUBSTR(column,-4) var nodes = functionCall.nodes.map(_this.visit.bind(_this)); if (nodes.length != 2) throw new Error('Not enough parameters passed to RIGHT function.'); - var txt = "SUBSTR(" + (nodes[0]+'') + ', -' + (nodes[1]+'') + ')'; + var txt = "SUBSTR(" + (nodes[0] + '') + ', -' + (nodes[1] + '') + ')'; return txt; } - var txt=""; - var name=functionCall.name; + function _extract() { + var nodes = functionCall.nodes.map(_this.visit.bind(_this)); + if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function'); + var format; + switch (functionCall.name) { + case 'YEAR': + format = "'%Y'"; + break; + case 'MONTH': + format = "'%m'"; + break; + case 'DAY': + format = "'%d'"; + break; + case 'HOUR': + format = "'%H'"; + break; + } + var txt = 'strftime(' + format + ', ' + (nodes[0] + '') + ')'; + return txt; + } + + var txt = ""; + var name = functionCall.name; // Override LEFT and RIGHT and convert to SUBSTR if (name == "LEFT") txt = _left(); else if (name == "RIGHT") txt = _right(); + // Override date functions since sqlite uses strftime + else if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract(); + else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name; else txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; - this._visitingFunctionCall = false; + this._visitingFunctionCall = false; return [txt]; }; diff --git a/lib/functions.js b/lib/functions.js index ef9b6f81..b0abcf54 100644 --- a/lib/functions.js +++ b/lib/functions.js @@ -47,13 +47,21 @@ var scalarFunctions = [ 'UPPER' ]; +var dateFunctions = [ + 'YEAR', + 'MONTH', + 'DAY', + 'HOUR', + 'CURRENT_TIMESTAMP' +]; + // hstore function available to Postgres var hstoreFunction = 'HSTORE'; //text search functions available to Postgres var textsearchFunctions = ['TS_RANK','TS_RANK_CD', 'PLAINTO_TSQUERY', 'TO_TSQUERY', 'TO_TSVECTOR', 'SETWEIGHT']; -var standardFunctionNames = aggregateFunctions.concat(scalarFunctions).concat(hstoreFunction).concat(textsearchFunctions); +var standardFunctionNames = aggregateFunctions.concat(scalarFunctions).concat(hstoreFunction).concat(textsearchFunctions).concat(dateFunctions); // creates a hash of standard functions for a sql instance var getStandardFunctions = function() { diff --git a/test/dialects/date-tests.js b/test/dialects/date-tests.js new file mode 100644 index 00000000..6eaf9851 --- /dev/null +++ b/test/dialects/date-tests.js @@ -0,0 +1,130 @@ +'use strict'; + +var Harness = require('./support'); +var customer = Harness.defineCustomerTable(); +var Sql = require('../../lib'); + +Harness.test({ + query: customer.select(Sql.functions.YEAR(customer.metadata)), + pg: { + text : 'SELECT EXTRACT(YEAR FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(YEAR FROM "customer"."metadata") FROM "customer"' + }, + sqlite: { + text : 'SELECT strftime(\'%Y\', "customer"."metadata") FROM "customer"', + string: 'SELECT strftime(\'%Y\', "customer"."metadata") FROM "customer"' + }, + mysql: { + text : 'SELECT YEAR(`customer`.`metadata`) FROM `customer`', + string: 'SELECT YEAR(`customer`.`metadata`) FROM `customer`' + }, + mssql: { + text : 'SELECT DATEPART(year, [customer].[metadata]) FROM [customer]', + string: 'SELECT DATEPART(year, [customer].[metadata]) FROM [customer]' + }, + oracle: { + text : 'SELECT EXTRACT(YEAR FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(YEAR FROM "customer"."metadata") FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: customer.select(Sql.functions.MONTH(customer.metadata)), + pg: { + text : 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"' + }, + sqlite: { + text : 'SELECT strftime(\'%m\', "customer"."metadata") FROM "customer"', + string: 'SELECT strftime(\'%m\', "customer"."metadata") FROM "customer"' + }, + mysql: { + text : 'SELECT MONTH(`customer`.`metadata`) FROM `customer`', + string: 'SELECT MONTH(`customer`.`metadata`) FROM `customer`' + }, + mssql: { + text : 'SELECT DATEPART(month, [customer].[metadata]) FROM [customer]', + string: 'SELECT DATEPART(month, [customer].[metadata]) FROM [customer]' + }, + oracle: { + text : 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: customer.select(Sql.functions.DAY(customer.metadata)), + pg: { + text : 'SELECT EXTRACT(DAY FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(DAY FROM "customer"."metadata") FROM "customer"' + }, + sqlite: { + text : 'SELECT strftime(\'%d\', "customer"."metadata") FROM "customer"', + string: 'SELECT strftime(\'%d\', "customer"."metadata") FROM "customer"' + }, + mysql: { + text : 'SELECT DAY(`customer`.`metadata`) FROM `customer`', + string: 'SELECT DAY(`customer`.`metadata`) FROM `customer`' + }, + mssql: { + text : 'SELECT DATEPART(day, [customer].[metadata]) FROM [customer]', + string: 'SELECT DATEPART(day, [customer].[metadata]) FROM [customer]' + }, + oracle: { + text : 'SELECT EXTRACT(DAY FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(DAY FROM "customer"."metadata") FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: customer.select(Sql.functions.HOUR(customer.metadata)), + pg: { + text : 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"' + }, + sqlite: { + text : 'SELECT strftime(\'%H\', "customer"."metadata") FROM "customer"', + string: 'SELECT strftime(\'%H\', "customer"."metadata") FROM "customer"' + }, + mysql: { + text : 'SELECT HOUR(`customer`.`metadata`) FROM `customer`', + string: 'SELECT HOUR(`customer`.`metadata`) FROM `customer`' + }, + mssql: { + text : 'SELECT DATEPART(hour, [customer].[metadata]) FROM [customer]', + string: 'SELECT DATEPART(hour, [customer].[metadata]) FROM [customer]' + }, + oracle: { + text : 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: customer.select(Sql.functions.CURRENT_TIMESTAMP()), + pg: { + text : 'SELECT CURRENT_TIMESTAMP FROM "customer"', + string: 'SELECT CURRENT_TIMESTAMP FROM "customer"' + }, + sqlite: { + text : 'SELECT CURRENT_TIMESTAMP FROM "customer"', + string: 'SELECT CURRENT_TIMESTAMP FROM "customer"' + }, + mysql: { + text : 'SELECT CURRENT_TIMESTAMP FROM `customer`', + string: 'SELECT CURRENT_TIMESTAMP FROM `customer`' + }, + mssql: { + text : 'SELECT CURRENT_TIMESTAMP FROM [customer]', + string: 'SELECT CURRENT_TIMESTAMP FROM [customer]' + }, + oracle: { + text : 'SELECT CURRENT_TIMESTAMP FROM "customer"', + string: 'SELECT CURRENT_TIMESTAMP FROM "customer"' + }, + params: [] +}); \ No newline at end of file From eec86e41ed0dfcbf51891dfed97df1d93995a3b3 Mon Sep 17 00:00:00 2001 From: brianc Date: Mon, 23 May 2016 14:30:07 -0500 Subject: [PATCH 19/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e0cb5cd..06159cf6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.68.0", + "version": "0.69.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From bc4d8d9dd00cf4e4ff8b69e5e2cca13f896e70fa Mon Sep 17 00:00:00 2001 From: Barry Hammen Date: Thu, 26 May 2016 13:14:28 -0400 Subject: [PATCH 20/62] Fix sqlite date functions for epoch (#319) --- lib/dialect/sqlite.js | 8 +++++++- test/dialects/date-tests.js | 14 ++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/dialect/sqlite.js b/lib/dialect/sqlite.js index d2885c2f..6085ba21 100644 --- a/lib/dialect/sqlite.js +++ b/lib/dialect/sqlite.js @@ -81,7 +81,13 @@ Sqlite.prototype.visitFunctionCall = function (functionCall) { format = "'%H'"; break; } - var txt = 'strftime(' + format + ', ' + (nodes[0] + '') + ')'; + var col = (nodes[0] + ''); + if (_this.config.dateTimeMillis) { + // Convert to a datetime before running the strftime function + // Sqlite unix epoch is in seconds, but javascript is milliseconds. + col = 'datetime(' + col + '/1000, "unixepoch")'; + } + var txt = 'strftime(' + format + ', ' + col + ')'; return txt; } diff --git a/test/dialects/date-tests.js b/test/dialects/date-tests.js index 6eaf9851..5a5fadda 100644 --- a/test/dialects/date-tests.js +++ b/test/dialects/date-tests.js @@ -36,8 +36,11 @@ Harness.test({ string: 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"' }, sqlite: { - text : 'SELECT strftime(\'%m\', "customer"."metadata") FROM "customer"', - string: 'SELECT strftime(\'%m\', "customer"."metadata") FROM "customer"' + text: 'SELECT strftime(\'%m\', datetime("customer"."metadata"/1000, "unixepoch")) FROM "customer"', + string: 'SELECT strftime(\'%m\', datetime("customer"."metadata"/1000, "unixepoch")) FROM "customer"', + config: { + dateTimeMillis: true + } }, mysql: { text : 'SELECT MONTH(`customer`.`metadata`) FROM `customer`', @@ -86,8 +89,11 @@ Harness.test({ string: 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"' }, sqlite: { - text : 'SELECT strftime(\'%H\', "customer"."metadata") FROM "customer"', - string: 'SELECT strftime(\'%H\', "customer"."metadata") FROM "customer"' + text: 'SELECT strftime(\'%H\', datetime("customer"."metadata"/1000, "unixepoch")) FROM "customer"', + string: 'SELECT strftime(\'%H\', datetime("customer"."metadata"/1000, "unixepoch")) FROM "customer"', + config: { + dateTimeMillis: true + } }, mysql: { text : 'SELECT HOUR(`customer`.`metadata`) FROM `customer`', From e6b40a96683cfe4bdd079a096243c3ba877035cf Mon Sep 17 00:00:00 2001 From: brianc Date: Thu, 26 May 2016 12:14:41 -0500 Subject: [PATCH 21/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06159cf6..3503e59d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.69.0", + "version": "0.70.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From cf928eeb2bd4305ed576ba32d1bcc01c55630a17 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Thu, 16 Jun 2016 18:44:28 -0400 Subject: [PATCH 22/62] _.isArray -> Array.isArray (#309) --- lib/dialect/postgres.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index ab12869d..fb7fb4d4 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -39,13 +39,13 @@ Postgres.prototype._getParameterValue = function(value, quoteChar) { // string uses single quote by default value = this.quote(value, quoteChar || "'"); } else if ('object' === typeof value) { - if (_.isArray(value)) { + if (Array.isArray(value)) { if (this._myClass === Postgres) { // naive check to see if this is an array of objects, which // is handled differently than an array of primitives if (value.length && 'object' === typeof value[0] && !_.isFunction(value[0].toISOString) && - !_.isArray(value[0])) { + !Array.isArray(value[0])) { value = "'" + JSON.stringify(value) + "'"; } else { var self = this; From 6786f50b89489a311e1a5f3e045f0e0a6078d629 Mon Sep 17 00:00:00 2001 From: Kevin Anthoney Date: Wed, 20 Jul 2016 20:14:41 +0100 Subject: [PATCH 23/62] Use count(*) syntax for MySQL instead of count(`table`.*) (#325) --- lib/dialect/mysql.js | 24 ++++++++++++++++++++++++ test/dialects/aggregate-tests.js | 16 ++++++++-------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js index 225ea6be..86416fcf 100644 --- a/lib/dialect/mysql.js +++ b/lib/dialect/mysql.js @@ -126,5 +126,29 @@ Mysql.prototype.visitFunctionCall = function(functionCall) { return [txt]; }; +Mysql.prototype.visitColumn = function(columnNode) { + var self = this; + var inSelectClause; + + function isCountStarExpression(columnNode){ + if (!columnNode.aggregator) return false; + if (columnNode.aggregator.toLowerCase()!='count') return false; + if (!columnNode.star) return false; + return true; + } + + function _countStar(){ + // Implement our own since count(table.*) is invalid in Mysql + var result='COUNT(*)'; + if(inSelectClause && columnNode.alias) { + result += ' AS ' + self.quote(columnNode.alias); + } + return result; + } + + inSelectClause = !this._selectOrDeleteEndIndex; + if(isCountStarExpression(columnNode)) return _countStar(); + return Mysql.super_.prototype.visitColumn.call(this, columnNode); +}; module.exports = Mysql; diff --git a/test/dialects/aggregate-tests.js b/test/dialects/aggregate-tests.js index d67d775a..59d48d57 100644 --- a/test/dialects/aggregate-tests.js +++ b/test/dialects/aggregate-tests.js @@ -16,8 +16,8 @@ Harness.test({ string: 'SELECT COUNT("post".*) AS "post_count" FROM "post"' }, mysql: { - text : 'SELECT COUNT(`post`.*) AS `post_count` FROM `post`', - string: 'SELECT COUNT(`post`.*) AS `post_count` FROM `post`' + text : 'SELECT COUNT(*) AS `post_count` FROM `post`', + string: 'SELECT COUNT(*) AS `post_count` FROM `post`' }, mssql: { text : 'SELECT COUNT(*) AS [post_count] FROM [post]', @@ -41,8 +41,8 @@ Harness.test({ string: 'SELECT COUNT("post".*) AS "post_count" FROM "post"' }, msyql: { - text : 'SELECT COUNT(`post`.*) AS `post_count` FROM `post`', - string: 'SELECT COUNT(`post`.*) AS `post_count` FROM `post`' + text : 'SELECT COUNT(*) AS `post_count` FROM `post`', + string: 'SELECT COUNT(*) AS `post_count` FROM `post`' }, mssql: { text : 'SELECT COUNT(*) AS [post_count] FROM [post]', @@ -66,8 +66,8 @@ Harness.test({ string: 'SELECT COUNT("post".*) AS "post_amount" FROM "post"' }, mysql: { - text : 'SELECT COUNT(`post`.*) AS `post_amount` FROM `post`', - string: 'SELECT COUNT(`post`.*) AS `post_amount` FROM `post`' + text : 'SELECT COUNT(*) AS `post_amount` FROM `post`', + string: 'SELECT COUNT(*) AS `post_amount` FROM `post`' }, mssql: { text : 'SELECT COUNT(*) AS [post_amount] FROM [post]', @@ -166,8 +166,8 @@ Harness.test({ string: 'SELECT COUNT("customer".*) AS "customer_count" FROM "customer"' }, mysql: { - text : 'SELECT COUNT(`customer`.*) AS `customer_count` FROM `customer`', - string: 'SELECT COUNT(`customer`.*) AS `customer_count` FROM `customer`' + text : 'SELECT COUNT(*) AS `customer_count` FROM `customer`', + string: 'SELECT COUNT(*) AS `customer_count` FROM `customer`' }, oracle: { text : 'SELECT COUNT(*) "customer_count" FROM "customer"', From b987e5c9dbd14cd8d4a34a9d245c1daf8a8b65e1 Mon Sep 17 00:00:00 2001 From: brianc Date: Wed, 20 Jul 2016 14:14:56 -0500 Subject: [PATCH 24/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3503e59d..5a82fb8d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.70.0", + "version": "0.70.1", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From eba2de6dc7a6b272ee94a7ad56eb6434a5a50305 Mon Sep 17 00:00:00 2001 From: danrzeppa Date: Tue, 30 Aug 2016 09:30:25 -0400 Subject: [PATCH 25/62] Alias a query with AS (#329) * Added a sql.literalColumn function for selecting literal/constant values. * - Added tests for function call on a literal column * Converted tabs to spaces. * Renamed "literalColumn" to "constant" * Added missing semicolons causing the Travis build to fail. * Added missing semicolon * Added ability to attach AS to a query. --- lib/dialect/mssql.js | 2 +- lib/dialect/oracle.js | 2 +- lib/dialect/postgres.js | 28 ++-- lib/index.js | 31 +++- lib/node/column.js | 2 + lib/node/query.js | 7 + test/dialects/select-tests.js | 278 ++++++++++++++++++++++++++++++++++ 7 files changed, 331 insertions(+), 19 deletions(-) diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index d8e299e8..2cb95d9d 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -186,7 +186,7 @@ Mssql.prototype.visitColumn = function(columnNode) { table = columnNode.table; inSelectClause = !this._selectOrDeleteEndIndex; if (isCountStarExpression(columnNode)) return _countStar(); - if (inSelectClause && !table.alias && columnNode.asArray) return _arrayAgg(); + if (inSelectClause && table && !table.alias && columnNode.asArray) return _arrayAgg(); return Mssql.super_.prototype.visitColumn.call(this, columnNode); }; diff --git a/lib/dialect/oracle.js b/lib/dialect/oracle.js index 6b597154..ea2cb85e 100644 --- a/lib/dialect/oracle.js +++ b/lib/dialect/oracle.js @@ -168,7 +168,7 @@ Oracle.prototype.visitColumn = function(columnNode) { table = columnNode.table; inSelectClause = !this._selectOrDeleteEndIndex; if (isCountStarExpression(columnNode)) return _countStar(); - if (inSelectClause && !table.alias && columnNode.asArray) return _arrayAgg(); + if (inSelectClause && table && !table.alias && columnNode.asArray) return _arrayAgg(); return Oracle.super_.prototype.visitColumn.call(this, columnNode); }; diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index fb7fb4d4..a57e6402 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -738,7 +738,7 @@ Postgres.prototype.visitColumn = function(columnNode) { var inCast = this._visitingCast; var txt = []; var closeParen = 0; - if(inSelectClause && (!table.alias || !!columnNode.alias)) { + if(inSelectClause && (table && !table.alias || !!columnNode.alias)) { if (columnNode.asArray) { closeParen++; txt.push(this._arrayAggFunctionName+'('); @@ -755,16 +755,18 @@ Postgres.prototype.visitColumn = function(columnNode) { } } if(!inInsertUpdateClause && !this.visitingReturning && !this._visitingCreate && !this._visitingAlter && !columnNode.subfieldContainer) { - if(typeof table.alias === 'string') { - txt.push(this.quote(table.alias)); - } else { - if(table.getSchema()) { - txt.push(this.quote(table.getSchema())); - txt.push('.'); + if (table) { + if (typeof table.alias === 'string') { + txt.push(this.quote(table.alias)); + } else { + if (table.getSchema()) { + txt.push(this.quote(table.getSchema())); + txt.push('.'); + } + txt.push(this.quote(table.getName())); } - txt.push(this.quote(table.getName())); + txt.push('.'); } - txt.push('.'); } if (columnNode.star) { var allCols = []; @@ -785,6 +787,14 @@ Postgres.prototype.visitColumn = function(columnNode) { txt.push('*'); } } + else if (columnNode.isConstant) { + // this injects directly into SELECT statement rather than creating a parameter + // txt.push(this._getParameterValue(columnNode.literalValue)) + // currently thinking it is better to generate a parameter + var value = columnNode.constantValue; + this.params.push(value); + txt.push(this._getParameterText(this.params.length, value)); + } else { if (columnNode.subfieldContainer) { txt.push('(' + this.visitColumn(columnNode.subfieldContainer) + ').'); diff --git a/lib/index.js b/lib/index.js index 6f609570..1309fcf7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,13 +1,15 @@ 'use strict'; -var _ = require('lodash'); -var FunctionCall = require('./node/functionCall'); -var ArrayCall = require('./node/arrayCall'); -var functions = require('./functions'); -var getDialect = require('./dialect'); -var Query = require('./node/query'); -var sliced = require('sliced'); -var Table = require('./table'); +var _ = require('lodash'); +var Column = require("./column"); +var FunctionCall = require('./node/functionCall'); +var ArrayCall = require('./node/arrayCall'); +var functions = require('./functions'); +var getDialect = require('./dialect'); +var ParameterNode = require('./node/parameter'); +var Query = require('./node/query'); +var sliced = require('sliced'); +var Table = require('./table'); // default dialect is postgres var DEFAULT_DIALECT = 'postgres'; @@ -58,6 +60,19 @@ Sql.prototype.setDialect = function(dialect, config) { return this; }; +// Create a constant Column (for use in SELECT) +Sql.prototype.constant = function(value) { + var config={ + name:"constant", + property:"constant", + isConstant:true, + constantValue:value, + }; + var cn = new Column(config); + return cn; +}; + + // back compat shim for the Sql class constructor var create = function(dialect, config) { return new Sql(dialect, {}); diff --git a/lib/node/column.js b/lib/node/column.js index 6fa999fb..6a5f7284 100644 --- a/lib/node/column.js +++ b/lib/node/column.js @@ -10,6 +10,8 @@ module.exports = Node.define({ this.property = config.property || config.name; this.alias = config.alias; this.star = config.star; + this.isConstant = config.isConstant; + this.constantValue = config.constantValue; this.asArray = config.asArray; this.aggregator = config.aggregator; this.table = config.table; diff --git a/lib/node/query.js b/lib/node/query.js index cec925e5..6c7ca8fd 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var alias = require(__dirname + '/alias'); var assert = require('assert'); var sliced = require('sliced'); var util = require('util'); @@ -492,4 +493,10 @@ delete valueExpressions.or; delete valueExpressions.and; _.extend(Query.prototype, valueExpressions); +// Extend the query with the aliasMixin so that it's possible to write queries like +// var query=sql.select(a.select(a.count()).as("column1")) +// which generates: +// SELECT (SELECT COUNT(*) FROM a) AS "column1" +_.extend(Query.prototype, alias.AliasMixin); + module.exports = Query; diff --git a/test/dialects/select-tests.js b/test/dialects/select-tests.js index baa3eb40..2892c8ae 100644 --- a/test/dialects/select-tests.js +++ b/test/dialects/select-tests.js @@ -2,6 +2,7 @@ var Harness = require('./support'); var post = Harness.definePostTable(); +var user = Harness.defineUserTable(); var customerAlias = Harness.defineCustomerAliasTable(); var Sql = require('../../lib'); @@ -181,3 +182,280 @@ Harness.test({ }, params: [] }); + +Harness.test({ + query: post.select(post.id.as('col1')), + pg: { + text : 'SELECT "post"."id" AS "col1" FROM "post"', + string: 'SELECT "post"."id" AS "col1" FROM "post"' + }, + sqlite: { + text : 'SELECT "post"."id" AS "col1" FROM "post"', + string: 'SELECT "post"."id" AS "col1" FROM "post"' + }, + mysql: { + text : 'SELECT `post`.`id` AS `col1` FROM `post`', + string: 'SELECT `post`.`id` AS `col1` FROM `post`' + }, + mssql: { + text : 'SELECT [post].[id] AS [col1] FROM [post]', + string: 'SELECT [post].[id] AS [col1] FROM [post]' + }, + oracle: { + text : 'SELECT "post"."id" "col1" FROM "post"', + string: 'SELECT "post"."id" "col1" FROM "post"' + }, + params: [] +}); + +Harness.test({ + query: post.select(Sql.constant(4)), + pg: { + text : 'SELECT $1 FROM "post"', + string: 'SELECT 4 FROM "post"' + }, + sqlite: { + text : 'SELECT $1 FROM "post"', + string: 'SELECT 4 FROM "post"' + }, + mysql: { + text : 'SELECT ? FROM `post`', + string: 'SELECT 4 FROM `post`' + }, + mssql: { + text : 'SELECT @1 FROM [post]', + string: 'SELECT 4 FROM [post]' + }, + oracle: { + text : 'SELECT :1 FROM "post"', + string: 'SELECT 4 FROM "post"' + }, + params: [4] +}); + +Harness.test({ + query: post.select(post.id,Sql.constant(4)), + pg: { + text : 'SELECT "post"."id", $1 FROM "post"', + string: 'SELECT "post"."id", 4 FROM "post"' + }, + sqlite: { + text : 'SELECT "post"."id", $1 FROM "post"', + string: 'SELECT "post"."id", 4 FROM "post"' + }, + mysql: { + text : 'SELECT `post`.`id`, ? FROM `post`', + string: 'SELECT `post`.`id`, 4 FROM `post`' + }, + mssql: { + text : 'SELECT [post].[id], @1 FROM [post]', + string: 'SELECT [post].[id], 4 FROM [post]' + }, + oracle: { + text : 'SELECT "post"."id", :1 FROM "post"', + string: 'SELECT "post"."id", 4 FROM "post"' + }, + params: [4] +}); + +Harness.test({ + query: post.select(Sql.constant(4).as('col1')), + pg: { + text : 'SELECT $1 AS "col1" FROM "post"', + string: 'SELECT 4 AS "col1" FROM "post"' + }, + sqlite: { + text : 'SELECT $1 AS "col1" FROM "post"', + string: 'SELECT 4 AS "col1" FROM "post"' + }, + mysql: { + text : 'SELECT ? AS `col1` FROM `post`', + string: 'SELECT 4 AS `col1` FROM `post`' + }, + mssql: { + text : 'SELECT @1 AS [col1] FROM [post]', + string: 'SELECT 4 AS [col1] FROM [post]' + }, + oracle: { + text : 'SELECT :1 "col1" FROM "post"', + string: 'SELECT 4 "col1" FROM "post"' + }, + params: [4] +}); + +Harness.test({ + query: post.select(Sql.constant(4).plus(5)), + pg: { + text : 'SELECT ($1 + $2) FROM "post"', + string: 'SELECT (4 + 5) FROM "post"' + }, + sqlite: { + text : 'SELECT ($1 + $2) FROM "post"', + string: 'SELECT (4 + 5) FROM "post"' + }, + mysql: { + text : 'SELECT (? + ?) FROM `post`', + string: 'SELECT (4 + 5) FROM `post`' + }, + mssql: { + text : 'SELECT (@1 + @2) FROM [post]', + string: 'SELECT (4 + 5) FROM [post]' + }, + oracle: { + text : 'SELECT (:1 + :2) FROM "post"', + string: 'SELECT (4 + 5) FROM "post"' + }, + params: [4,5] +}); + +Harness.test({ + query: post.select(Sql.constant(4).plus(5).as('col1')), + pg: { + text : 'SELECT ($1 + $2) AS "col1" FROM "post"', + string: 'SELECT (4 + 5) AS "col1" FROM "post"' + }, + sqlite: { + text : 'SELECT ($1 + $2) AS "col1" FROM "post"', + string: 'SELECT (4 + 5) AS "col1" FROM "post"' + }, + mysql: { + text : 'SELECT (? + ?) AS `col1` FROM `post`', + string: 'SELECT (4 + 5) AS `col1` FROM `post`' + }, + mssql: { + text : 'SELECT (@1 + @2) AS [col1] FROM [post]', + string: 'SELECT (4 + 5) AS [col1] FROM [post]' + }, + oracle: { + text : 'SELECT (:1 + :2) "col1" FROM "post"', + string: 'SELECT (4 + 5) "col1" FROM "post"' + }, + params: [4,5] +}); + +Harness.test({ + query: post.select(Sql.constant(4),Sql.constant("abc"),Sql.constant(true)), + pg: { + text : 'SELECT $1, $2, $3 FROM "post"', + string: 'SELECT 4, \'abc\', TRUE FROM "post"' + }, + sqlite: { + text : 'SELECT $1, $2, $3 FROM "post"', + string: 'SELECT 4, \'abc\', 1 FROM "post"' + }, + mysql: { + text : 'SELECT ?, ?, ? FROM `post`', + string: 'SELECT 4, \'abc\', TRUE FROM `post`' + }, + mssql: { + text : 'SELECT @1, @2, @3 FROM [post]', + string: 'SELECT 4, \'abc\', TRUE FROM [post]' + }, + oracle: { + text : 'SELECT :1, :2, :3 FROM "post"', + string: 'SELECT 4, \'abc\', TRUE FROM "post"' + }, + params: [4,'abc',true] +}); + +Harness.test({ + query: post.select(Sql.constant(1).sum()), + pg: { + text : 'SELECT SUM($1) AS "constant_sum" FROM "post"', + string: 'SELECT SUM(1) AS "constant_sum" FROM "post"' + }, + sqlite: { + text : 'SELECT SUM($1) AS "constant_sum" FROM "post"', + string: 'SELECT SUM(1) AS "constant_sum" FROM "post"' + }, + mysql: { + text : 'SELECT SUM(?) AS `constant_sum` FROM `post`', + string: 'SELECT SUM(1) AS `constant_sum` FROM `post`' + }, + mssql: { + text : 'SELECT SUM(@1) AS [constant_sum] FROM [post]', + string: 'SELECT SUM(1) AS [constant_sum] FROM [post]' + }, + oracle: { + text : 'SELECT SUM(:1) "constant_sum" FROM "post"', + string: 'SELECT SUM(1) "constant_sum" FROM "post"' + }, + params: [1] +}); + +Harness.test({ + query: Sql.select(post.select(post.id).as("column1")), + pg: { + text : 'SELECT (SELECT "post"."id" FROM "post") AS "column1"', + string: 'SELECT (SELECT "post"."id" FROM "post") AS "column1"' + }, + sqlite: { + text : 'SELECT (SELECT "post"."id" FROM "post") AS "column1"', + string: 'SELECT (SELECT "post"."id" FROM "post") AS "column1"' + }, + mysql: { + text : 'SELECT (SELECT `post`.`id` FROM `post`) AS `column1`', + string: 'SELECT (SELECT `post`.`id` FROM `post`) AS `column1`' + }, + mssql: { + text : 'SELECT (SELECT [post].[id] FROM [post]) AS [column1]', + string: 'SELECT (SELECT [post].[id] FROM [post]) AS [column1]' + }, + oracle: { + text : 'SELECT (SELECT "post"."id" FROM "post") "column1"', + string: 'SELECT (SELECT "post"."id" FROM "post") "column1"' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(post.select(post.count()).as("column1")), + pg: { + text : 'SELECT (SELECT COUNT("post".*) AS "post_count" FROM "post") AS "column1"', + string: 'SELECT (SELECT COUNT("post".*) AS "post_count" FROM "post") AS "column1"' + }, + sqlite: { + text : 'SELECT (SELECT COUNT("post".*) AS "post_count" FROM "post") AS "column1"', + string: 'SELECT (SELECT COUNT("post".*) AS "post_count" FROM "post") AS "column1"' + }, + mysql: { + text : 'SELECT (SELECT COUNT(*) AS `post_count` FROM `post`) AS `column1`', + string: 'SELECT (SELECT COUNT(*) AS `post_count` FROM `post`) AS `column1`' + }, + mssql: { + text : 'SELECT (SELECT COUNT(*) AS [post_count] FROM [post]) AS [column1]', + string: 'SELECT (SELECT COUNT(*) AS [post_count] FROM [post]) AS [column1]' + }, + oracle: { + text : 'SELECT (SELECT COUNT(*) "post_count" FROM "post") "column1"', + string: 'SELECT (SELECT COUNT(*) "post_count" FROM "post") "column1"' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(post.select(post.id).as("column1"),user.select(user.id).as("column2")), + pg: { + text : 'SELECT (SELECT "post"."id" FROM "post") AS "column1", (SELECT "user"."id" FROM "user") AS "column2"', + string: 'SELECT (SELECT "post"."id" FROM "post") AS "column1", (SELECT "user"."id" FROM "user") AS "column2"' + }, + sqlite: { + text : 'SELECT (SELECT "post"."id" FROM "post") AS "column1", (SELECT "user"."id" FROM "user") AS "column2"', + string: 'SELECT (SELECT "post"."id" FROM "post") AS "column1", (SELECT "user"."id" FROM "user") AS "column2"' + }, + mysql: { + text : 'SELECT (SELECT `post`.`id` FROM `post`) AS `column1`, (SELECT `user`.`id` FROM `user`) AS `column2`', + string: 'SELECT (SELECT `post`.`id` FROM `post`) AS `column1`, (SELECT `user`.`id` FROM `user`) AS `column2`' + }, + mssql: { + text : 'SELECT (SELECT [post].[id] FROM [post]) AS [column1], (SELECT [user].[id] FROM [user]) AS [column2]', + string: 'SELECT (SELECT [post].[id] FROM [post]) AS [column1], (SELECT [user].[id] FROM [user]) AS [column2]' + }, + oracle: { + text : 'SELECT (SELECT "post"."id" FROM "post") "column1", (SELECT "user"."id" FROM "user") "column2"', + string: 'SELECT (SELECT "post"."id" FROM "post") "column1", (SELECT "user"."id" FROM "user") "column2"' + }, + params: [] +}); + + From 5ec7827cf637a4fe6b930fd4e8d27e6a8cb5289f Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Tue, 30 Aug 2016 03:32:46 -0500 Subject: [PATCH 26/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a82fb8d..64e2f317 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.70.1", + "version": "0.71.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From f10abbed79a61e05c0a608095b76b9cf0e44b4fb Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Wed, 19 Oct 2016 16:54:02 +0200 Subject: [PATCH 27/62] Add support for ON CONFLICT as a support for upserts in PostgreSQL (#335) * Add support for ON CONFLICT as a support for upserts in PostgreSQL PostgreSQL supports upserts by ON CONFLICT clause provided in versions 9.5 and above. This should fix issue #333 * avoid redefining variable * fix scope --- lib/dialect/mssql.js | 4 + lib/dialect/mysql.js | 4 + lib/dialect/oracle.js | 4 + lib/dialect/postgres.js | 31 ++++++ lib/dialect/sqlite.js | 4 + lib/node/onConflict.js | 7 ++ lib/node/query.js | 10 ++ test/dialects/insert-tests.js | 184 ++++++++++++++++++++++++++++++++++ 8 files changed, 248 insertions(+) create mode 100644 lib/node/onConflict.js diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index 2cb95d9d..e93fc45a 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -381,6 +381,10 @@ Mssql.prototype.visitOnDuplicate = function(onDuplicate) { throw new Error('MSSQL does not allow onDuplicate clause.'); }; +Mssql.prototype.visitOnConflict = function(onConflict) { + throw new Error('MSSQL does not allow onConflict clause.'); +}; + Mssql.prototype.visitReturning = function() { // TODO: need to add some code to the INSERT clause to support this since its the equivalent of the OUTPUT clause // in MS SQL which appears before the values, not at the end of the statement. diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js index 86416fcf..4d054564 100644 --- a/lib/dialect/mysql.js +++ b/lib/dialect/mysql.js @@ -46,6 +46,10 @@ Mysql.prototype.visitOnDuplicate = function(onDuplicate) { return result; }; +Mysql.prototype.visitOnConflict = function(onConflict) { + throw new Error('Mysql does not allow onConflict clause.'); +}; + Mysql.prototype.visitReturning = function() { throw new Error('MySQL does not allow returning clause.'); }; diff --git a/lib/dialect/oracle.js b/lib/dialect/oracle.js index ea2cb85e..d791b8f7 100644 --- a/lib/dialect/oracle.js +++ b/lib/dialect/oracle.js @@ -224,6 +224,9 @@ Oracle.prototype.visitCase = function(caseExp) { return Mssql.prototype.visitCase.call(this, caseExp); }; +Oracle.prototype.visitOnConflict = function(onConflict) { + throw new Error('Oracle does not allow onConflict clause.'); +}; function isCreateIfNotExists(create){ if (create.nodes.length===0) return false; @@ -253,4 +256,5 @@ function isCountStarExpression(columnNode){ return true; } + module.exports = Oracle; diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index a57e6402..a55f343d 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -149,6 +149,7 @@ Postgres.prototype.visit = function(node) { case 'HAVING' : return this.visitHaving(node); case 'RETURNING' : return this.visitReturning(node); case 'ONDUPLICATE' : return this.visitOnDuplicate(node); + case 'ONCONFLICT' : return this.visitOnConflict(node); case 'FOR UPDATE' : return this.visitForUpdate(); case 'FOR SHARE' : return this.visitForShare(); case 'TABLE' : return this.visitTable(node); @@ -1037,6 +1038,36 @@ Postgres.prototype.visitOnDuplicate = function(onDuplicate) { throw new Error('PostgreSQL does not allow onDuplicate clause.'); }; +Postgres.prototype.visitOnConflict = function(onConflict) { + var result = ['ON CONFLICT']; + var columns = []; + var updateClause = [], i; + + if(onConflict.constraint) + result.push(['ON CONSTRAINT', this.quote(onConflict.constraint)].join(' ')); + else if(onConflict.columns) { + for(i=0; i < onConflict.columns.length; i++) { + columns.push(this.quote(onConflict.columns[i])); + } + result.push( '(' + columns.join(', ') + ')' ); + } + + if(onConflict.update){ + updateClause.push("DO UPDATE SET"); + var update = onConflict.update; + var setClause = []; + for(i=0; i Date: Wed, 19 Oct 2016 09:57:45 -0500 Subject: [PATCH 28/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64e2f317..6f2b9d02 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.71.0", + "version": "0.72.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From cc897ca79340758f1f10f30ef847d496d781e22e Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Tue, 20 Dec 2016 19:45:49 +0100 Subject: [PATCH 29/62] =?UTF-8?q?the=20ON=20CONFLICT=20feature=20for=20#Po?= =?UTF-8?q?stgreSQL=20was=20missing=20camelcase=20support=E2=80=A6=20(#342?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * the ON CONFLICT feature for #PostgreSQL was missing camelcase support of column names this commit fixes that and takes care of issue #341 it now honours snakeToCamel set to true * we can quote the column name only once and save extra operation --- lib/dialect/postgres.js | 9 ++-- test/dialects/insert-tests.js | 83 +++++++++++++++++++++++++++++++++++ test/dialects/support.js | 9 ++++ 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index a55f343d..9d9f547a 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -1041,13 +1041,13 @@ Postgres.prototype.visitOnDuplicate = function(onDuplicate) { Postgres.prototype.visitOnConflict = function(onConflict) { var result = ['ON CONFLICT']; var columns = []; - var updateClause = [], i; - + var updateClause = [], i, col; + var table = this._queryNode.table; if(onConflict.constraint) result.push(['ON CONSTRAINT', this.quote(onConflict.constraint)].join(' ')); else if(onConflict.columns) { for(i=0; i < onConflict.columns.length; i++) { - columns.push(this.quote(onConflict.columns[i])); + columns.push(this.quote(table.getColumn(onConflict.columns[i]).name)); } result.push( '(' + columns.join(', ') + ')' ); } @@ -1057,7 +1057,8 @@ Postgres.prototype.visitOnConflict = function(onConflict) { var update = onConflict.update; var setClause = []; for(i=0; i Date: Tue, 20 Dec 2016 13:46:06 -0500 Subject: [PATCH 30/62] Fix adding/dropping columns with oracle. (#338) --- lib/dialect/oracle.js | 61 ++++++++++++++++++++++++++++++ test/dialects/alter-table-tests.js | 32 ++++++++++++++-- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/lib/dialect/oracle.js b/lib/dialect/oracle.js index d791b8f7..9a9f6caa 100644 --- a/lib/dialect/oracle.js +++ b/lib/dialect/oracle.js @@ -28,6 +28,56 @@ Oracle.prototype.visitAlias = function(alias) { return result; }; +Oracle.prototype.visitAlter = function(alter) { + var self=this; + var errMsg='ALTER TABLE cannot be used to perform multiple different operations in the same statement.'; + + // Implement our own add column: + // PostgreSQL: ALTER TABLE "name" ADD COLUMN "col1", ADD COLUMN "col2" + // Oracle: ALTER TABLE "name" ADD ("col1", "col2") + function _addColumn(){ + self._visitingAlter = true; + var table = self._queryNode.table; + self._visitingAddColumn = true; + var result='ALTER TABLE '+self.visit(table.toNode())+' ADD ('+self.visit(alter.nodes[0].nodes[0]); + for (var i= 1,len=alter.nodes.length; i Date: Tue, 20 Dec 2016 13:46:21 -0500 Subject: [PATCH 31/62] Fix issue when calling insert with an object that has a length property instead of an array. (#337) --- lib/table.js | 2 +- test/dialects/insert-tests.js | 25 +++++++++++++++++++++++++ test/dialects/support.js | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/table.js b/lib/table.js index 21895aea..0b75c5fe 100644 --- a/lib/table.js +++ b/lib/table.js @@ -208,7 +208,7 @@ Table.prototype.subQuery = function(alias) { Table.prototype.insert = function() { var query = new Query(this); - if(arguments[0].length === 0){ + if(!arguments[0] || (util.isArray(arguments[0]) && arguments[0].length === 0)){ query.select.call(query, this.star()); query.where.apply(query,["1=2"]); } else { diff --git a/test/dialects/insert-tests.js b/test/dialects/insert-tests.js index 30f83712..aca6be7b 100644 --- a/test/dialects/insert-tests.js +++ b/test/dialects/insert-tests.js @@ -57,6 +57,31 @@ Harness.test({ params: ['whoah'] }); +Harness.test({ + query: post.insert({length: 0}), + pg: { + text : 'INSERT INTO "post" ("length") VALUES ($1)', + string: 'INSERT INTO "post" ("length") VALUES (0)' + }, + sqlite: { + text : 'INSERT INTO "post" ("length") VALUES ($1)', + string: 'INSERT INTO "post" ("length") VALUES (0)' + }, + mysql: { + text : 'INSERT INTO `post` (`length`) VALUES (?)', + string: 'INSERT INTO `post` (`length`) VALUES (0)' + }, + mssql: { + text : 'INSERT INTO [post] ([length]) VALUES (@1)', + string: 'INSERT INTO [post] ([length]) VALUES (0)' + }, + oracle: { + text : 'INSERT INTO "post" ("length") VALUES (:1)', + string: 'INSERT INTO "post" ("length") VALUES (0)' + }, + params: [0] +}); + Harness.test({ query: post.insert({ content: 'test', diff --git a/test/dialects/support.js b/test/dialects/support.js index 83da378f..4a9fa427 100644 --- a/test/dialects/support.js +++ b/test/dialects/support.js @@ -76,7 +76,7 @@ module.exports = { definePostTable: function() { return Table.define({ name: 'post', - columns: ['id', 'userId', 'content', 'tags'] + columns: ['id', 'userId', 'content', 'tags', 'length'] }); }, From 1049cd7508d0ff86cb6fc314d6eaba89ad8ae98a Mon Sep 17 00:00:00 2001 From: petrovi4 Date: Tue, 20 Dec 2016 21:46:43 +0300 Subject: [PATCH 32/62] Added value expression for ? operator "contain key" (#334) --- lib/node/valueExpression.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/node/valueExpression.js b/lib/node/valueExpression.js index faad8033..2fa741a9 100644 --- a/lib/node/valueExpression.js +++ b/lib/node/valueExpression.js @@ -148,6 +148,7 @@ var ValueExpressionMixin = function() { at : atMethod, contains : binaryMethod('@>'), containedBy : binaryMethod('<@'), + containsKey : binaryMethod('?'), overlap : binaryMethod('&&'), slice : sliceMethod, cast : castMethod, From 72aff9012a3f4339892d6283a6b872d682faef11 Mon Sep 17 00:00:00 2001 From: Kevin Anthoney Date: Tue, 20 Dec 2016 18:47:03 +0000 Subject: [PATCH 33/62] Add date time interval functionality to postgres and mysql dialects (#326) * Add date time interval functionality to postgres and mysql dialects * Add date interval test involving years and months --- lib/dialect/mysql.js | 27 +++++++++++ lib/dialect/postgres.js | 21 +++++++++ lib/index.js | 26 ++++++---- lib/node/interval.js | 21 +++++++++ test/dialects/date-tests.js | 94 ++++++++++++++++++++++++++++++++++++- 5 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 lib/node/interval.js diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js index 4d054564..5212ba5b 100644 --- a/lib/dialect/mysql.js +++ b/lib/dialect/mysql.js @@ -2,6 +2,7 @@ var util = require('util'); var assert = require('assert'); +var _ = require('lodash'); var Mysql = function(config) { this.output = []; @@ -155,4 +156,30 @@ Mysql.prototype.visitColumn = function(columnNode) { return Mysql.super_.prototype.visitColumn.call(this, columnNode); }; +Mysql.prototype.visitInterval = function(interval) { + var parameter; + if(_.isNumber(interval.years)) { + if(_.isNumber(interval.months)) { + parameter = "'" + interval.years + '-' + interval.months + "' YEAR_MONTH"; + } else { + parameter = interval.years + ' YEAR'; + } + } else if(_.isNumber(interval.months)) { + parameter = interval.months + ' MONTH'; + } else if(_.isNumber(interval.days)) { + parameter = "'" + interval.days + ' ' + + (_.isNumber(interval.hours)?interval.hours:0) + ':' + + (_.isNumber(interval.minutes)?interval.minutes:0) + ':' + + (_.isNumber(interval.seconds)?interval.seconds:0) + "' DAY_SECOND"; + } else { + parameter = "'" + (_.isNumber(interval.hours)?interval.hours:0) + ':' + + (_.isNumber(interval.minutes)?interval.minutes:0) + ':' + + (_.isNumber(interval.seconds)?interval.seconds:0) + "' HOUR_SECOND"; + } + var result = "INTERVAL " + parameter; + return result; +}; + + + module.exports = Mysql; diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 9d9f547a..6520e1cc 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -174,6 +174,7 @@ Postgres.prototype.visit = function(node) { case 'FUNCTION CALL' : return this.visitFunctionCall(node); case 'ARRAY CALL' : return this.visitArrayCall(node); case 'CREATE VIEW' : return this.visitCreateView(node); + case 'INTERVAL' : return this.visitInterval(node); case 'POSTFIX UNARY' : return this.visitPostfixUnary(node); case 'PREFIX UNARY' : return this.visitPrefixUnary(node); @@ -1139,6 +1140,26 @@ Postgres.prototype.visitCreateView = function(createView) { return result; }; +Postgres.prototype.visitInterval = function(interval) { + var parameter = ''; + function _add(n, unit) { + if(!_.isNumber(n)) return; + if(parameter !== '') { + parameter += ' '; + } + parameter += n + ' ' + unit; + } + _add(interval.years, 'YEAR'); + _add(interval.months, 'MONTH'); + _add(interval.days, 'DAY'); + _add(interval.hours, 'HOUR'); + _add(interval.minutes, 'MINUTE'); + _add(interval.seconds, 'SECOND'); + if(parameter === '') parameter = '0 SECOND'; + var result = "INTERVAL '" + parameter + "'"; + return result; +}; + /** * Broken out as a separate function so that dialects that derive from this class can still use this functionality. * diff --git a/lib/index.js b/lib/index.js index 1309fcf7..3a1cdfdc 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,15 +1,15 @@ 'use strict'; -var _ = require('lodash'); -var Column = require("./column"); -var FunctionCall = require('./node/functionCall'); -var ArrayCall = require('./node/arrayCall'); -var functions = require('./functions'); -var getDialect = require('./dialect'); -var ParameterNode = require('./node/parameter'); -var Query = require('./node/query'); -var sliced = require('sliced'); -var Table = require('./table'); +var _ = require('lodash'); +var Column = require("./column"); +var FunctionCall = require('./node/functionCall'); +var ArrayCall = require('./node/arrayCall'); +var functions = require('./functions'); +var getDialect = require('./dialect'); +var Query = require('./node/query'); +var sliced = require('sliced'); +var Table = require('./table'); +var Interval = require('./node/interval'); // default dialect is postgres var DEFAULT_DIALECT = 'postgres'; @@ -51,6 +51,12 @@ Sql.prototype.select = function() { return query; }; +// Returns an interval clause +Sql.prototype.interval = function() { + var interval = new Interval(sliced(arguments)); + return interval; +}; + // Set the dialect Sql.prototype.setDialect = function(dialect, config) { this.dialect = getDialect(dialect); diff --git a/lib/node/interval.js b/lib/node/interval.js new file mode 100644 index 00000000..231e0d0d --- /dev/null +++ b/lib/node/interval.js @@ -0,0 +1,21 @@ +'use strict'; + +var Node = require(__dirname); +var ParameterNode = require(__dirname + '/parameter'); + +var IntervalNode = Node.define({ + type: 'INTERVAL', + constructor: function(args) { + Node.call(this); + var interval = args[0] || {}; + this.years = interval.years; + this.months = interval.months; + this.days = interval.days; + this.hours = interval.hours; + this.minutes = interval.minutes; + this.seconds = interval.seconds; + } +}); + +module.exports = IntervalNode; + diff --git a/test/dialects/date-tests.js b/test/dialects/date-tests.js index 5a5fadda..dd2c4d64 100644 --- a/test/dialects/date-tests.js +++ b/test/dialects/date-tests.js @@ -133,4 +133,96 @@ Harness.test({ string: 'SELECT CURRENT_TIMESTAMP FROM "customer"' }, params: [] -}); \ No newline at end of file +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().plus(Sql.interval({hours:1}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1 HOUR\')', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1 HOUR\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1:0:0\' HOUR_SECOND)', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1:0:0\' HOUR_SECOND)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().minus(Sql.interval({years:3}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3 YEAR\')', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3 YEAR\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL 3 YEAR)', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL 3 YEAR)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().minus(Sql.interval({years:3, months:2}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3 YEAR 2 MONTH\')', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3 YEAR 2 MONTH\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3-2\' YEAR_MONTH)', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3-2\' YEAR_MONTH)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().plus(Sql.interval({hours:1, minutes:20}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1 HOUR 20 MINUTE\')', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1 HOUR 20 MINUTE\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1:20:0\' HOUR_SECOND)', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1:20:0\' HOUR_SECOND)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().plus(Sql.interval({hours:'sql\'injection', minutes:20}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'20 MINUTE\')', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'20 MINUTE\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'0:20:0\' HOUR_SECOND)', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'0:20:0\' HOUR_SECOND)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().minus(Sql.interval({days: 1, hours:5, minutes: 'sql\'injection'}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'1 DAY 5 HOUR\')', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'1 DAY 5 HOUR\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'1 5:0:0\' DAY_SECOND)', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'1 5:0:0\' DAY_SECOND)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().minus(Sql.interval({years: 2, months: 5}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'2 YEAR 5 MONTH\')', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'2 YEAR 5 MONTH\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'2-5\' YEAR_MONTH)', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'2-5\' YEAR_MONTH)' + }, + params: [] +}); + From f941c74561e8a1fe31ca0baec4a9ffbbb7cac6f4 Mon Sep 17 00:00:00 2001 From: Christian Schuhmann Date: Tue, 20 Dec 2016 19:47:27 +0100 Subject: [PATCH 34/62] Fix Table.hasColumn for columns with custom property names (#322) There was a bug which caused Table.hasColumn to return false when passed a property name (defined via column.property) instead of a column name. --- lib/table.js | 9 +++------ test/table-tests.js | 13 +++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/table.js b/lib/table.js index 0b75c5fe..0f657236 100644 --- a/lib/table.js +++ b/lib/table.js @@ -126,13 +126,10 @@ Table.prototype.addColumn = function(col, options) { }; Table.prototype.hasColumn = function(col) { - col = this.createColumn(col); - - var cols = this.columns.filter(function(column) { - return column.property === col.property || column.name === col.name; + var columnName = col instanceof Column ? col.name : col; + return this.columns.some(function(column) { + return column.property === columnName || column.name === columnName; }); - - return cols.length > 0; }; Table.prototype.getColumn = diff --git a/test/table-tests.js b/test/table-tests.js index 66c3bdd3..73d43367 100644 --- a/test/table-tests.js +++ b/test/table-tests.js @@ -167,6 +167,19 @@ test('hasColumn', function() { assert.equal(table.hasColumn('baz'), true); }); +test('hasColumn with user-defined column property', function() { + var table = Table.define({ + name: 'blah', + columns: [{ + name: 'id', + property: 'theId' + }, {name: 'foo'}] + }); + + assert.equal(table.hasColumn('id'), true); + assert.equal(table.hasColumn('theId'), true); +}); + test('the column "from" does not overwrite the from method', function() { var table = Table.define({ name: 'foo', columns: [] }); table.addColumn('from'); From 2808409da82cd4d5bda5577a64de5f11dcb43a62 Mon Sep 17 00:00:00 2001 From: Kevin Anthoney Date: Tue, 20 Dec 2016 18:47:57 +0000 Subject: [PATCH 35/62] add defaultValue parameter when creating tables (#311) --- lib/column.js | 1 + lib/dialect/postgres.js | 3 +++ lib/node/column.js | 1 + test/dialects/create-table-tests.js | 33 +++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/lib/column.js b/lib/column.js index 3c5e70f5..55a3da18 100644 --- a/lib/column.js +++ b/lib/column.js @@ -20,6 +20,7 @@ var Column = function(config) { direction : new TextNode('DESC') }); this.dataType = config.dataType; + this.defaultValue = config.defaultValue; }; // mix in value expression diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 6520e1cc..20c4edab 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -826,6 +826,9 @@ Postgres.prototype.visitColumn = function(columnNode) { if (!columnNode.primaryKey && columnNode.unique) { txt.push(' UNIQUE'); } + if (columnNode.defaultValue !== undefined) { + txt.push(' DEFAULT ' + this._getParameterValue(columnNode.defaultValue)); + } } if (!!columnNode.references) { diff --git a/lib/node/column.js b/lib/node/column.js index 6a5f7284..3fda47b0 100644 --- a/lib/node/column.js +++ b/lib/node/column.js @@ -20,6 +20,7 @@ module.exports = Node.define({ this.distinct = config.distinct; this.primaryKey = config.primaryKey; this.notNull = config.notNull; + this.defaultValue = config.defaultValue; this.references = config.references; // If subfieldContainer is present, this is a subfield and subfieldContainer // is the parent Column diff --git a/test/dialects/create-table-tests.js b/test/dialects/create-table-tests.js index 97f6a1fd..b1150617 100644 --- a/test/dialects/create-table-tests.js +++ b/test/dialects/create-table-tests.js @@ -248,6 +248,39 @@ Harness.test({ } }); +Harness.test({ + query: Table.define({ + name: 'user', + columns: [{ + name: 'id', + dataType: 'int', + primaryKey: true, + notNull: true + }, { + name: 'posts', + dataType: 'int', + notNull: true, + defaultValue: 0 + }] + }).create(), + pg: { + text : 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)', + string: 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)' + }, + sqlite: { + text : 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)', + string: 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)' + }, + mysql: { + text : 'CREATE TABLE `user` (`id` int PRIMARY KEY, `posts` int NOT NULL DEFAULT 0)', + string: 'CREATE TABLE `user` (`id` int PRIMARY KEY, `posts` int NOT NULL DEFAULT 0)' + }, + oracle: { + text : 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)', + string: 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)' + } +}); + Harness.test({ query: Table.define({ name: 'post', From 4533b3636ef43ac2a0656ba05d4765ce41ddc6c0 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Tue, 20 Dec 2016 13:48:14 -0500 Subject: [PATCH 36/62] Check instanceof Date. (#310) Since we just need to know if this value is a Date, we can check it directly. --- lib/dialect/postgres.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 20c4edab..9628f85d 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -59,9 +59,9 @@ Postgres.prototype._getParameterValue = function(value, quoteChar) { value = _.map(value, this._getParameterValue.bind(this)); value = '(' + value.join(', ') + ')'; } - } else if (_.isFunction(value.toISOString)) { + } else if (value instanceof Date) { // Date object's default toString format does not get parsed well - // Handle date like objects using toISOString + // Handle dates using toISOString value = this._getParameterValue(value.toISOString()); } else if (Buffer.isBuffer(value)) { value = this._getParameterValue('\\x' + value.toString('hex')); From bc02f7850245964e15e63b4d904fa43114ec36a0 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Tue, 20 Dec 2016 12:49:20 -0600 Subject: [PATCH 37/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f2b9d02..2dc3a8e8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.72.0", + "version": "0.73.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From 73bf26079feabb31d6f2258b58d0abb47e95fd00 Mon Sep 17 00:00:00 2001 From: Andy Katz Date: Wed, 8 Feb 2017 09:34:32 -0600 Subject: [PATCH 38/62] add OR IGNORE for sqlite (#347) * add OR IGNORE for sqlite * add missing semicolon --- lib/dialect/postgres.js | 14 +++++++++----- lib/dialect/sqlite.js | 4 ++++ lib/node/orIgnore.js | 7 +++++++ lib/node/query.js | 6 ++++++ test/dialects/insert-tests.js | 24 ++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 lib/node/orIgnore.js diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 9628f85d..6e67c38e 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -162,6 +162,7 @@ Postgres.prototype.visit = function(node) { case 'DEFAULT' : return this.visitDefault(node); case 'IF EXISTS' : return this.visitIfExists(); case 'IF NOT EXISTS' : return this.visitIfNotExists(); + case 'OR IGNORE' : return this.visitOrIgnore(); case 'CASCADE' : return this.visitCascade(); case 'RESTRICT' : return this.visitRestrict(); case 'RENAME' : return this.visitRename(node); @@ -237,11 +238,10 @@ Postgres.prototype.visitInsert = function(insert) { // don't use table.column for inserts this._visitedInsert = true; - var result = [ - 'INSERT INTO', - this.visit(this._queryNode.table.toNode()), - '(' + insert.columns.map(this.visit.bind(this)).join(', ') + ')' - ]; + var result = ['INSERT']; + result = result.concat(insert.nodes.map(this.visit.bind(this))); + result.push('INTO ' + this.visit(this._queryNode.table.toNode())); + result.push('(' + insert.columns.map(this.visit.bind(this)).join(', ') + ')'); var paramNodes = insert.getParameters(); @@ -995,6 +995,10 @@ Postgres.prototype.visitIfNotExists = function() { return ['IF NOT EXISTS']; }; +Postgres.prototype.visitOrIgnore = function() { + throw new Error('PostgreSQL does not allow orIgnore clause.'); +}; + Postgres.prototype.visitCascade = function() { return ['CASCADE']; }; diff --git a/lib/dialect/sqlite.js b/lib/dialect/sqlite.js index 0cd31335..fb2450d4 100644 --- a/lib/dialect/sqlite.js +++ b/lib/dialect/sqlite.js @@ -165,4 +165,8 @@ Sqlite.prototype.visitBinary = function(binary) { return Sqlite.super_.prototype.visitBinary.call(this, binary); }; +Sqlite.prototype.visitOrIgnore = function() { + return ['OR IGNORE']; +}; + module.exports = Sqlite; diff --git a/lib/node/orIgnore.js b/lib/node/orIgnore.js new file mode 100644 index 00000000..849750bc --- /dev/null +++ b/lib/node/orIgnore.js @@ -0,0 +1,7 @@ +'use strict'; + +var Node = require(__dirname); + +module.exports = Node.define({ + type: 'OR IGNORE' +}); diff --git a/lib/node/query.js b/lib/node/query.js index 9abc9b64..1927dbe9 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -37,6 +37,7 @@ var ParameterNode = require('./parameter'); var PrefixUnaryNode = require('./prefixUnary'); var IfExists = require('./ifExists'); var IfNotExists = require('./ifNotExists'); +var OrIgnore = require('./orIgnore'); var Cascade = require('./cascade'); var Restrict = require('./restrict'); var Indexes = require('./indexes'); @@ -468,6 +469,11 @@ var Query = Node.define({ return this; }, + orIgnore: function() { + this.nodes[0].unshift(new OrIgnore()); + return this; + }, + cascade: function() { this.nodes[0].add(new Cascade()); return this; diff --git a/test/dialects/insert-tests.js b/test/dialects/insert-tests.js index aca6be7b..60ba6a62 100644 --- a/test/dialects/insert-tests.js +++ b/test/dialects/insert-tests.js @@ -680,6 +680,30 @@ Harness.test({ params: [2, 'test'] }); +Harness.test({ + query: customerAliasTable.insert({ + id : 2, + name : 'test' + }).orIgnore(), + mysql: { + throws: true + }, + sqlite: { + text : 'INSERT OR IGNORE INTO "customer" ("id", "name") VALUES ($1, $2)', + string: 'INSERT OR IGNORE INTO "customer" ("id", "name") VALUES (2, \'test\')' + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [2, 'test'] +}); + Harness.test({ query: post.insert({ content: 'test', From 6d20ddd55e26c8d43032d35b874c87614cae13e8 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Wed, 8 Feb 2017 09:34:43 -0600 Subject: [PATCH 39/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dc3a8e8..6b984199 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.73.0", + "version": "0.74.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From 1af2a33c4b69972de90df7e4cf8f535753d3098d Mon Sep 17 00:00:00 2001 From: Lorenzo Giuliani Date: Sat, 15 Apr 2017 00:59:11 +0200 Subject: [PATCH 40/62] use browserify-safe require statements (#352) all boils down to _not_ using `__dirname` inside a require call --- lib/dialect/mssql.js | 2 +- lib/dialect/mysql.js | 2 +- lib/dialect/oracle.js | 4 ++-- lib/dialect/sqlite.js | 2 +- lib/functions.js | 2 +- lib/node/addColumn.js | 2 +- lib/node/alias.js | 2 +- lib/node/alter.js | 2 +- lib/node/arrayCall.js | 8 ++++---- lib/node/at.js | 6 +++--- lib/node/binary.js | 6 +++--- lib/node/cascade.js | 2 +- lib/node/case.js | 6 +++--- lib/node/cast.js | 6 +++--- lib/node/column.js | 2 +- lib/node/create.js | 2 +- lib/node/createView.js | 2 +- lib/node/default.js | 2 +- lib/node/delete.js | 2 +- lib/node/distinct.js | 2 +- lib/node/distinctOn.js | 2 +- lib/node/drop.js | 2 +- lib/node/dropColumn.js | 2 +- lib/node/forShare.js | 2 +- lib/node/forUpdate.js | 2 +- lib/node/foreignKey.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 +- lib/node/in.js | 6 +++--- lib/node/index.js | 2 +- lib/node/interval.js | 4 ++-- lib/node/join.js | 2 +- lib/node/literal.js | 2 +- lib/node/notIn.js | 6 +++--- lib/node/onConflict.js | 2 +- lib/node/onDuplicate.js | 2 +- lib/node/orIgnore.js | 2 +- lib/node/orderBy.js | 2 +- lib/node/orderByValue.js | 2 +- lib/node/parameter.js | 2 +- lib/node/postfixUnary.js | 6 +++--- lib/node/prefixUnary.js | 6 +++--- lib/node/query.js | 4 ++-- lib/node/rename.js | 2 +- lib/node/renameColumn.js | 2 +- lib/node/restrict.js | 2 +- lib/node/returning.js | 2 +- lib/node/select.js | 2 +- lib/node/slice.js | 6 +++--- lib/node/table.js | 2 +- lib/node/ternary.js | 6 +++--- lib/node/text.js | 2 +- lib/node/truncate.js | 2 +- lib/node/update.js | 2 +- lib/node/where.js | 6 +++--- lib/table.js | 14 +++++++------- 60 files changed, 97 insertions(+), 97 deletions(-) diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index e93fc45a..3606f5b6 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -20,7 +20,7 @@ var Mssql = function(config) { this.config = config || {}; }; -var Postgres = require(__dirname + '/postgres'); +var Postgres = require('./postgres'); util.inherits(Mssql, Postgres); diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js index 5212ba5b..b2fc2c26 100644 --- a/lib/dialect/mysql.js +++ b/lib/dialect/mysql.js @@ -10,7 +10,7 @@ var Mysql = function(config) { this.config = config || {}; }; -var Postgres = require(__dirname + '/postgres'); +var Postgres = require('./postgres'); util.inherits(Mysql, Postgres); diff --git a/lib/dialect/oracle.js b/lib/dialect/oracle.js index 9a9f6caa..b1f95eec 100644 --- a/lib/dialect/oracle.js +++ b/lib/dialect/oracle.js @@ -9,9 +9,9 @@ var Oracle = function(config) { this.config = config || {}; }; -var Postgres = require(__dirname + '/postgres'); +var Postgres = require('./postgres'); -var Mssql = require(__dirname + '/mssql'); +var Mssql = require('./mssql'); util.inherits(Oracle, Postgres); diff --git a/lib/dialect/sqlite.js b/lib/dialect/sqlite.js index fb2450d4..af2c3ef4 100644 --- a/lib/dialect/sqlite.js +++ b/lib/dialect/sqlite.js @@ -11,7 +11,7 @@ var Sqlite = function(config) { this.config = config || {}; }; -var Postgres = require(__dirname + '/postgres'); +var Postgres = require('./postgres'); util.inherits(Sqlite, Postgres); diff --git a/lib/functions.js b/lib/functions.js index b0abcf54..4362b706 100644 --- a/lib/functions.js +++ b/lib/functions.js @@ -1,7 +1,7 @@ 'use strict'; var _ = require('lodash'); var sliced = require('sliced'); -var FunctionCall = require(__dirname + '/node/functionCall'); +var FunctionCall = require('./node/functionCall'); // create a function that creates a function call of the specific name, using the specified sql instance var getFunctionCallCreator = function(name) { diff --git a/lib/node/addColumn.js b/lib/node/addColumn.js index 273d8836..3d7d4066 100644 --- a/lib/node/addColumn.js +++ b/lib/node/addColumn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ADD COLUMN' diff --git a/lib/node/alias.js b/lib/node/alias.js index 78f01323..9f9855f5 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 Node = require('./index'); var AliasNode = Node.define({ type: 'ALIAS', diff --git a/lib/node/alter.js b/lib/node/alter.js index ce53623c..4aae4d83 100644 --- a/lib/node/alter.js +++ b/lib/node/alter.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ALTER' diff --git a/lib/node/arrayCall.js b/lib/node/arrayCall.js index 4f2f657b..7533190d 100644 --- a/lib/node/arrayCall.js +++ b/lib/node/arrayCall.js @@ -1,9 +1,9 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var ParameterNode = require(__dirname + '/parameter'); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var ParameterNode = require('./parameter'); +var valueExpressionMixin = require('./valueExpression'); var ArrayCallNode = Node.define({ type: 'ARRAY CALL', @@ -18,7 +18,7 @@ var ArrayCallNode = Node.define({ _.extend(ArrayCallNode.prototype, valueExpressionMixin()); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(ArrayCallNode.prototype, AliasNode.AliasMixin); module.exports = ArrayCallNode; diff --git a/lib/node/at.js b/lib/node/at.js index 2ded9362..5e8ca47b 100644 --- a/lib/node/at.js +++ b/lib/node/at.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var AtNode = Node.define({ @@ -21,7 +21,7 @@ var AtNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(AtNode.prototype, AliasNode.AliasMixin); module.exports = AtNode; diff --git a/lib/node/binary.js b/lib/node/binary.js index 7b721650..da8eb8bd 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('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var BinaryNode = Node.define(_.extend({ @@ -23,7 +23,7 @@ var BinaryNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(BinaryNode.prototype, AliasNode.AliasMixin); module.exports = BinaryNode; diff --git a/lib/node/cascade.js b/lib/node/cascade.js index 1dea1eef..4b6646fd 100644 --- a/lib/node/cascade.js +++ b/lib/node/cascade.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'CASCADE' diff --git a/lib/node/case.js b/lib/node/case.js index b8729314..ac3bf214 100644 --- a/lib/node/case.js +++ b/lib/node/case.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var CaseNode = Node.define(_.extend({ @@ -23,7 +23,7 @@ var CaseNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(CaseNode.prototype, AliasNode.AliasMixin); module.exports = CaseNode; diff --git a/lib/node/cast.js b/lib/node/cast.js index 60966ea9..be915b1e 100644 --- a/lib/node/cast.js +++ b/lib/node/cast.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var CastNode = Node.define({ @@ -21,7 +21,7 @@ var CastNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(CastNode.prototype, AliasNode.AliasMixin); module.exports = CastNode; diff --git a/lib/node/column.js b/lib/node/column.js index 3fda47b0..6b145d74 100644 --- a/lib/node/column.js +++ b/lib/node/column.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'COLUMN', diff --git a/lib/node/create.js b/lib/node/create.js index c048c3ce..67cd9b3d 100644 --- a/lib/node/create.js +++ b/lib/node/create.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'CREATE', diff --git a/lib/node/createView.js b/lib/node/createView.js index bc52684f..7d456d90 100644 --- a/lib/node/createView.js +++ b/lib/node/createView.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'CREATE VIEW', diff --git a/lib/node/default.js b/lib/node/default.js index 49bd7ebd..b5adc894 100644 --- a/lib/node/default.js +++ b/lib/node/default.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = require(__dirname).define({ +module.exports = require('./index').define({ type: 'DEFAULT', value: function() { return; diff --git a/lib/node/delete.js b/lib/node/delete.js index 2acb5872..0d02ebab 100644 --- a/lib/node/delete.js +++ b/lib/node/delete.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DELETE' diff --git a/lib/node/distinct.js b/lib/node/distinct.js index a115f148..8c681551 100644 --- a/lib/node/distinct.js +++ b/lib/node/distinct.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DISTINCT', diff --git a/lib/node/distinctOn.js b/lib/node/distinctOn.js index a208dfde..72c430e8 100644 --- a/lib/node/distinctOn.js +++ b/lib/node/distinctOn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DISTINCT ON', diff --git a/lib/node/drop.js b/lib/node/drop.js index 177b6d1a..b0428347 100644 --- a/lib/node/drop.js +++ b/lib/node/drop.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DROP', diff --git a/lib/node/dropColumn.js b/lib/node/dropColumn.js index 60ad4219..01fe36c5 100644 --- a/lib/node/dropColumn.js +++ b/lib/node/dropColumn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DROP COLUMN' diff --git a/lib/node/forShare.js b/lib/node/forShare.js index 328a4ebe..68a83678 100644 --- a/lib/node/forShare.js +++ b/lib/node/forShare.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'FOR SHARE' diff --git a/lib/node/forUpdate.js b/lib/node/forUpdate.js index c869f981..da4a2518 100644 --- a/lib/node/forUpdate.js +++ b/lib/node/forUpdate.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'FOR UPDATE' diff --git a/lib/node/foreignKey.js b/lib/node/foreignKey.js index c738d842..a0ad99c1 100644 --- a/lib/node/foreignKey.js +++ b/lib/node/foreignKey.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'FOREIGN KEY', diff --git a/lib/node/from.js b/lib/node/from.js index 84a6010f..12b54565 100644 --- a/lib/node/from.js +++ b/lib/node/from.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); var From = Node.define({ type: 'FROM' diff --git a/lib/node/functionCall.js b/lib/node/functionCall.js index 6d43c279..608a3c1e 100644 --- a/lib/node/functionCall.js +++ b/lib/node/functionCall.js @@ -1,9 +1,9 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var ParameterNode = require(__dirname + '/parameter'); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var ParameterNode = require('./parameter'); +var valueExpressionMixin = require('./valueExpression'); var FunctionCallNode = Node.define({ type: 'FUNCTION CALL', @@ -18,7 +18,7 @@ var FunctionCallNode = Node.define({ _.extend(FunctionCallNode.prototype, valueExpressionMixin()); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(FunctionCallNode.prototype, AliasNode.AliasMixin); module.exports = FunctionCallNode; diff --git a/lib/node/groupBy.js b/lib/node/groupBy.js index 6e5888d1..53f63e94 100644 --- a/lib/node/groupBy.js +++ b/lib/node/groupBy.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'GROUP BY' diff --git a/lib/node/having.js b/lib/node/having.js index 7dcf3434..6f1523ac 100644 --- a/lib/node/having.js +++ b/lib/node/having.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'HAVING' diff --git a/lib/node/ifExists.js b/lib/node/ifExists.js index 9b759779..c26df660 100644 --- a/lib/node/ifExists.js +++ b/lib/node/ifExists.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'IF EXISTS' diff --git a/lib/node/ifNotExists.js b/lib/node/ifNotExists.js index ef414551..d731ccb2 100644 --- a/lib/node/ifNotExists.js +++ b/lib/node/ifNotExists.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'IF NOT EXISTS' diff --git a/lib/node/in.js b/lib/node/in.js index 4c807331..233b7c28 100644 --- a/lib/node/in.js +++ b/lib/node/in.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var InNode = Node.define(_.extend({ @@ -22,7 +22,7 @@ var InNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(InNode.prototype, AliasNode.AliasMixin); module.exports = InNode; diff --git a/lib/node/index.js b/lib/node/index.js index d519466f..66c0e481 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -98,4 +98,4 @@ Node.define = function(def) { }; module.exports = Node; -var TextNode = require(__dirname + '/text'); +var TextNode = require('./text'); diff --git a/lib/node/interval.js b/lib/node/interval.js index 231e0d0d..9f46f9ab 100644 --- a/lib/node/interval.js +++ b/lib/node/interval.js @@ -1,7 +1,7 @@ 'use strict'; -var Node = require(__dirname); -var ParameterNode = require(__dirname + '/parameter'); +var Node = require('./index'); +var ParameterNode = require('./parameter'); var IntervalNode = Node.define({ type: 'INTERVAL', diff --git a/lib/node/join.js b/lib/node/join.js index 7186f694..bbc1f93a 100644 --- a/lib/node/join.js +++ b/lib/node/join.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); var JoinNode = module.exports = Node.define({ type: 'JOIN', constructor: function(subType, from, to) { diff --git a/lib/node/literal.js b/lib/node/literal.js index c4f539bb..f5a1ea58 100644 --- a/lib/node/literal.js +++ b/lib/node/literal.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'LITERAL', diff --git a/lib/node/notIn.js b/lib/node/notIn.js index e363f7f3..c50976c3 100644 --- a/lib/node/notIn.js +++ b/lib/node/notIn.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var NotInNode = Node.define(_.extend({ @@ -22,7 +22,7 @@ var NotInNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(NotInNode.prototype, AliasNode.AliasMixin); module.exports = NotInNode; diff --git a/lib/node/onConflict.js b/lib/node/onConflict.js index cd785120..cc71ac47 100644 --- a/lib/node/onConflict.js +++ b/lib/node/onConflict.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ONCONFLICT' diff --git a/lib/node/onDuplicate.js b/lib/node/onDuplicate.js index 38146793..cd653cb3 100644 --- a/lib/node/onDuplicate.js +++ b/lib/node/onDuplicate.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ONDUPLICATE' diff --git a/lib/node/orIgnore.js b/lib/node/orIgnore.js index 849750bc..e23c9d6d 100644 --- a/lib/node/orIgnore.js +++ b/lib/node/orIgnore.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'OR IGNORE' diff --git a/lib/node/orderBy.js b/lib/node/orderBy.js index 6db2a978..b97fb891 100644 --- a/lib/node/orderBy.js +++ b/lib/node/orderBy.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ORDER BY' diff --git a/lib/node/orderByValue.js b/lib/node/orderByValue.js index 51d44567..47a8d5c0 100644 --- a/lib/node/orderByValue.js +++ b/lib/node/orderByValue.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); var OrderByColumn = Node.define({ type: 'ORDER BY VALUE', diff --git a/lib/node/parameter.js b/lib/node/parameter.js index c2276710..051ed852 100644 --- a/lib/node/parameter.js +++ b/lib/node/parameter.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); var ParameterNode = module.exports = Node.define({ type: 'PARAMETER', diff --git a/lib/node/postfixUnary.js b/lib/node/postfixUnary.js index 0c132f8a..8c66b89f 100644 --- a/lib/node/postfixUnary.js +++ b/lib/node/postfixUnary.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var PostfixUnaryNode = Node.define({ @@ -22,7 +22,7 @@ var PostfixUnaryNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(PostfixUnaryNode.prototype, AliasNode.AliasMixin); module.exports = PostfixUnaryNode; diff --git a/lib/node/prefixUnary.js b/lib/node/prefixUnary.js index 67a59fa1..fbde69c0 100644 --- a/lib/node/prefixUnary.js +++ b/lib/node/prefixUnary.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var PrefixUnaryNode = Node.define({ @@ -22,7 +22,7 @@ var PrefixUnaryNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(PrefixUnaryNode.prototype, AliasNode.AliasMixin); module.exports = PrefixUnaryNode; diff --git a/lib/node/query.js b/lib/node/query.js index 1927dbe9..674d07b7 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -1,11 +1,11 @@ 'use strict'; var _ = require('lodash'); -var alias = require(__dirname + '/alias'); +var alias = require('./alias'); var assert = require('assert'); var sliced = require('sliced'); var util = require('util'); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var valueExpressionMixin = require('./valueExpression'); var Node = require('./'); var Select = require('./select'); diff --git a/lib/node/rename.js b/lib/node/rename.js index 9767a528..c87bcfb3 100644 --- a/lib/node/rename.js +++ b/lib/node/rename.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'RENAME' diff --git a/lib/node/renameColumn.js b/lib/node/renameColumn.js index f886a11d..c3c66865 100644 --- a/lib/node/renameColumn.js +++ b/lib/node/renameColumn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'RENAME COLUMN' diff --git a/lib/node/restrict.js b/lib/node/restrict.js index f7c63c15..942f4112 100644 --- a/lib/node/restrict.js +++ b/lib/node/restrict.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'RESTRICT' diff --git a/lib/node/returning.js b/lib/node/returning.js index 35ea0a18..2f27d067 100644 --- a/lib/node/returning.js +++ b/lib/node/returning.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'RETURNING' diff --git a/lib/node/select.js b/lib/node/select.js index a485b814..93e22538 100644 --- a/lib/node/select.js +++ b/lib/node/select.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'SELECT', diff --git a/lib/node/slice.js b/lib/node/slice.js index ab32f314..86c1ce21 100644 --- a/lib/node/slice.js +++ b/lib/node/slice.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var SliceNode = Node.define({ @@ -22,7 +22,7 @@ var SliceNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(SliceNode.prototype, AliasNode.AliasMixin); module.exports = SliceNode; diff --git a/lib/node/table.js b/lib/node/table.js index b1257105..4fca70d1 100644 --- a/lib/node/table.js +++ b/lib/node/table.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'TABLE', constructor: function(table) { diff --git a/lib/node/ternary.js b/lib/node/ternary.js index 1ef5ce7b..3f688ba9 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('./index'); +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..bf026151 100644 --- a/lib/node/text.js +++ b/lib/node/text.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'TEXT', diff --git a/lib/node/truncate.js b/lib/node/truncate.js index 790c26ca..165e0d9e 100644 --- a/lib/node/truncate.js +++ b/lib/node/truncate.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'TRUNCATE', diff --git a/lib/node/update.js b/lib/node/update.js index 32a29ba9..890fbee9 100644 --- a/lib/node/update.js +++ b/lib/node/update.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'UPDATE' diff --git a/lib/node/where.js b/lib/node/where.js index b542807d..5c0465fc 100644 --- a/lib/node/where.js +++ b/lib/node/where.js @@ -1,8 +1,8 @@ 'use strict'; -var Node = require(__dirname); -var BinaryNode = require(__dirname + '/binary'); -var TextNode = require(__dirname + '/text'); +var Node = require('./index'); +var BinaryNode = require('./binary'); +var TextNode = require('./text'); var normalizeNode = function(table, node) { var result = node; diff --git a/lib/table.js b/lib/table.js index 0f657236..23e335a8 100644 --- a/lib/table.js +++ b/lib/table.js @@ -3,13 +3,13 @@ var util = require('util'); var lodash = require('lodash'); -var Query = require(__dirname + '/node/query'); -var Column = require(__dirname + '/column'); -var TableNode = require(__dirname + '/node/table'); -var JoinNode = require(__dirname + '/node/join'); -var LiteralNode = require(__dirname + '/node/literal'); -var Joiner = require(__dirname + '/joiner'); -var ForeignKeyNode = require(__dirname + '/node/foreignKey'); +var Query = require('./node/query'); +var Column = require('./column'); +var TableNode = require('./node/table'); +var JoinNode = require('./node/join'); +var LiteralNode = require('./node/literal'); +var Joiner = require('./joiner'); +var ForeignKeyNode = require('./node/foreignKey'); var Table = function(config) { this._schema = config.schema; From 7187f755da8dcd08a525ca3c9f5cf8e119288b11 Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Fri, 14 Apr 2017 17:59:50 -0500 Subject: [PATCH 41/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6b984199..8a61259b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.74.0", + "version": "0.75.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From 6defdca12271765a3aef057a94241527aa6bad1e Mon Sep 17 00:00:00 2001 From: Santiago Castro Date: Mon, 17 Apr 2017 13:42:29 -0300 Subject: [PATCH 42/62] Fix broken Markdown headings (#353) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 193ab2be..e1a3af8c 100644 --- a/README.md +++ b/README.md @@ -183,5 +183,5 @@ Usually after a few high-quality pull requests and friendly interactions we will After all, open source belongs to everyone. -##license +## license MIT From f9420542eddbb3479b21f635644617de7629e4b5 Mon Sep 17 00:00:00 2001 From: 3n-mb <3n-mb@users.noreply.github.com> Date: Tue, 23 May 2017 10:25:04 -0400 Subject: [PATCH 43/62] Adding types v1 (#357) * Create types.d.ts * Add types field --- lib/types.d.ts | 211 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 212 insertions(+) create mode 100644 lib/types.d.ts diff --git a/lib/types.d.ts b/lib/types.d.ts new file mode 100644 index 00000000..d75e59e8 --- /dev/null +++ b/lib/types.d.ts @@ -0,0 +1,211 @@ + +/** + * This is an adaptation of https://github.com/doxout/anydb-sql/blob/4e4c0ff4a7f2efb7f820baaafea1f624f1ae0399/d.ts/anydb-sql.d.ts + * Whole project is MIT licensed, so, we can use it. We also feed back any + * improvements, questions, concerns. + */ +declare module "sql" { + + interface OrderByValueNode {} + + interface Named { + name?: Name; + } + interface ColumnDefinition extends Named { + jsType?: Type; + dataType: string; + primaryKey?: boolean; + references?: { + table:string; + column: string; + onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; + onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; + }; + notNull?: boolean; + unique?: boolean; + defaultValue?: Type; + } + + interface TableDefinition { + name: Name; + schema: string; + columns: {[CName in keyof Row]: ColumnDefinition}; + isTemporary?: boolean; + foreignKeys?: { + table: string, + columns: (keyof Row)[], + refColumns: string[], + onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; + onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; + } + } + + interface QueryLike { + values: any[] + text:string + } + + interface Executable { + toQuery():QueryLike; + } + + interface Queryable extends Executable { + where(...nodes:any[]):Query + delete():ModifyingQuery + select(star: Column): Query; + select(n1: Column):Query<{[N in N1]: T1}>; + select( + n1: Column, + n2: Column):Query<{[N in N1]: T1} & {[N in N2]: T2}> + select( + n1: Column, + n2: Column, + n3: Column):Query<{[N in N1]: T1} & {[N in N2]: T2} & {[N in N3]: T3}> + select(...nodesOrTables:any[]):Query + + } + + interface Query extends Executable, Queryable { + resultType: T; + + from(table:TableNode):Query + from(statement:string):Query + update(o:{[key: string]:any}):ModifyingQuery + update(o:{}):ModifyingQuery + group(...nodes:any[]):Query + order(...criteria:OrderByValueNode[]):Query + limit(l:number):Query + offset(o:number):Query + } + + interface SubQuery { + select(node:Column):SubQuery + select(...nodes: any[]):SubQuery + where(...nodes:any[]):SubQuery + from(table:TableNode):SubQuery + from(statement:string):SubQuery + group(...nodes:any[]):SubQuery + order(criteria:OrderByValueNode):SubQuery + exists():BinaryNode + notExists(): BinaryNode; + notExists(subQuery:SubQuery):BinaryNode + } + + + interface ModifyingQuery extends Executable { + returning(...nodes:any[]):Query + where(...nodes:any[]):ModifyingQuery + } + + interface TableNode { + join(table:TableNode):JoinTableNode + leftJoin(table:TableNode):JoinTableNode + } + + interface JoinTableNode extends TableNode { + on(filter:BinaryNode):TableNode + on(filter:string):TableNode + } + + interface CreateQuery extends Executable { + ifNotExists():Executable + } + interface DropQuery extends Executable { + ifExists():Executable + } + + type Columns = { + [Name in keyof T]: Column + } + type Table = TableNode & Queryable & Named & Columns & { + getName(): string; + getSchema(): string; + + literal(statement: string): any; + + create():CreateQuery + drop():DropQuery + as(name:OtherName):Table + update(o: Partial):ModifyingQuery + insert(row:T):ModifyingQuery + insert(rows:T[]):ModifyingQuery + select():Query + select(...nodes:any[]):Query + from(table:TableNode):Query + from(statement:string):Query + star():Column + subQuery():SubQuery + columns:Column[] + sql: SQL; + alter():AlterQuery; + indexes(): IndexQuery; + } + + interface AlterQuery extends Executable { + addColumn(column:Column): AlterQuery; + addColumn(name: string, options:string): AlterQuery; + dropColumn(column: Column|string): AlterQuery; + renameColumn(column: Column, newColumn: Column):AlterQuery; + renameColumn(column: Column, newName: string):AlterQuery; + renameColumn(name: string, newName: string):AlterQuery; + rename(newName: string): AlterQuery + } + interface IndexQuery { + create(): IndexCreationQuery; + create(indexName: string): IndexCreationQuery; + drop(indexName: string): Executable; + drop(...columns: Column[]): Executable + } + interface IndexCreationQuery extends Executable { + unique(): IndexCreationQuery; + using(name: string): IndexCreationQuery; + on(...columns: (Column|OrderByValueNode)[]): IndexCreationQuery; + withParser(parserName: string): IndexCreationQuery; + fulltext(): IndexCreationQuery; + spatial(): IndexCreationQuery; + } + + interface SQL { + functions: { + LOWER(c:Column):Column + } + } + + interface BinaryNode { + and(node:BinaryNode):BinaryNode + or(node:BinaryNode):BinaryNode + } + + interface Column { + name: Name + in(arr:T[]):BinaryNode + in(subQuery:SubQuery):BinaryNode + notIn(arr:T[]):BinaryNode + equals(node: T|Column):BinaryNode + notEquals(node: T|Column):BinaryNode + gte(node: T|Column):BinaryNode + lte(node: T|Column):BinaryNode + gt(node:T|Column):BinaryNode + lt(node: T|Column):BinaryNode + like(str:string):BinaryNode + multiply:{ + (node:Column):Column + (n:number):Column //todo check column names + } + isNull():BinaryNode + isNotNull():BinaryNode + //todo check column names + sum():Column + count():Column + count(name:string):Column + distinct():Column + as(name:OtherName):Column + ascending:OrderByValueNode + descending:OrderByValueNode + asc:OrderByValueNode + desc:OrderByValueNode + } + + function define(map:TableDefinition): Table; + +} diff --git a/package.json b/package.json index 8a61259b..be22764f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "url": "git://github.com/brianc/node-sql.git" }, "main": "lib/", + "types": "lib/types.d.ts", "scripts": { "test": "node_modules/.bin/mocha", "lint": "jshint lib test", From 0da386cec5348b5816688493ba36b68cd0600ce1 Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Tue, 23 May 2017 11:57:06 -0500 Subject: [PATCH 44/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index be22764f..64cbac97 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.75.0", + "version": "0.76.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From 3688b020749be9e0db8f8290ffcb284e447b06d7 Mon Sep 17 00:00:00 2001 From: Lorenzo Giuliani Date: Thu, 1 Jun 2017 21:10:58 +0200 Subject: [PATCH 45/62] add missing function `setDialect` (#359) add a new type definition: SQLDialects as Union of strings. --- lib/types.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/types.d.ts b/lib/types.d.ts index d75e59e8..84fb7ab3 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -6,6 +6,14 @@ */ declare module "sql" { + type SQLDialects = + | "mssql" + | "mysql" + | "oracle" + | "postgres" + | "sqlite" + ; + interface OrderByValueNode {} interface Named { @@ -30,6 +38,7 @@ declare module "sql" { name: Name; schema: string; columns: {[CName in keyof Row]: ColumnDefinition}; + dialect?: SQLDialects; isTemporary?: boolean; foreignKeys?: { table: string, @@ -207,5 +216,6 @@ declare module "sql" { } function define(map:TableDefinition): Table; + function setDialect(dialect: SQLDialects): void; } From f135dfc6ed49cbcf3dba31bcd5a02c815aa2a0f5 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Thu, 1 Jun 2017 14:11:31 -0500 Subject: [PATCH 46/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64cbac97..b7c9b141 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.76.0", + "version": "0.76.1", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From 1fbaf62b18c20651f2de6d53c2fd7828e9c49987 Mon Sep 17 00:00:00 2001 From: Luke Childs Date: Mon, 31 Jul 2017 15:00:44 +0100 Subject: [PATCH 47/62] Support REPLACE (#368) * Support REPLACE * Add REPLACE to default Postgres dialect * Make sure REPLACE doesn't get prepended with SELECT * * Make sure REPLACE doesn't use table.column syntax * REPLACE throws on unsupported dialects * Move replace to dialect prototypes * Copy MySQL insert syntax for repalce * Run insert tests against replace * Fix misplaced semicolon * Treat subquery parenthesis the same on INSERT and REPLACE --- lib/dialect/mssql.js | 4 + lib/dialect/mysql.js | 36 ++ lib/dialect/oracle.js | 4 + lib/dialect/postgres.js | 10 +- lib/dialect/sqlite.js | 33 ++ lib/node/query.js | 31 + lib/node/replace.js | 70 +++ lib/table.js | 11 + test/dialects/replace-tests.js | 1000 ++++++++++++++++++++++++++++++++ 9 files changed, 1197 insertions(+), 2 deletions(-) create mode 100644 lib/node/replace.js create mode 100644 test/dialects/replace-tests.js diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index 3606f5b6..488f7fa2 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -30,6 +30,10 @@ Mssql.prototype._quoteCharacter = '['; Mssql.prototype._arrayAggFunctionName = ''; +Mssql.prototype.visitReplace = function(replace) { + throw new Error('Mssql does not support REPLACE.'); +}; + Mssql.prototype._getParameterPlaceholder = function(index, value) { if (this.config.questionMarkParameterPlaceholder) return '?'; return '@' + index; diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js index b2fc2c26..0a63f3be 100644 --- a/lib/dialect/mysql.js +++ b/lib/dialect/mysql.js @@ -20,6 +20,42 @@ Mysql.prototype._quoteCharacter = '`'; Mysql.prototype._arrayAggFunctionName = 'GROUP_CONCAT'; +Mysql.prototype.visitReplace = function(replace) { + var self = this; + // don't use table.column for replaces + this._visitedReplace = true; + + var result = ['REPLACE']; + result = result.concat(replace.nodes.map(this.visit.bind(this))); + result.push('INTO ' + this.visit(this._queryNode.table.toNode())); + result.push('(' + replace.columns.map(this.visit.bind(this)).join(', ') + ')'); + + var paramNodes = replace.getParameters(); + + if (paramNodes.length > 0) { + var paramText = paramNodes.map(function (paramSet) { + return paramSet.map(function (param) { + return self.visit(param); + }).join(', '); + }).map(function (param) { + return '('+param+')'; + }).join(', '); + + result.push('VALUES', paramText); + + if (result.slice(2, 5).join(' ') === '() VALUES ()') { + result.splice(2, 3, 'DEFAULT VALUES'); + } + } + + this._visitedReplace = false; + + if (result[2] === 'DEFAULT VALUES') { + result[2] = '() VALUES ()'; + } + return result; +}; + Mysql.prototype._getParameterPlaceholder = function() { return '?'; }; diff --git a/lib/dialect/oracle.js b/lib/dialect/oracle.js index b1f95eec..38267339 100644 --- a/lib/dialect/oracle.js +++ b/lib/dialect/oracle.js @@ -17,6 +17,10 @@ util.inherits(Oracle, Postgres); Oracle.prototype._myClass = Oracle; +Oracle.prototype.visitReplace = function(replace) { + throw new Error('Oracle does not support REPLACE.'); +}; + Oracle.prototype._aliasText = ' '; Oracle.prototype._getParameterPlaceholder = function(index, value) { /* jshint unused: false */ diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 6e67c38e..2816c901 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -131,6 +131,7 @@ Postgres.prototype.visit = function(node) { case 'SUBQUERY' : return this.visitSubquery(node); case 'SELECT' : return this.visitSelect(node); case 'INSERT' : return this.visitInsert(node); + case 'REPLACE' : return this.visitReplace(node); case 'UPDATE' : return this.visitUpdate(node); case 'DELETE' : return this.visitDelete(node); case 'CREATE' : return this.visitCreate(node); @@ -266,6 +267,10 @@ Postgres.prototype.visitInsert = function(insert) { return result; }; +Postgres.prototype.visitReplace = function(replace) { + throw new Error('Postgres does not support REPLACE.'); +}; + Postgres.prototype.visitUpdate = function(update) { // don't auto-generate from clause var params = []; @@ -624,6 +629,7 @@ Postgres.prototype.visitQuery = function(queryNode) { break; case "INDEXES": case "INSERT": + case "REPLACE": case "UPDATE": case "CREATE": case "DROP": @@ -725,7 +731,7 @@ Postgres.prototype.visitTable = function(tableNode) { Postgres.prototype.visitColumn = function(columnNode) { var table = columnNode.table; - var inInsertUpdateClause = this._visitedInsert || this._visitingUpdateTargetColumn; + var inInsertUpdateClause = this._visitedInsert || this._visitedReplace || this._visitingUpdateTargetColumn; var inDdlClause = this._visitingAddColumn || this._visitingAlter || this._visitingCreate; var inSelectClause = this.visitingReturning || @@ -1211,7 +1217,7 @@ Postgres.prototype.handleDistinct = function(actions,filters) { function dontParenthesizeSubQuery(parentQuery){ if (!parentQuery) return false; if (parentQuery.nodes.length === 0) return false; - if (parentQuery.nodes[0].type != 'INSERT') return false; + if (['INSERT', 'REPLACE'].indexOf(parentQuery.nodes[0].type) === -1) return false; return true; } diff --git a/lib/dialect/sqlite.js b/lib/dialect/sqlite.js index af2c3ef4..60d46997 100644 --- a/lib/dialect/sqlite.js +++ b/lib/dialect/sqlite.js @@ -19,6 +19,39 @@ Sqlite.prototype._myClass = Sqlite; Sqlite.prototype._arrayAggFunctionName = 'GROUP_CONCAT'; +Sqlite.prototype.visitReplace = function(replace) { + var self = this; + // don't use table.column for replaces + this._visitedReplace = true; + + var result = ['REPLACE']; + result = result.concat(replace.nodes.map(this.visit.bind(this))); + result.push('INTO ' + this.visit(this._queryNode.table.toNode())); + result.push('(' + replace.columns.map(this.visit.bind(this)).join(', ') + ')'); + + var paramNodes = replace.getParameters(); + + if (paramNodes.length > 0) { + var paramText = paramNodes.map(function (paramSet) { + return paramSet.map(function (param) { + return self.visit(param); + }).join(', '); + }).map(function (param) { + return '('+param+')'; + }).join(', '); + + result.push('VALUES', paramText); + + if (result.slice(2, 5).join(' ') === '() VALUES ()') { + result.splice(2, 3, 'DEFAULT VALUES'); + } + } + + this._visitedReplace = false; + + return result; +}; + Sqlite.prototype._getParameterValue = function(value) { if (Buffer.isBuffer(value)) { value = 'x' + this._getParameterValue(value.toString('hex')); diff --git a/lib/node/query.js b/lib/node/query.js index 674d07b7..973267bc 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -15,6 +15,7 @@ var OrderBy = require('./orderBy'); var GroupBy = require('./groupBy'); var Having = require('./having'); var Insert = require('./insert'); +var Replace = require('./replace'); var Update = require('./update'); var Delete = require('./delete'); var Returning = require('./returning'); @@ -224,6 +225,36 @@ var Query = Node.define({ }, + replace: function(o) { + var self = this; + + var args = sliced(arguments); + // object literal + if (arguments.length === 1 && !o.toNode && !o.forEach) { + args = []; + Object.keys(o).forEach(function(key) { + var col = self.table.get(key); + if(col && !col.autoGenerated) + args.push(col.value(o[key])); + }); + } else if (o.forEach) { + o.forEach(function(arg) { + return self.replace.call(self, arg); + }); + return self; + } + + if (self.replaceClause) { + self.replaceClause.add(args); + return self; + } else { + self.replaceClause = new Replace(); + self.replaceClause.add(args); + return self.add(self.replaceClause); + } + + }, + update: function(o) { var self = this; var update = new Update(); diff --git a/lib/node/replace.js b/lib/node/replace.js new file mode 100644 index 00000000..d17099d2 --- /dev/null +++ b/lib/node/replace.js @@ -0,0 +1,70 @@ +'use strict'; + +var DefaultNode = require('./default'); +var Node = require('./'); +var ParameterNode = require('./parameter'); + +var Replace = Node.define({ + type: 'REPLACE', + constructor: function () { + Node.call(this); + this.names = []; + this.columns = []; + this.valueSets = []; + } +}); + +module.exports = Replace; + +Replace.prototype.add = function (nodes) { + var hasColumns = false; + var hasValues = false; + var self = this; + var values = {}; + nodes.forEach(function (node) { + var column = node.toNode(); + var name = column.name; + var idx = self.names.indexOf(name); + if (idx < 0) { + self.names.push(name); + self.columns.push(column); + } + hasColumns = true; + hasValues = hasValues || column.value !== undefined; + values[name] = column; + }); + + // When none of the columns have a value, it's ambiguous whether the user + // intends to replace a row of default values or append a SELECT statement + // 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); + } + + return self; +}; + +/* + * Get parameters for all values to be replaced. This function + * handles handles bulk replaces, where keys may be present + * in some objects and not others. When keys are not present, + * the replace should refer to the column value as DEFAULT. + */ +Replace.prototype.getParameters = function () { + var self = this; + return this.valueSets + .map(function (nodeDict) { + var set = []; + self.names.forEach(function (name) { + var node = nodeDict[name]; + if (node) { + set.push(ParameterNode.getNodeOrParameterNode(node.value)); + } + else { + set.push(new DefaultNode()); + } + }); + return set; + }); +}; diff --git a/lib/table.js b/lib/table.js index 23e335a8..3fae2710 100644 --- a/lib/table.js +++ b/lib/table.js @@ -214,6 +214,17 @@ Table.prototype.insert = function() { return query; }; +Table.prototype.replace = function() { + var query = new Query(this); + if(!arguments[0] || (util.isArray(arguments[0]) && arguments[0].length === 0)){ + query.select.call(query, this.star()); + query.where.apply(query,["1=2"]); + } else { + query.replace.apply(query, arguments); + } + return query; +}; + Table.prototype.toNode = function() { return new TableNode(this); }; diff --git a/test/dialects/replace-tests.js b/test/dialects/replace-tests.js new file mode 100644 index 00000000..72af0e4b --- /dev/null +++ b/test/dialects/replace-tests.js @@ -0,0 +1,1000 @@ +'use strict'; + +var Harness = require('./support'); +var post = Harness.definePostTable(); +var user = Harness.defineUserTable(); +var contentTable = Harness.defineContentTable(); +var customerAliasTable = Harness.defineCustomerAliasTable(); + +var arrayTable = require('../../lib/table').define({ + name: 'arraytest', + columns: ['id', 'numbers'] +}); + +Harness.test({ + query: post.replace(post.content.value('test'), post.userId.value(1)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (\'test\', 1)' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (\'test\', 1)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 1] +}); + +Harness.test({ + query: post.replace(post.content.value('whoah')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content") VALUES ($1)', + string: 'REPLACE INTO "post" ("content") VALUES (\'whoah\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`) VALUES (?)', + string: 'REPLACE INTO `post` (`content`) VALUES (\'whoah\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['whoah'] +}); + +Harness.test({ + query: post.replace({length: 0}), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("length") VALUES ($1)', + string: 'REPLACE INTO "post" ("length") VALUES (0)' + }, + mysql: { + text : 'REPLACE INTO `post` (`length`) VALUES (?)', + string: 'REPLACE INTO `post` (`length`) VALUES (0)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [0] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (\'test\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.replace({ + content: post.sql.functions.LOWER('TEST'), + userId: 2 + }), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES (LOWER($1), $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (LOWER(\'TEST\'), 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['TEST', 2] +}); + +// allow bulk replace +Harness.test({ + query: post.replace([{ + content: 'whoah' + }, { + content: 'hey' + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content") VALUES ($1), ($2)', + string: 'REPLACE INTO "post" ("content") VALUES (\'whoah\'), (\'hey\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['whoah', 'hey'] +}); + +Harness.test({ + query: post.replace([{ + content: 'whoah', + userId: 1 + }, { + content: 'hey', + userId: 2 + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2), ($3, $4)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (\'whoah\', 1), (\'hey\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['whoah', 1, 'hey', 2] +}); + +// consistent order +Harness.test({ + query: post.replace([{ + content: 'whoah', + userId: 1 + }, { + userId: 2, + content: 'hey' + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2), ($3, $4)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (\'whoah\', 1), (\'hey\', 2)' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?), (?, ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (\'whoah\', 1), (\'hey\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['whoah', 1, 'hey', 2] +}); + +Harness.test({ + query: post.replace({}), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" DEFAULT VALUES', + string: 'REPLACE INTO "post" DEFAULT VALUES' + }, + mysql: { + text : 'REPLACE INTO `post` () VALUES ()', + string: 'REPLACE INTO `post` () VALUES ()' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning('*'), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning(post.star()), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning(post.id), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning(post.id, post.content), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning([post.id, post.content]), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +// handle missing columns +Harness.test({ + query: post.replace([{ + content: 'whoah', + userId: 1 + }, { + content: 'hey' + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'Sqlite requires the same number of columns in each replace row', + throws: true + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?), (?, DEFAULT)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (\'whoah\', 1), (\'hey\', DEFAULT)', + params: ['whoah', 1, 'hey'] + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, +}); + +Harness.test({ + query: post.replace([{ + userId: 1 + }, { + content: 'hey', + userId: 2 + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'Sqlite requires the same number of columns in each replace row', + throws: true + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`, `content`) VALUES (?, DEFAULT), (?, ?)', + string: 'REPLACE INTO `post` (`userId`, `content`) VALUES (1, DEFAULT), (2, \'hey\')', + params: [1, 2, 'hey'] + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, +}); + +Harness.test({ + query: post.replace(post.content, post.userId) + .select('\'test\'', user.id).from(user).where(user.name.like('A%')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" LIKE $1)', + string: 'REPLACE INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" LIKE \'A%\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) SELECT \'test\', `user`.`id` FROM `user` WHERE (`user`.`name` LIKE ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) SELECT \'test\', `user`.`id` FROM `user` WHERE (`user`.`name` LIKE \'A%\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['A%'] +}); + +Harness.test({ + query: post.replace([post.content, post.userId]) + .select('\'test\'', user.id).from(user).where(user.name.like('A%')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" LIKE $1)', + string: 'REPLACE INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" LIKE \'A%\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) SELECT \'test\', `user`.`id` FROM `user` WHERE (`user`.`name` LIKE ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) SELECT \'test\', `user`.`id` FROM `user` WHERE (`user`.`name` LIKE \'A%\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['A%'] +}); + +Harness.test({ + query: post.replace(post.userId) + .select(user.id).from(user).where(user.name.like('A%')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user" WHERE ("user"."name" LIKE $1)', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user" WHERE ("user"."name" LIKE \'A%\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['A%'] +}); + +Harness.test({ + query: post.replace(post.userId) + .select(post.userId).from(user.join(post).on(user.id.equals(post.userId))).where(post.tags.like('A%')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "post"."userId" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("post"."tags" LIKE $1)', + string: 'REPLACE INTO "post" ("userId") SELECT "post"."userId" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("post"."tags" LIKE \'A%\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `post`.`userId` FROM `user` INNER JOIN `post` ON (`user`.`id` = `post`.`userId`) WHERE (`post`.`tags` LIKE ?)', + string: 'REPLACE INTO `post` (`userId`) SELECT `post`.`userId` FROM `user` INNER JOIN `post` ON (`user`.`id` = `post`.`userId`) WHERE (`post`.`tags` LIKE \'A%\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['A%'] +}); + +Harness.test({ + query: post.replace(post.userId).select(user.id).distinct().from(user), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT DISTINCT "user"."id" FROM "user"', + string: 'REPLACE INTO "post" ("userId") SELECT DISTINCT "user"."id" FROM "user"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT DISTINCT `user`.`id` FROM `user`', + string: 'REPLACE INTO `post` (`userId`) SELECT DISTINCT `user`.`id` FROM `user`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +// Binary replaces +Harness.test({ + query: post.replace(post.content.value(new Buffer('test')), post.userId.value(2)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (x\'74657374\', 2)' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (x\'74657374\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [new Buffer('test'), 2] +}); + +Harness.test({ + query: post.replace({ + content: new Buffer('test'), + userId: 2 + }), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (x\'74657374\', 2)' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (x\'74657374\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [new Buffer('test'), 2] +}); + +Harness.test({ + query: post.replace([{ + content: new Buffer('whoah') + }, { + content: new Buffer('hey') + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content") VALUES ($1), ($2)', + string: 'REPLACE INTO "post" ("content") VALUES (x\'77686f6168\'), (x\'686579\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`) VALUES (?), (?)', + string: 'REPLACE INTO `post` (`content`) VALUES (x\'77686f6168\'), (x\'686579\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [new Buffer('whoah'), new Buffer('hey')] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onDuplicate({ + content: 'testupdate', + }), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `post`.`content` = ?', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (\'test\', 2) ON DUPLICATE KEY UPDATE `post`.`content` = \'testupdate\'' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2, 'testupdate'] +}); + +Harness.test({ + query: customerAliasTable.replace({ + id : 2, + name : 'test' + }).onConflict({ + columns: ['id'], + update: ['name'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [2, 'test'] +}); + +Harness.test({ + query: customerAliasTable.replace({ + id : 2, + name : 'test' + }).orIgnore(), + mysql: { + throws: true + }, + sqlite: { + text : 'REPLACE OR IGNORE INTO "customer" ("id", "name") VALUES ($1, $2)', + string: 'REPLACE OR IGNORE INTO "customer" ("id", "name") VALUES (2, \'test\')' + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [2, 'test'] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + update: ['content'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId','content'], + update: ['content','userId'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + update: ['content'] + }).where(post.userId.equals(2)), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2, 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + constraint: 'conc_userId', + update: ['content'] + }).where(post.userId.equals(2)), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2, 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + constraint: 'conc_userId', + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: contentTable.replace({ + contentId: 20, + text : "something" + }).onConflict({ + columns: ['contentId'], + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [20, "something"] +}); + +Harness.test({ + query: contentTable.replace({ + contentId: 20, + text : "something", + contentPosts : "another thing", + }).onConflict({ + columns: ['contentId'], + update: ['contentPosts'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [20, "something", "another thing"] +}); + +Harness.test({ + query: post.replace([]), + + mysql: { + text : 'SELECT `post`.* FROM `post` WHERE (1=2)', + string: 'SELECT `post`.* FROM `post` WHERE (1=2)' + }, + params: [] +}); + +Harness.test({ + query: arrayTable.replace(arrayTable.id.value(1), arrayTable.numbers.value([2, 3, 4])), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'REPLACE INTO "arraytest" ("id", "numbers") VALUES (1, \'[2,3,4]\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + } +}); + +Harness.test({ + query: arrayTable.replace(arrayTable.id.value(1), arrayTable.numbers.value(["one", "two", "three"])), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'REPLACE INTO "arraytest" ("id", "numbers") VALUES (1, \'["one","two","three"]\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + } +}); + +Harness.test({ + query: post.replace(post.userId).select(user.id).from(user), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace(post.userId).select(user.id).from(user).onConflict({ + columns: ['userId'], + update: ['content'] + }), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace(post.userId).add(user.select(user.id)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace(post.userId).add(user.select(user.id).from(user)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace(post.userId).add(user.select(user.id).order(user.id)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user` ORDER BY `user`.`id`', + string: 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user` ORDER BY `user`.`id`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); From 9173f92ea375ef86b315163ae1c2cb094ea98a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rd=C3=B6gh=20L=C3=A1szl=C3=B3?= Date: Mon, 31 Jul 2017 16:01:10 +0200 Subject: [PATCH 48/62] Fix order method with empty array generates invalid SQL (#364) --- lib/node/query.js | 3 +++ test/dialects/order-tests.js | 28 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/node/query.js b/lib/node/query.js index 973267bc..ebb3ecfa 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -173,6 +173,9 @@ var Query = Node.define({ order: function() { var args = getArrayOrArgsAsArray(arguments); var orderBy; + if (args.length === 0) { + return this; + } if (this._orderBy) { orderBy = this._orderBy; } else { diff --git a/test/dialects/order-tests.js b/test/dialects/order-tests.js index 2c4803f5..a0813d8e 100644 --- a/test/dialects/order-tests.js +++ b/test/dialects/order-tests.js @@ -336,4 +336,30 @@ Harness.test({ string: 'SELECT "post"."content" FROM "post" ORDER BY "post"."content"' }, params: [] -}); \ No newline at end of file +}); + +Harness.test({ + query: post.select(post.content).order([]), + pg: { + text : 'SELECT "post"."content" FROM "post"', + string: 'SELECT "post"."content" FROM "post"' + }, + sqlite: { + text : 'SELECT "post"."content" FROM "post"', + string: 'SELECT "post"."content" FROM "post"' + }, + mysql: { + text : 'SELECT `post`.`content` FROM `post`', + string: 'SELECT `post`.`content` FROM `post`' + }, + mssql: { + text : 'SELECT [post].[content] FROM [post]', + string: 'SELECT [post].[content] FROM [post]' + }, + oracle: { + text : 'SELECT "post"."content" FROM "post"', + string: 'SELECT "post"."content" FROM "post"' + }, + params: [] +}); + From 9135b0bbfd44d5d5a326022096a83d78427598e3 Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Mon, 31 Jul 2017 09:02:20 -0500 Subject: [PATCH 49/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b7c9b141..36ce3057 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.76.1", + "version": "0.77.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From c7334f3e34006a8fd604eaf72f841c546db7751a Mon Sep 17 00:00:00 2001 From: hstanford <30746076+hstanford@users.noreply.github.com> Date: Sun, 6 Aug 2017 21:09:50 +0100 Subject: [PATCH 50/62] Exposes function creation to allow use of unsupported functions (#371) --- lib/functions.js | 4 ++++ lib/index.js | 1 + test/function-tests.js | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/lib/functions.js b/lib/functions.js index 4362b706..fe9cdaaf 100644 --- a/lib/functions.js +++ b/lib/functions.js @@ -13,6 +13,9 @@ var getFunctionCallCreator = function(name) { // creates a hash of functions for a sql instance var getFunctions = function(functionNames) { + if (typeof functionNames === 'string') + return getFunctionCallCreator(functionNames); + var functions = _.reduce(functionNames, function(reducer, name) { reducer[name] = getFunctionCallCreator(name); return reducer; @@ -68,4 +71,5 @@ var getStandardFunctions = function() { return getFunctions(standardFunctionNames); }; +module.exports.getFunctions = getFunctions; module.exports.getStandardFunctions = getStandardFunctions; diff --git a/lib/index.js b/lib/index.js index 3a1cdfdc..f6cf39e6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -19,6 +19,7 @@ var Sql = function(dialect, config) { // attach the standard SQL functions to this instance this.functions = functions.getStandardFunctions(); + this.function = functions.getFunctions; }; // Define a table diff --git a/test/function-tests.js b/test/function-tests.js index 77ef0087..6c53072d 100644 --- a/test/function-tests.js +++ b/test/function-tests.js @@ -84,4 +84,10 @@ suite('function', function() { assert.equal(query.text, 'SELECT (AVG((DISTINCT((COUNT("user"."id") + MAX("user"."id"))) - MIN("user"."id"))) * $1) FROM "user"'); assert.equal(query.values[0], 100); }); + + test('use custom function', function() { + var query = user.select(sql.function('PHRASE_TO_TSQUERY')('simple', user.name)).toQuery(); + assert.equal(query.text, 'SELECT PHRASE_TO_TSQUERY($1, "user"."name") FROM "user"'); + assert.equal(query.values[0], 'simple'); + }); }); From 95d775a9a10650c2de34edf23a1a859abf7744dc Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Sun, 6 Aug 2017 15:10:02 -0500 Subject: [PATCH 51/62] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36ce3057..dc250c01 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.77.0", + "version": "0.78.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { From 6916535a96633548d5da31d8b74890ce2f087513 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 11:55:40 -0400 Subject: [PATCH 52/62] - Support for "left join lateral" in Postgres which converts to "outer apply" in Microsoft SQL Server and Oracle. --- lib/dialect/mssql.js | 10 +++++++ lib/dialect/oracle.js | 5 ++++ lib/dialect/postgres.js | 11 ++++++++ lib/node/join.js | 3 +++ lib/table.js | 4 +++ test/dialects/join-tests.js | 54 +++++++++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+) diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index 488f7fa2..6a19b564 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -274,6 +274,16 @@ Mssql.prototype.visitFunctionCall = function(functionCall) { return [txt]; }; +Mssql.prototype.visitJoin = function(join) { + if (join.subType !== 'LEFT LATERAL') return Mssql.super_.prototype.visitJoin.call(this, join); + var result = []; + this._visitingJoin = true; + result = result.concat(this.visit(join.from)); + result = result.concat('OUTER APPLY'); + result = result.concat(this.visit(join.to)); + return result; +}; + Mssql.prototype.visitOrderBy = function(orderBy) { var result=Mssql.super_.prototype.visitOrderBy.call(this, orderBy); var offsetNode=orderBy.msSQLOffsetNode; diff --git a/lib/dialect/oracle.js b/lib/dialect/oracle.js index 38267339..a55e7aca 100644 --- a/lib/dialect/oracle.js +++ b/lib/dialect/oracle.js @@ -278,6 +278,11 @@ Oracle.prototype.visitCase = function(caseExp) { return Mssql.prototype.visitCase.call(this, caseExp); }; +// Using same JOIN implementation as MSSQL +Oracle.prototype.visitJoin = function(joinExp) { + return Mssql.prototype.visitJoin.call(this, joinExp); +}; + Oracle.prototype.visitOnConflict = function(onConflict) { throw new Error('Oracle does not allow onConflict clause.'); }; diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 2816c901..87c7c9e9 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -1022,6 +1022,7 @@ Postgres.prototype.visitForShare = function() { }; Postgres.prototype.visitJoin = function(join) { + if (join.subType === 'LEFT LATERAL') return this.visitLeftJoinLateral(join) var result = []; this._visitingJoin = true; result = result.concat(this.visit(join.from)); @@ -1032,6 +1033,16 @@ Postgres.prototype.visitJoin = function(join) { return result; }; +Postgres.prototype.visitLeftJoinLateral = function(join) { + var result = []; + this._visitingJoin = true; + result = result.concat(this.visit(join.from)); + result = result.concat('LEFT JOIN LATERAL'); + result = result.concat(this.visit(join.to)); + result = result.concat('ON true'); + return result; +}; + Postgres.prototype.visitLiteral = function(node) { var txt = [node.literal]; if(node.alias) { diff --git a/lib/node/join.js b/lib/node/join.js index bbc1f93a..f569997e 100644 --- a/lib/node/join.js +++ b/lib/node/join.js @@ -19,5 +19,8 @@ var JoinNode = module.exports = Node.define({ }, leftJoin: function(other) { return new JoinNode('LEFT', this, other); + }, + leftJoinLateral: function(other) { + return new JoinNode('LEFT LATERAL', this, other); } }); diff --git a/lib/table.js b/lib/table.js index 3fae2710..1408542b 100644 --- a/lib/table.js +++ b/lib/table.js @@ -237,6 +237,10 @@ Table.prototype.leftJoin = function(other) { return new JoinNode('LEFT', this.toNode(), other.toNode()); }; +Table.prototype.leftJoinLateral = function(other) { + return new JoinNode('LEFT LATERAL', this.toNode(), other.toNode()); +}; + // auto-join tables based on column intropsection Table.prototype.joinTo = function(other) { return Joiner.leftJoin(this, other); diff --git a/test/dialects/join-tests.js b/test/dialects/join-tests.js index abebdb01..2e0c6882 100644 --- a/test/dialects/join-tests.js +++ b/test/dialects/join-tests.js @@ -174,3 +174,57 @@ Harness.test({ }, params: [] }); + +Harness.test({ + query: user.select().from(user.leftJoinLateral(post.subQuery().select(post.userId))), + pg: { + text : 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post") ON true', + string: 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post") ON true' + }, + mssql: { + text : 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post])', + string: 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post])' + }, + oracle: { + text : 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post")', + string: 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post")' + }, + params: [] +}); + +Harness.test({ + query: user.select().from(user.leftJoinLateral(post.subQuery().select(post.userId).where(user.id.equals(post.userId)))), + pg: { + text : 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post" WHERE ("user"."id" = "post"."userId")) ON true', + string: 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post" WHERE ("user"."id" = "post"."userId")) ON true' + }, + mssql: { + text : 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post] WHERE ([user].[id] = [post].[userId]))', + string: 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post] WHERE ([user].[id] = [post].[userId]))' + }, + oracle: { + text : 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post" WHERE ("user"."id" = "post"."userId"))', + string: 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post" WHERE ("user"."id" = "post"."userId"))' + }, + params: [] +}); + +Harness.test({ + query: user.select().from(user + .leftJoinLateral(post.subQuery().select(post.userId)) + .leftJoinLateral(comment.subQuery().select(comment.postId))), + pg: { + text : 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post") ON true LEFT JOIN LATERAL (SELECT "comment"."postId" FROM "comment") ON true', + string: 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post") ON true LEFT JOIN LATERAL (SELECT "comment"."postId" FROM "comment") ON true' + }, + mssql: { + text : 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post]) OUTER APPLY (SELECT [comment].[postId] FROM [comment])', + string: 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post]) OUTER APPLY (SELECT [comment].[postId] FROM [comment])' + }, + oracle: { + text : 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post") OUTER APPLY (SELECT "comment"."postId" FROM "comment")', + string: 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post") OUTER APPLY (SELECT "comment"."postId" FROM "comment")' + }, + params: [] +}); + From ed315d24da96e5ed332a3ffcb9d82dfadc808e3e Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 12:08:55 -0400 Subject: [PATCH 53/62] - Override for Microsoft SQL Server concatenation operator. --- lib/dialect/mssql.js | 5 ++++ test/dialects/value-expression-tests.js | 34 ++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index 488f7fa2..92229e06 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -48,6 +48,11 @@ Mssql.prototype.visitBinary = function(binary) { return [text]; } + if(binary.operator === '||'){ + var text = '(' + this.visit(binary.left) + ' + ' + this.visit(binary.right) + ')'; + return [text]; + } + if (!isRightSideArray(binary)){ return Mssql.super_.prototype.visitBinary.call(this, binary); } diff --git a/test/dialects/value-expression-tests.js b/test/dialects/value-expression-tests.js index 5eaae5ae..d5c7fcd4 100644 --- a/test/dialects/value-expression-tests.js +++ b/test/dialects/value-expression-tests.js @@ -114,20 +114,46 @@ Harness.test({ query: post.select(post.id).where(post.content.equals(new Buffer('test'))), pg: { text : 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = $1)', - string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = \'\\x74657374\')', + string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = \'\\x74657374\')' }, sqlite: { text : 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = $1)', - string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = x\'74657374\')', + string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = x\'74657374\')' }, mysql: { text : 'SELECT `post`.`id` FROM `post` WHERE (`post`.`content` = ?)', - string: 'SELECT `post`.`id` FROM `post` WHERE (`post`.`content` = x\'74657374\')', + string: 'SELECT `post`.`id` FROM `post` WHERE (`post`.`content` = x\'74657374\')' }, oracle: { text : 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = :1)', - string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = utl_raw.cast_to_varchar2(hextoraw(\'74657374\')))', + string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = utl_raw.cast_to_varchar2(hextoraw(\'74657374\')))' }, params: [new Buffer('test')] }); +// concat tests +Harness.test({ + query: post.select(post.content.concat(post.tags)), + pg: { + text : 'SELECT ("post"."content" || "post"."tags") FROM "post"', + string: 'SELECT ("post"."content" || "post"."tags") FROM "post"' + }, + sqlite: { + text : 'SELECT ("post"."content" || "post"."tags") FROM "post"', + string: 'SELECT ("post"."content" || "post"."tags") FROM "post"' + }, + mysql: { + text : 'SELECT (`post`.`content` || `post`.`tags`) FROM `post`', + string: 'SELECT (`post`.`content` || `post`.`tags`) FROM `post`' + }, + mssql: { + text : 'SELECT ([post].[content] + [post].[tags]) FROM [post]', + string: 'SELECT ([post].[content] + [post].[tags]) FROM [post]' + }, + oracle: { + text : 'SELECT ("post"."content" || "post"."tags") FROM "post"', + string: 'SELECT ("post"."content" || "post"."tags") FROM "post"' + }, + params: [] +}); + From b0c1622ab53192b099db081b6bdbe58461a8d0f6 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 12:21:39 -0400 Subject: [PATCH 54/62] Tweak so that travis checks will pass --- lib/dialect/mssql.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index 92229e06..413ebd9b 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -49,8 +49,7 @@ Mssql.prototype.visitBinary = function(binary) { } if(binary.operator === '||'){ - var text = '(' + this.visit(binary.left) + ' + ' + this.visit(binary.right) + ')'; - return [text]; + return ['(' + this.visit(binary.left) + ' + ' + this.visit(binary.right) + ')']; } if (!isRightSideArray(binary)){ From 44f281f02ccda77b0a883fcc3a3c44c912f7f50d Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 12:23:21 -0400 Subject: [PATCH 55/62] Added missing semicolon. --- lib/dialect/postgres.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 87c7c9e9..9b81af25 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -1022,7 +1022,7 @@ Postgres.prototype.visitForShare = function() { }; Postgres.prototype.visitJoin = function(join) { - if (join.subType === 'LEFT LATERAL') return this.visitLeftJoinLateral(join) + if (join.subType === 'LEFT LATERAL') return this.visitLeftJoinLateral(join); var result = []; this._visitingJoin = true; result = result.concat(this.visit(join.from)); From 845e4cc2986041c5c3459975e345701e0e65c692 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 12:31:01 -0400 Subject: [PATCH 56/62] - Update ".travis.yml" to allow failures on Node 0.10 and 0.11 since the mocha library is doing stuff that isn't allowed. --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index f04dabe6..07becfb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,8 @@ node_js: - "4.2" - "0.10" - "0.11" + +matrix: + allow_failures: + - node_js: "0.10" + - node_js: "0.11" \ No newline at end of file From 8e5a36ba8691659e4820ff8ee31b2c0507f548cf Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 12:33:59 -0400 Subject: [PATCH 57/62] - Update ".travis.yml" to allow failures on Node 0.10 and 0.11 since the mocha library is doing stuff that isn't allowed. --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index f04dabe6..07becfb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,8 @@ node_js: - "4.2" - "0.10" - "0.11" + +matrix: + allow_failures: + - node_js: "0.10" + - node_js: "0.11" \ No newline at end of file From 761469e033d35eefc1c97292be1b0bf58d61c317 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 13:27:58 -0400 Subject: [PATCH 58/62] Top-Level SubQuery support. --- lib/index.js | 12 ++++++++++++ test/dialects/subquery-tests.js | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/index.js b/lib/index.js index f6cf39e6..8e3d7cfa 100644 --- a/lib/index.js +++ b/lib/index.js @@ -52,6 +52,18 @@ Sql.prototype.select = function() { return query; }; +// Returns a subQuery clause +Sql.prototype.subQuery = function(alias) { + // create the query and pass it off + var query = new Query(this); + query.type = 'SUBQUERY'; + query.alias = alias; + query.join = function(other) { + return new JoinNode('INNER', this.toNode(), other.toNode(), other); + }; + return query; +}; + // Returns an interval clause Sql.prototype.interval = function() { var interval = new Interval(sliced(arguments)); diff --git a/test/dialects/subquery-tests.js b/test/dialects/subquery-tests.js index fe5b2aad..7d977b43 100644 --- a/test/dialects/subquery-tests.js +++ b/test/dialects/subquery-tests.js @@ -219,3 +219,29 @@ Harness.test({ params: [] }); +// Top-level subQuery +Harness.test({ + query: Sql.subQuery().select(user.id).from(user), + pg: { + text : '(SELECT "user"."id" FROM "user")', + string: '(SELECT "user"."id" FROM "user")' + }, + sqlite: { + text : '(SELECT "user"."id" FROM "user")', + string: '(SELECT "user"."id" FROM "user")' + }, + mysql: { + text : '(SELECT `user`.`id` FROM `user`)', + string: '(SELECT `user`.`id` FROM `user`)' + }, + mssql: { + text : '(SELECT [user].[id] FROM [user])', + string: '(SELECT [user].[id] FROM [user])' + }, + oracle: { + text : '(SELECT "user"."id" FROM "user")', + string: '(SELECT "user"."id" FROM "user")' + }, + params: [] +}); + From c5c2e666acd8401a6c91a4a6c93704a4aaa4b954 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 13:30:38 -0400 Subject: [PATCH 59/62] Added test for aliased top-level subQuery --- test/dialects/subquery-tests.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/dialects/subquery-tests.js b/test/dialects/subquery-tests.js index 7d977b43..05d76f1f 100644 --- a/test/dialects/subquery-tests.js +++ b/test/dialects/subquery-tests.js @@ -245,3 +245,29 @@ Harness.test({ params: [] }); +// Top-level subQuery with alias +Harness.test({ + query: Sql.subQuery("x").select(user.id).from(user), + pg: { + text : '(SELECT "user"."id" FROM "user") "x"', + string: '(SELECT "user"."id" FROM "user") "x"' + }, + sqlite: { + text : '(SELECT "user"."id" FROM "user") "x"', + string: '(SELECT "user"."id" FROM "user") "x"' + }, + mysql: { + text : '(SELECT `user`.`id` FROM `user`) `x`', + string: '(SELECT `user`.`id` FROM `user`) `x`' + }, + mssql: { + text : '(SELECT [user].[id] FROM [user]) [x]', + string: '(SELECT [user].[id] FROM [user]) [x]' + }, + oracle: { + text : '(SELECT "user"."id" FROM "user") "x"', + string: '(SELECT "user"."id" FROM "user") "x"' + }, + params: [] +}); + From e8f45252690249c532efd0e7b77f57a8a6553cf7 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 13:37:09 -0400 Subject: [PATCH 60/62] Added missing require. --- lib/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 8e3d7cfa..f26ad827 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,11 +1,12 @@ 'use strict'; var _ = require('lodash'); -var Column = require("./column"); +var Column = require("./column"); var FunctionCall = require('./node/functionCall'); var ArrayCall = require('./node/arrayCall'); var functions = require('./functions'); var getDialect = require('./dialect'); +var JoinNode = require('./node/join'); var Query = require('./node/query'); var sliced = require('sliced'); var Table = require('./table'); From 3b8de4721b2404f4dd9a904be2af722c95588a68 Mon Sep 17 00:00:00 2001 From: Dan Rzeppa Date: Sat, 9 Jun 2018 13:37:49 -0400 Subject: [PATCH 61/62] Update ".travis.yml" to allow failures on Node 0.10 and 0.11 since the mocha library is doing stuff that isn't allowed. --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index f04dabe6..07becfb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,8 @@ node_js: - "4.2" - "0.10" - "0.11" + +matrix: + allow_failures: + - node_js: "0.10" + - node_js: "0.11" \ No newline at end of file From cc15b6720feefda627340838b986852546836130 Mon Sep 17 00:00:00 2001 From: 3n-mb <3n-mb@users.noreply.github.com> Date: Sat, 24 Aug 2024 16:44:34 -0400 Subject: [PATCH 62/62] Type change to allow typescript > 2.7.2 (#421) Typescript versions greater than 2.7.2, `keyof` produces `string|number|symbol`, failing on line 40. One fix is have on lines 19 and 22 `Name extends string|number|symbol` instead of `Name extends string`. But this work around may feel a bit not intuitive. Change directly on line 40, `&` with `string` does the trick of fitting types. --- lib/types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types.d.ts b/lib/types.d.ts index 84fb7ab3..a7011f99 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -37,7 +37,7 @@ declare module "sql" { interface TableDefinition { name: Name; schema: string; - columns: {[CName in keyof Row]: ColumnDefinition}; + columns: {[CName in ((keyof Row) & string)]: ColumnDefinition}; dialect?: SQLDialects; isTemporary?: boolean; foreignKeys?: {