diff --git a/src/format_request.js b/src/format_request.js index 93d58bf..3d66a67 100644 --- a/src/format_request.js +++ b/src/format_request.js @@ -102,7 +102,7 @@ async function format_request(options, dareInstance) { } else if ( options.method === 'patch' && !options.parent && - !dareInstance.applyTableAliasOnUpdate + !dareInstance.applyAliasesOnUpdate ) { options.sql_alias = options.sql_table; } else { diff --git a/src/index.js b/src/index.js index ad040b3..278b0ff 100644 --- a/src/index.js +++ b/src/index.js @@ -790,7 +790,7 @@ Dare.prototype.patch = async function patch(table, filter, body, options = {}) { // Construct a db update const sql = SQL` - UPDATE ${raw(exec)}${raw(req.sql_table)} ${dareInstance.applyTableAliasOnUpdate ? raw(req.sql_alias) : empty} + UPDATE ${raw(exec)}${raw(req.sql_table)} ${dareInstance.applyAliasesOnUpdate ? raw(req.sql_alias) : empty} ${req.sql_joins.length ? join(req.sql_joins, '\n') : empty} SET ${sql_set} WHERE diff --git a/src/sqlite.js b/src/sqlite.js index 84fca72..8867904 100644 --- a/src/sqlite.js +++ b/src/sqlite.js @@ -98,12 +98,6 @@ SQLiteDare.prototype.applySubqueryOnDML = true; */ SQLiteDare.prototype.applyAliasesOnUpdate = false; -/** - * SQLite does not support UPDATE tbl alias SET ... - * @type {boolean} - */ -SQLiteDare.prototype.applyTableAliasOnUpdate = false; - /** * SQL insert suffix - SQLite uses RETURNING clause * @type {string} diff --git a/test/integration/patch.spec.js b/test/integration/patch.spec.js new file mode 100644 index 0000000..daa4b6f --- /dev/null +++ b/test/integration/patch.spec.js @@ -0,0 +1,141 @@ +import assert from 'node:assert/strict'; + +import {DareError} from '../../src/index.js'; +import defaultAPI from './helpers/api.js'; + +describe('dare.patch', () => { + let dare; + + beforeEach(() => { + dare = defaultAPI(); + }); + + it('should update a single record by id', async () => { + const username = 'patchUser'; + const newName = 'patchedUser'; + + const {insertId} = await dare.post('users', {username}); + + const resp = await dare.patch( + 'users', + {id: insertId}, + {username: newName} + ); + + assert.strictEqual(resp.affectedRows, 1); + + const result = await dare.get('users', ['username'], {id: insertId}); + assert.strictEqual(result.username, newName); + }); + + it('should update multiple records with a limit', async () => { + const body = ['patchA', 'patchB', 'patchC'].map(username => ({ + username, + })); + await dare.post('users', body); + + const resp = await dare.patch({ + table: 'users', + filter: {username: 'patch%'}, + body: {first_name: 'updated'}, + limit: 100, + }); + + assert.strictEqual(resp.affectedRows, 3); + }); + + it('should throw NOT_FOUND when no records match', async () => { + await assert.rejects( + dare.patch('users', {id: -999}, {username: 'nope'}), + error => + error instanceof DareError && error.code === DareError.NOT_FOUND + ); + }); + + it('should return notfound value when no records match and notfound option is set', async () => { + const notfound = null; + + const resp = await dare.patch( + 'users', + {id: -999}, + {username: 'nope'}, + {notfound} + ); + + assert.strictEqual(resp, notfound); + }); + + it('should support object-style request', async () => { + const username = 'objStylePatch'; + const {insertId} = await dare.post('users', {username}); + + const resp = await dare.patch({ + table: 'users', + filter: {id: insertId}, + body: {username: 'objPatched'}, + }); + + assert.strictEqual(resp.affectedRows, 1); + + const result = await dare.get('users', ['username'], {id: insertId}); + assert.strictEqual(result.username, 'objPatched'); + }); + + it('should patch with a cross-table filter', async () => { + const code = 'PX'; + const {insertId: country_id} = await dare.post('country', {code}); + + const {insertId} = await dare.post('users', { + username: 'crossPatch', + country_id, + }); + + const resp = await dare.patch({ + table: 'users', + filter: { + id: insertId, + country: {code}, + }, + body: {username: 'crossPatched'}, + }); + + assert.strictEqual(resp.affectedRows, 1); + + const result = await dare.get('users', ['username'], {id: insertId}); + assert.strictEqual(result.username, 'crossPatched'); + }); + + it('should set a field to null', async () => { + const {insertId} = await dare.post('users', { + username: 'nullTest', + first_name: 'hasValue', + }); + + await dare.patch('users', {id: insertId}, {first_name: null}); + + const result = await dare.get('users', ['first_name'], {id: insertId}); + assert.strictEqual(result.first_name, null); + }); + + it('should trigger model patch handler', async () => { + const {insertId} = await dare.post('users', {username: 'handlerTest'}); + + dare = dare.use({ + models: { + users: { + patch(options) { + options.body.first_name = 'injected'; + }, + }, + }, + }); + + await dare.patch('users', {id: insertId}, {username: 'handlerPatched'}); + + const result = await dare.get('users', ['username', 'first_name'], { + id: insertId, + }); + assert.strictEqual(result.username, 'handlerPatched'); + assert.strictEqual(result.first_name, 'injected'); + }); +}); diff --git a/test/specs/postgres/patch.spec.js b/test/specs/postgres/patch.spec.js index db9662d..8e9499e 100644 --- a/test/specs/postgres/patch.spec.js +++ b/test/specs/postgres/patch.spec.js @@ -26,7 +26,7 @@ describe('patch', () => { const dareInst = dare.use({engine: 'postgres:16.3'}); dareInst.execute = async ({sql, values}) => { - sqlEqual(sql, 'UPDATE tbl a SET "name" = ? WHERE a.id = ?'); + sqlEqual(sql, 'UPDATE tbl SET "name" = ? WHERE tbl.id = ?'); assert.deepStrictEqual(values, [name, id]); return {success: true}; };