Skip to content

Commit bc9b501

Browse files
authored
fix: querying virtual fields deeply with draft: true (#12868)
Fixes an issue when querying deeply new relationship virtual fields with `draft: true`. Changes the method for `where` sanitization, before it was done in `validateSearchParam` which didn't work with versions properly, now there's a separate `sanitizeWhereQuery` function that does this.
1 parent bb17cc3 commit bc9b501

File tree

11 files changed

+108
-5
lines changed

11 files changed

+108
-5
lines changed

packages/payload/src/collections/operations/count.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Collection } from '../config/types.js'
66
import executeAccess from '../../auth/executeAccess.js'
77
import { combineQueries } from '../../database/combineQueries.js'
88
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
9+
import { sanitizeWhereQuery } from '../../database/sanitizeWhereQuery.js'
910
import { killTransaction } from '../../utilities/killTransaction.js'
1011
import { buildAfterOperation } from './utils.js'
1112

@@ -71,6 +72,7 @@ export const countOperation = async <TSlug extends CollectionSlug>(
7172
let result: { totalDocs: number }
7273

7374
const fullWhere = combineQueries(where!, accessResult!)
75+
sanitizeWhereQuery({ fields: collectionConfig.flattenedFields, payload, where: fullWhere })
7476

7577
await validateQueryPaths({
7678
collectionConfig,

packages/payload/src/collections/operations/countVersions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { Collection } from '../config/types.js'
55
import executeAccess from '../../auth/executeAccess.js'
66
import { combineQueries } from '../../database/combineQueries.js'
77
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
8+
import { sanitizeWhereQuery } from '../../database/sanitizeWhereQuery.js'
89
import { buildVersionCollectionFields, type CollectionSlug } from '../../index.js'
910
import { killTransaction } from '../../utilities/killTransaction.js'
1011
import { buildAfterOperation } from './utils.js'
@@ -77,6 +78,8 @@ export const countVersionsOperation = async <TSlug extends CollectionSlug>(
7778

7879
const versionFields = buildVersionCollectionFields(payload.config, collectionConfig, true)
7980

81+
sanitizeWhereQuery({ fields: versionFields, payload, where: fullWhere })
82+
8083
await validateQueryPaths({
8184
collectionConfig,
8285
overrideAccess: overrideAccess!,

packages/payload/src/collections/operations/delete.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
import executeAccess from '../../auth/executeAccess.js'
1414
import { combineQueries } from '../../database/combineQueries.js'
1515
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
16+
import { sanitizeWhereQuery } from '../../database/sanitizeWhereQuery.js'
1617
import { APIError } from '../../errors/index.js'
1718
import { afterRead } from '../../fields/hooks/afterRead/index.js'
1819
import { deleteUserPreferences } from '../../preferences/deleteUserPreferences.js'
@@ -107,6 +108,8 @@ export const deleteOperation = async <
107108

108109
const fullWhere = combineQueries(where, accessResult!)
109110

111+
sanitizeWhereQuery({ fields: collectionConfig.flattenedFields, payload, where: fullWhere })
112+
110113
const select = sanitizeSelect({
111114
fields: collectionConfig.flattenedFields,
112115
forceSelect: collectionConfig.forceSelect,

packages/payload/src/collections/operations/find.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import executeAccess from '../../auth/executeAccess.js'
1919
import { combineQueries } from '../../database/combineQueries.js'
2020
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
2121
import { sanitizeJoinQuery } from '../../database/sanitizeJoinQuery.js'
22+
import { sanitizeWhereQuery } from '../../database/sanitizeWhereQuery.js'
2223
import { afterRead } from '../../fields/hooks/afterRead/index.js'
2324
import { lockedDocumentsCollectionSlug } from '../../locked-documents/config.js'
2425
import { killTransaction } from '../../utilities/killTransaction.js'
@@ -144,6 +145,7 @@ export const findOperation = async <
144145
let result: PaginatedDocs<DataFromCollectionSlug<TSlug>>
145146

146147
let fullWhere = combineQueries(where!, accessResult!)
148+
sanitizeWhereQuery({ fields: collectionConfig.flattenedFields, payload, where: fullWhere })
147149

148150
const sort = sanitizeSortQuery({
149151
fields: collection.config.flattenedFields,

packages/payload/src/collections/operations/findByID.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
import executeAccess from '../../auth/executeAccess.js'
1919
import { combineQueries } from '../../database/combineQueries.js'
2020
import { sanitizeJoinQuery } from '../../database/sanitizeJoinQuery.js'
21+
import { sanitizeWhereQuery } from '../../database/sanitizeWhereQuery.js'
2122
import { NotFound } from '../../errors/index.js'
2223
import { afterRead } from '../../fields/hooks/afterRead/index.js'
2324
import { validateQueryPaths } from '../../index.js'
@@ -110,6 +111,12 @@ export const findByIDOperation = async <
110111

111112
const fullWhere = combineQueries(where, accessResult)
112113

114+
sanitizeWhereQuery({
115+
fields: collectionConfig.flattenedFields,
116+
payload: args.req.payload,
117+
where: fullWhere,
118+
})
119+
113120
const sanitizedJoins = await sanitizeJoinQuery({
114121
collectionConfig,
115122
joins,

packages/payload/src/collections/operations/findVersions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { Collection } from '../config/types.js'
77
import executeAccess from '../../auth/executeAccess.js'
88
import { combineQueries } from '../../database/combineQueries.js'
99
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
10+
import { sanitizeWhereQuery } from '../../database/sanitizeWhereQuery.js'
1011
import { afterRead } from '../../fields/hooks/afterRead/index.js'
1112
import { killTransaction } from '../../utilities/killTransaction.js'
1213
import { sanitizeInternalFields } from '../../utilities/sanitizeInternalFields.js'
@@ -71,9 +72,10 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
7172
})
7273

7374
const fullWhere = combineQueries(where!, accessResults)
75+
sanitizeWhereQuery({ fields: versionFields, payload, where: fullWhere })
7476

7577
const select = sanitizeSelect({
76-
fields: buildVersionCollectionFields(payload.config, collectionConfig, true),
78+
fields: versionFields,
7779
forceSelect: getQueryDraftsSelect({ select: collectionConfig.forceSelect }),
7880
select: incomingSelect,
7981
versions: true,

packages/payload/src/collections/operations/update.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import type {
1515
import executeAccess from '../../auth/executeAccess.js'
1616
import { combineQueries } from '../../database/combineQueries.js'
1717
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
18-
import { APIError, Forbidden } from '../../errors/index.js'
18+
import { sanitizeWhereQuery } from '../../database/sanitizeWhereQuery.js'
19+
import { APIError } from '../../errors/index.js'
1920
import { type CollectionSlug, deepCopyObjectSimple } from '../../index.js'
2021
import { generateFileData } from '../../uploads/generateFileData.js'
2122
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles.js'
@@ -140,6 +141,8 @@ export const updateOperation = async <
140141

141142
const fullWhere = combineQueries(where, accessResult!)
142143

144+
sanitizeWhereQuery({ fields: collectionConfig.flattenedFields, payload, where: fullWhere })
145+
143146
const sort = sanitizeSortQuery({
144147
fields: collection.config.flattenedFields,
145148
sort: incomingSort,

packages/payload/src/database/queryValidation/validateSearchParams.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,6 @@ export async function validateSearchParam({
113113
if ('virtual' in field && field.virtual) {
114114
if (field.virtual === true) {
115115
errors.push({ path })
116-
} else {
117-
constraint[`${field.virtual}` as keyof WhereField] = constraint[path as keyof WhereField]
118-
delete constraint[path as keyof WhereField]
119116
}
120117
}
121118

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { FlattenedField } from '../fields/config/types.js'
2+
import type { Payload, Where } from '../types/index.js'
3+
4+
/**
5+
* Currently used only for virtual fields linked with relationships
6+
*/
7+
export const sanitizeWhereQuery = ({
8+
fields,
9+
payload,
10+
where,
11+
}: {
12+
fields: FlattenedField[]
13+
payload: Payload
14+
where: Where
15+
}) => {
16+
for (const key in where) {
17+
const value = where[key]
18+
19+
if (['and', 'or'].includes(key.toLowerCase()) && Array.isArray(value)) {
20+
for (const where of value) {
21+
sanitizeWhereQuery({ fields, payload, where })
22+
}
23+
continue
24+
}
25+
26+
const paths = key.split('.')
27+
let pathHasChanged = false
28+
29+
let currentFields = fields
30+
31+
for (let i = 0; i < paths.length; i++) {
32+
const path = paths[i]!
33+
const field = currentFields.find((each) => each.name === path)
34+
35+
if (!field) {
36+
break
37+
}
38+
39+
if ('virtual' in field && field.virtual && typeof field.virtual === 'string') {
40+
paths[i] = field.virtual
41+
pathHasChanged = true
42+
}
43+
44+
if ('flattenedFields' in field) {
45+
currentFields = field.flattenedFields
46+
}
47+
48+
if (
49+
(field.type === 'relationship' || field.type === 'upload') &&
50+
typeof field.relationTo === 'string'
51+
) {
52+
const relatedCollection = payload.collections[field.relationTo]
53+
if (relatedCollection) {
54+
currentFields = relatedCollection.config.flattenedFields
55+
}
56+
}
57+
}
58+
59+
if (pathHasChanged) {
60+
where[paths.join('.')] = where[key]!
61+
delete where[key]
62+
}
63+
}
64+
}

test/database/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default buildConfigWithDefaults({
3838
collections: [
3939
{
4040
slug: 'categories',
41+
versions: { drafts: true },
4142
fields: [
4243
{
4344
type: 'text',

0 commit comments

Comments
 (0)