Skip to content

Commit 41c7413

Browse files
authored
feat(db-*): add updateMany method to database adapter (#11441)
This PR adds a new `payload.db.updateMany` method, which is a more performant way to update multiple documents compared to using `payload.update`.
1 parent 3709950 commit 41c7413

File tree

13 files changed

+370
-18
lines changed

13 files changed

+370
-18
lines changed

packages/db-mongodb/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
TypeWithVersion,
1717
UpdateGlobalArgs,
1818
UpdateGlobalVersionArgs,
19+
UpdateManyArgs,
1920
UpdateOneArgs,
2021
UpdateVersionArgs,
2122
} from 'payload'
@@ -53,6 +54,7 @@ import { commitTransaction } from './transactions/commitTransaction.js'
5354
import { rollbackTransaction } from './transactions/rollbackTransaction.js'
5455
import { updateGlobal } from './updateGlobal.js'
5556
import { updateGlobalVersion } from './updateGlobalVersion.js'
57+
import { updateMany } from './updateMany.js'
5658
import { updateOne } from './updateOne.js'
5759
import { updateVersion } from './updateVersion.js'
5860
import { upsert } from './upsert.js'
@@ -160,6 +162,7 @@ declare module 'payload' {
160162
updateGlobalVersion: <T extends TypeWithID = TypeWithID>(
161163
args: { options?: QueryOptions } & UpdateGlobalVersionArgs<T>,
162164
) => Promise<TypeWithVersion<T>>
165+
163166
updateOne: (args: { options?: QueryOptions } & UpdateOneArgs) => Promise<Document>
164167
updateVersion: <T extends TypeWithID = TypeWithID>(
165168
args: { options?: QueryOptions } & UpdateVersionArgs<T>,
@@ -200,6 +203,7 @@ export function mongooseAdapter({
200203
mongoMemoryServer,
201204
sessions: {},
202205
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
206+
updateMany,
203207
url,
204208
versions: {},
205209
// DatabaseAdapter

packages/db-mongodb/src/updateMany.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { MongooseUpdateQueryOptions } from 'mongoose'
2+
import type { UpdateMany } from 'payload'
3+
4+
import type { MongooseAdapter } from './index.js'
5+
6+
import { buildQuery } from './queries/buildQuery.js'
7+
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
8+
import { getSession } from './utilities/getSession.js'
9+
import { handleError } from './utilities/handleError.js'
10+
import { transform } from './utilities/transform.js'
11+
12+
export const updateMany: UpdateMany = async function updateMany(
13+
this: MongooseAdapter,
14+
{ collection, data, locale, options: optionsArgs = {}, req, returning, select, where },
15+
) {
16+
const Model = this.collections[collection]
17+
const fields = this.payload.collections[collection].config.fields
18+
19+
const options: MongooseUpdateQueryOptions = {
20+
...optionsArgs,
21+
lean: true,
22+
new: true,
23+
projection: buildProjectionFromSelect({
24+
adapter: this,
25+
fields: this.payload.collections[collection].config.flattenedFields,
26+
select,
27+
}),
28+
session: await getSession(this, req),
29+
}
30+
31+
const query = await buildQuery({
32+
adapter: this,
33+
collectionSlug: collection,
34+
fields: this.payload.collections[collection].config.flattenedFields,
35+
locale,
36+
where,
37+
})
38+
39+
transform({ adapter: this, data, fields, operation: 'write' })
40+
41+
try {
42+
await Model.updateMany(query, data, options)
43+
} catch (error) {
44+
handleError({ collection, error, req })
45+
}
46+
47+
if (returning === false) {
48+
return null
49+
}
50+
51+
const result = await Model.find(query, {}, options)
52+
53+
transform({
54+
adapter: this,
55+
data: result,
56+
fields,
57+
operation: 'read',
58+
})
59+
60+
return result
61+
}

packages/db-postgres/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
rollbackTransaction,
3434
updateGlobal,
3535
updateGlobalVersion,
36+
updateMany,
3637
updateOne,
3738
updateVersion,
3839
} from '@payloadcms/drizzle'
@@ -185,6 +186,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
185186
rollbackTransaction,
186187
updateGlobal,
187188
updateGlobalVersion,
189+
updateMany,
188190
updateOne,
189191
updateVersion,
190192
upsert: updateOne,

packages/db-sqlite/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
rollbackTransaction,
3535
updateGlobal,
3636
updateGlobalVersion,
37+
updateMany,
3738
updateOne,
3839
updateVersion,
3940
} from '@payloadcms/drizzle'
@@ -120,6 +121,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
120121
tableNameMap: new Map<string, string>(),
121122
tables: {},
122123
transactionOptions: args.transactionOptions || undefined,
124+
updateMany,
123125
versionsSuffix: args.versionsSuffix || '_v',
124126

125127
// DatabaseAdapter

packages/db-vercel-postgres/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
rollbackTransaction,
3434
updateGlobal,
3535
updateGlobalVersion,
36+
updateMany,
3637
updateOne,
3738
updateVersion,
3839
} from '@payloadcms/drizzle'
@@ -186,6 +187,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
186187
rollbackTransaction,
187188
updateGlobal,
188189
updateGlobalVersion,
190+
updateMany,
189191
updateOne,
190192
updateVersion,
191193
upsert: updateOne,

packages/drizzle/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export { commitTransaction } from './transactions/commitTransaction.js'
3333
export { rollbackTransaction } from './transactions/rollbackTransaction.js'
3434
export { updateGlobal } from './updateGlobal.js'
3535
export { updateGlobalVersion } from './updateGlobalVersion.js'
36+
export { updateMany } from './updateMany.js'
3637
export { updateOne } from './updateOne.js'
3738
export { updateVersion } from './updateVersion.js'
3839
export { upsertRow } from './upsertRow/index.js'

packages/drizzle/src/updateMany.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
2+
import type { UpdateMany } from 'payload'
3+
4+
import toSnakeCase from 'to-snake-case'
5+
6+
import type { DrizzleAdapter } from './types.js'
7+
8+
import buildQuery from './queries/buildQuery.js'
9+
import { selectDistinct } from './queries/selectDistinct.js'
10+
import { upsertRow } from './upsertRow/index.js'
11+
import { getTransaction } from './utilities/getTransaction.js'
12+
13+
export const updateMany: UpdateMany = async function updateMany(
14+
this: DrizzleAdapter,
15+
{
16+
collection: collectionSlug,
17+
data,
18+
joins: joinQuery,
19+
locale,
20+
req,
21+
returning,
22+
select,
23+
where: whereToUse,
24+
},
25+
) {
26+
const db = await getTransaction(this, req)
27+
const collection = this.payload.collections[collectionSlug].config
28+
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
29+
30+
const { joins, selectFields, where } = buildQuery({
31+
adapter: this,
32+
fields: collection.flattenedFields,
33+
locale,
34+
tableName,
35+
where: whereToUse,
36+
})
37+
38+
let idsToUpdate: (number | string)[] = []
39+
40+
const selectDistinctResult = await selectDistinct({
41+
adapter: this,
42+
db,
43+
joins,
44+
selectFields,
45+
tableName,
46+
where,
47+
})
48+
49+
if (selectDistinctResult?.[0]?.id) {
50+
idsToUpdate = selectDistinctResult?.map((doc) => doc.id)
51+
52+
// If id wasn't passed but `where` without any joins, retrieve it with findFirst
53+
} else if (whereToUse && !joins.length) {
54+
const _db = db as LibSQLDatabase
55+
56+
const table = this.tables[tableName]
57+
58+
const docsToUpdate = await _db
59+
.select({
60+
id: table.id,
61+
})
62+
.from(table)
63+
.where(where)
64+
65+
idsToUpdate = docsToUpdate?.map((doc) => doc.id)
66+
}
67+
68+
if (!idsToUpdate.length) {
69+
return []
70+
}
71+
72+
const results = []
73+
74+
// TODO: We need to batch this to reduce the amount of db calls. This can get very slow if we are updating a lot of rows.
75+
for (const idToUpdate of idsToUpdate) {
76+
const result = await upsertRow({
77+
id: idToUpdate,
78+
adapter: this,
79+
data,
80+
db,
81+
fields: collection.flattenedFields,
82+
ignoreResult: returning === false,
83+
joinQuery,
84+
operation: 'update',
85+
req,
86+
select,
87+
tableName,
88+
})
89+
results.push(result)
90+
}
91+
92+
if (returning === false) {
93+
return null
94+
}
95+
96+
return results
97+
}

packages/payload/src/database/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ export interface BaseDatabaseAdapter {
145145

146146
updateGlobalVersion: UpdateGlobalVersion
147147

148+
updateMany: UpdateMany
149+
148150
updateOne: UpdateOne
149151

150152
updateVersion: UpdateVersion
@@ -510,6 +512,29 @@ export type UpdateOneArgs = {
510512
*/
511513
export type UpdateOne = (args: UpdateOneArgs) => Promise<Document>
512514

515+
export type UpdateManyArgs = {
516+
collection: CollectionSlug
517+
data: Record<string, unknown>
518+
draft?: boolean
519+
joins?: JoinQuery
520+
locale?: string
521+
/**
522+
* Additional database adapter specific options to pass to the query
523+
*/
524+
options?: Record<string, unknown>
525+
req?: Partial<PayloadRequest>
526+
/**
527+
* If true, returns the updated documents
528+
*
529+
* @default true
530+
*/
531+
returning?: boolean
532+
select?: SelectType
533+
where: Where
534+
}
535+
536+
export type UpdateMany = (args: UpdateManyArgs) => Promise<Document[] | null>
537+
513538
export type UpsertArgs = {
514539
collection: CollectionSlug
515540
data: Record<string, unknown>

packages/payload/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,7 @@ export type {
11121112
Connect,
11131113
Count,
11141114
CountArgs,
1115+
CountGlobalVersionArgs,
11151116
CountGlobalVersions,
11161117
CountVersions,
11171118
Create,
@@ -1156,6 +1157,8 @@ export type {
11561157
UpdateGlobalArgs,
11571158
UpdateGlobalVersion,
11581159
UpdateGlobalVersionArgs,
1160+
UpdateMany,
1161+
UpdateManyArgs,
11591162
UpdateOne,
11601163
UpdateOneArgs,
11611164
UpdateVersion,

0 commit comments

Comments
 (0)