Skip to content

Commit

Permalink
fix: Replace VALUES() by alias in upsert method
Browse files Browse the repository at this point in the history
  • Loading branch information
mbaumgartl committed Sep 20, 2024
1 parent cf851cf commit 9b747cf
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 17 deletions.
5 changes: 3 additions & 2 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ class Context {
* @param {string} table
* @param {Object|Array.<Object>} data
* @param {Array.<string>|Object} update
* @param {?string} [alias=new]
* @return {Promise}
*/
upsert(table, data, update) {
const sql = upsertStmt(table, data, update);
upsert(table, data, update, alias = 'new') {
const sql = upsertStmt(table, data, update, alias);
return this.exec(sql);
}

Expand Down
5 changes: 3 additions & 2 deletions lib/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,11 @@ class Transaction {
* @param {string} table
* @param {Object|Array.<Object>} data
* @param {Array.<string>|Object} update
* @param {?string} [alias=new]
* @return {Promise}
*/
upsert(table, data, update) {
const sql = upsertStmt(table, data, update);
upsert(table, data, update, alias = 'new') {
const sql = upsertStmt(table, data, update, alias);
return this.exec(sql).then(({ affectedRows, changedRows }) => ({ affectedRows, changedRows }));
}

Expand Down
7 changes: 4 additions & 3 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,15 @@ function deleteStmt(table, where) {
* @param {string} table
* @param {Object|Array.<Object>} data
* @param {Array.<string>|Object} update
* @param {string} alias
* @return {string}
*/
function upsertStmt(table, data, update) {
function upsertStmt(table, data, update, alias) {
if (!update || typeof update !== 'object') {
throw new ImplementationError('Update parameter must be either an object or an array of strings');
}

const sql = insertStmt(table, data) + ' ON DUPLICATE KEY UPDATE ';
const sql = `${insertStmt(table, data)} AS ${escapeId(alias)} ON DUPLICATE KEY UPDATE `;

if (!Array.isArray(update) && isIterable(update)) {
return (
Expand All @@ -152,7 +153,7 @@ function upsertStmt(table, data, update) {
}

if (Array.isArray(update) && update.every((item) => typeof item === 'string')) {
return sql + update.map((column) => escapeId(column) + ' = VALUES(' + escapeId(column) + ')').join(', ');
return sql + update.map((column) => `${escapeId(column)} = ${escapeId(alias)}.${escapeId(column)}`).join(', ');
}

return sql;
Expand Down
10 changes: 7 additions & 3 deletions test/integration/transaction.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,15 @@ describe('transaction', () => {
await ctx.exec(`CREATE TABLE ${table} (id INT PRIMARY KEY, col1 VARCHAR(32))`);
await ctx.transaction(async (trx) => {
await trx.insert(table, { id: 1, col1: 'foo' });
await trx.upsert(table, { id: 1, col1: 'bar' }, { col1: ctx.raw('MD5(col1)') });
await trx.upsert(
table,
{ id: 1, col1: 'bar' },
{ col1: ctx.raw(`CONCAT(${table}.col1, \`new\`.col1)`) }
);
});

const hash = await ctx.queryOne(`SELECT col1 FROM ${table} WHERE id = 1`);
assert.equal(hash, 'acbd18db4cc2f85cedef654fccc4a4d8');
const value = await ctx.queryOne(`SELECT col1 FROM ${table} WHERE id = 1`);
assert.equal(value, 'foobar');
});

it('should accept updates as an array', async () => {
Expand Down
28 changes: 21 additions & 7 deletions test/unit/context.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,20 +334,34 @@ describe('context', () => {

const [call] = ctx.exec.mock.calls;
assert.deepEqual(call.arguments, [
`INSERT INTO \`t\` (\`col1\`, \`col2\`, \`col3\`) VALUES ('val1', 1, NOW()) ON DUPLICATE KEY UPDATE \`col1\` = VALUES(\`col1\`), \`col2\` = VALUES(\`col2\`)`
`INSERT INTO \`t\` (\`col1\`, \`col2\`, \`col3\`) VALUES ('val1', 1, NOW()) AS \`new\` ON DUPLICATE KEY UPDATE \`col1\` = \`new\`.\`col1\`, \`col2\` = \`new\`.\`col2\``
]);
});

it('should accept assignment list as an object', async () => {
await ctx.upsert(
't',
{ col1: 'val1', col2: 1, col3: ctx.raw('NOW()') },
{ col1: 'foo', col2: ctx.raw(ctx.quoteIdentifier('col2') + ' + 1') }
);
await ctx.upsert('t', { col1: 'val1', col2: 1, col3: ctx.raw('NOW()') }, { col1: 'foo' });

const [call] = ctx.exec.mock.calls;
assert.deepEqual(call.arguments, [
`INSERT INTO \`t\` (\`col1\`, \`col2\`, \`col3\`) VALUES ('val1', 1, NOW()) ON DUPLICATE KEY UPDATE \`col1\` = 'foo', \`col2\` = \`col2\` + 1`
`INSERT INTO \`t\` (\`col1\`, \`col2\`, \`col3\`) VALUES ('val1', 1, NOW()) AS \`new\` ON DUPLICATE KEY UPDATE \`col1\` = 'foo'`
]);
});

it('must not use alias for raw values', async () => {
await ctx.upsert('t', { id: 1, ts: '2000-01-01 00:00:00' }, { ts: ctx.raw('NOW()') });

const [call] = ctx.exec.mock.calls;
assert.deepEqual(call.arguments, [
`INSERT INTO \`t\` (\`id\`, \`ts\`) VALUES (1, '2000-01-01 00:00:00') AS \`new\` ON DUPLICATE KEY UPDATE \`ts\` = NOW()`
]);
});

it('should support custom alias', async () => {
await ctx.upsert('t', { id: 1, col1: 'val1' }, ['col1'], 'funkyAlias');

const [call] = ctx.exec.mock.calls;
assert.deepEqual(call.arguments, [
`INSERT INTO \`t\` (\`id\`, \`col1\`) VALUES (1, 'val1') AS \`funkyAlias\` ON DUPLICATE KEY UPDATE \`col1\` = \`funkyAlias\`.\`col1\``
]);
});
});
Expand Down

0 comments on commit 9b747cf

Please sign in to comment.