Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/dialect/mssql.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions lib/dialect/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
};
Expand Down
4 changes: 4 additions & 0 deletions lib/dialect/oracle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -253,4 +256,5 @@ function isCountStarExpression(columnNode){
return true;
}


module.exports = Oracle;
31 changes: 31 additions & 0 deletions lib/dialect/postgres.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<update.length; i++) {
setClause.push(this.quote(update[i]) + ' = EXCLUDED.' +this.quote(update[i]));
}
updateClause.push(setClause.join(', '));
}
else
updateClause.push('DO NOTHING');

result.push(updateClause.join(' '));
return result;
};

Postgres.prototype.visitModifier = function(node) {
return [node.type, node.count.type ? this.visit(node.count) : node.count];
};
Expand Down
4 changes: 4 additions & 0 deletions lib/dialect/sqlite.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ Sqlite.prototype.visitOnDuplicate = function() {
throw new Error('SQLite does not allow onDuplicate clause.');
};

Sqlite.prototype.visitOnConflict = function(onConflict) {
throw new Error('Sqlite does not allow onConflict clause.');
};

Sqlite.prototype.visitReturning = function() {
throw new Error('SQLite does not allow returning clause.');
};
Expand Down
7 changes: 7 additions & 0 deletions lib/node/onConflict.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

var Node = require(__dirname);

module.exports = Node.define({
type: 'ONCONFLICT'
});
10 changes: 10 additions & 0 deletions lib/node/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var Update = require('./update');
var Delete = require('./delete');
var Returning = require('./returning');
var OnDuplicate = require('./onDuplicate');
var OnConflict = require('./onConflict');
var ForUpdate = require('./forUpdate');
var ForShare = require('./forShare');
var Create = require('./create');
Expand Down Expand Up @@ -289,6 +290,15 @@ var Query = Node.define({
return self.add(onDuplicate);
},

onConflict: function(o) {
var self = this;

var onConflict = new OnConflict();
Object.keys(o).forEach(function(key) {
onConflict[key] = o[key];
});
return self.add(onConflict);
},

forUpdate: function() {
assert(typeof this._select !== 'undefined', 'FOR UPDATE can be used only in a select statement');
Expand Down
184 changes: 184 additions & 0 deletions test/dialects/insert-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,166 @@ Harness.test({
params: ['test', 2, 'testupdate']
});

Harness.test({
query: post.insert({
content: 'test',
userId: 2
}).onConflict({
columns: ['userId'],
update: ['content']
}),
mysql: {
throws: true
},
sqlite: {
throws: true
},
pg: {
text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content"',
string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content"'
},
mssql: {
throws: true
},
oracle: {
throws: true
},
params: ['test', 2]
});

Harness.test({
query: post.insert({
content: 'test',
userId: 2
}).onConflict({
columns: ['userId','content'],
update: ['content','userId']
}),
mysql: {
throws: true
},
sqlite: {
throws: true
},
pg: {
text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ("userId", "content") DO UPDATE SET "content" = EXCLUDED."content", "userId" = EXCLUDED."userId"',
string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ("userId", "content") DO UPDATE SET "content" = EXCLUDED."content", "userId" = EXCLUDED."userId"'
},
mssql: {
throws: true
},
oracle: {
throws: true
},
params: ['test', 2]
});

Harness.test({
query: post.insert({
content: 'test',
userId: 2
}).onConflict({
columns: ['userId'],
update: ['content']
}).where(post.userId.equals(2)),
mysql: {
throws: true
},
sqlite: {
throws: true
},
pg: {
text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content" WHERE ("post"."userId" = $3)',
string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content" WHERE ("post"."userId" = 2)'
},
mssql: {
throws: true
},
oracle: {
throws: true
},
params: ['test', 2, 2]
});

Harness.test({
query: post.insert({
content: 'test',
userId: 2
}).onConflict({
constraint: 'conc_userId',
update: ['content']
}).where(post.userId.equals(2)),
mysql: {
throws: true
},
sqlite: {
throws: true
},
pg: {
text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ON CONSTRAINT "conc_userId" DO UPDATE SET "content" = EXCLUDED."content" WHERE ("post"."userId" = $3)',
string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ON CONSTRAINT "conc_userId" DO UPDATE SET "content" = EXCLUDED."content" WHERE ("post"."userId" = 2)'
},
mssql: {
throws: true
},
oracle: {
throws: true
},
params: ['test', 2, 2]
});

Harness.test({
query: post.insert({
content: 'test',
userId: 2
}).onConflict({
columns: ['userId'],
}),
mysql: {
throws: true
},
sqlite: {
throws: true
},
pg: {
text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ("userId") DO NOTHING',
string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ("userId") DO NOTHING'
},
mssql: {
throws: true
},
oracle: {
throws: true
},
params: ['test', 2]
});

Harness.test({
query: post.insert({
content: 'test',
userId: 2
}).onConflict({
constraint: 'conc_userId',
}),
mysql: {
throws: true
},
sqlite: {
throws: true
},
pg: {
text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ON CONSTRAINT "conc_userId" DO NOTHING',
string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ON CONSTRAINT "conc_userId" DO NOTHING'
},
mssql: {
throws: true
},
oracle: {
throws: true
},
params: ['test', 2]
});

Harness.test({
query: post.insert([]),

Expand Down Expand Up @@ -701,6 +861,30 @@ Harness.test({
params: []
});

Harness.test({
query: post.insert(post.userId).select(user.id).from(user).onConflict({
columns: ['userId'],
update: ['content']
}),
pg: {
text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content"',
string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content"'
},
sqlite: {
throws: true
},
mysql: {
throws: true
},
mssql: {
throws: true
},
oracle: {
throws: true
},
params: []
});

Harness.test({
query: post.insert(post.userId).add(user.select(user.id)),
pg: {
Expand Down