diff --git a/src/__tests__/query-builder/mutation.test.ts b/src/__tests__/query-builder/mutation.test.ts index 0c44016..f516369 100644 --- a/src/__tests__/query-builder/mutation.test.ts +++ b/src/__tests__/query-builder/mutation.test.ts @@ -82,6 +82,18 @@ describe('QueryBuilder/mutation', () => { expect(result).toEqual(['Bob', 'David']) }) + + it('updates multiple rows', async () => { + const returning = await new QueryBuilder(client, 'mutation') + .where('name', 'IN', ['Alice']) + .update({ name: 'David' }, { returning: ['name'] }) + + expect(returning).toEqual([{ name: 'David' }]) + + const result = await new QueryBuilder(client, 'mutation').pluck('name') + + expect(result).toEqual(['Bob', 'David']) + }) }) describe('delete', () => { diff --git a/src/query-builder.ts b/src/query-builder.ts index 500cab8..82e583c 100644 --- a/src/query-builder.ts +++ b/src/query-builder.ts @@ -241,7 +241,7 @@ export class QueryBuilder< return this } - private buildWhereSql() { + private buildWhereSql(mode: 'query' | 'update' = 'query') { const sql = [] const params: any[] = [] @@ -255,6 +255,10 @@ export class QueryBuilder< if (operator === 'IN' || operator === 'NOT IN') { params.push(value) + + if (mode === 'update') + return `${escapeIdentifier(column)} = ANY(?)` + return `${escapeIdentifier(column)} ${operator} (${value.map(() => '?').join(',')})` } @@ -393,6 +397,8 @@ export class QueryBuilder< * await db('users').insert({ id: 3, name: 'Charlie' }) // => [] * * await db('users').insert({ id: 3, name: 'Charlie' }, { returning: ['name'] }) // => [{ name: 'Charlie' }] + * + * await db('users').insert([{ id: 4, name: 'David' }, { id: 5, name: 'Eve' }]) // => [] * ``` */ async insert>, Returning extends (keyof TableType)[] | ['*']>( @@ -467,7 +473,7 @@ export class QueryBuilder< ] // Add where conditions - const { sql: whereSql, params: whereParams } = this.buildWhereSql() + const { sql: whereSql, params: whereParams } = this.buildWhereSql('update') if (!whereSql) throw new Error('Missing where conditions') @@ -509,6 +515,24 @@ export class QueryBuilder< return this.client.raw(sql.join(' '), ...whereParams) } + /** + * Inserts or updates records in the database table. + * + * @template FirstValue - A partial type of the table's row type. + * + * @param {FirstValue | [FirstValue, ...{ [K in Extract>]: ColumnValue }[]]} values - The values to insert or update. Can be a single object or an array of objects. + * @param {Object} options - The options for the upsert operation. + * @param {ColumnName[]} options.conflict - The columns to check for conflicts. + * @param {(keyof FirstValue)[]} [options.update] - The columns to update if a conflict occurs. + * @param {(keyof FirstValue)[] | ['*']} [options.returning] - The columns to return after the upsert operation. + * + * @returns {Promise} - A promise that resolves to the result of the upsert operation. + * + * @example + * ```ts + * await db('users').upsert({ id: 1, name: 'Alice' }, { conflict: ['id'], update: ['name'] }) // => [] + * ``` + */ async upsert>>( values: FirstValue | [FirstValue, ...{ [K in Extract>]: ColumnValue