feat: polymorphic join querying by fields that don't exist in every collection (#12648)

This PR makes it possible to do polymorphic join querying by fields that
don't exist in all collections specified in `field.collection`, for
example:
```
const result = await payload.find({
  collection: 'payload-folders',
  joins: {
    documentsAndFolders: {
      where: {
        and: [
          {
            relationTo: {
              in: ['folderPoly1', 'folderPoly2'],
            },
          },
          {
            folderPoly2Title: { // this field exists only in the folderPoly2 collection, before it'd throw a query error.
              equals: 'Poly 2 Title',
            },
          },
        ],
      },
    },
  },
})
```

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
This commit is contained in:
Sasha
2025-06-03 00:48:07 +03:00
committed by GitHub
parent 30dd9a23a3
commit 2b40e0f21f
6 changed files with 92 additions and 1 deletions

View File

@@ -59,6 +59,7 @@ export async function validateQueryPaths({
errors,
overrideAccess,
policies,
polymorphicJoin,
req,
versionFields,
where: item,
@@ -71,6 +72,7 @@ export async function validateQueryPaths({
globalConfig,
overrideAccess,
policies,
polymorphicJoin,
req,
versionFields,
where: item,

View File

@@ -99,7 +99,10 @@ export async function validateSearchParam({
promises.push(
...paths.map(async ({ collectionSlug, field, invalid, path }, i) => {
if (invalid) {
errors.push({ path })
if (!polymorphicJoin) {
errors.push({ path })
}
return
}

View File

@@ -0,0 +1,12 @@
import type { CollectionConfig } from 'payload'
export const FolderPoly1: CollectionConfig = {
slug: 'folderPoly1',
fields: [
{
name: 'folderPoly1Title',
type: 'text',
},
],
folders: true,
}

View File

@@ -0,0 +1,12 @@
import type { CollectionConfig } from 'payload'
export const FolderPoly2: CollectionConfig = {
slug: 'folderPoly2',
fields: [
{
name: 'folderPoly2Title',
type: 'text',
},
],
folders: true,
}

View File

@@ -4,6 +4,8 @@ import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { Categories } from './collections/Categories.js'
import { CategoriesVersions } from './collections/CategoriesVersions.js'
import { FolderPoly1 } from './collections/FolderPoly1.js'
import { FolderPoly2 } from './collections/FolderPoly2.js'
import { HiddenPosts } from './collections/HiddenPosts.js'
import { Posts } from './collections/Posts.js'
import { SelfJoins } from './collections/SelfJoins.js'
@@ -337,6 +339,8 @@ export default buildConfigWithDefaults({
},
],
},
FolderPoly1,
FolderPoly2,
],
localization: {
locales: [

View File

@@ -289,6 +289,64 @@ describe('Joins Field', () => {
expect(categoryWithPosts.localizedPolymorphics.docs[0]).toHaveProperty('id')
})
it('should not throw a path validation error when querying joins with polymorphic relationships', async () => {
const folderDoc = await payload.create({
collection: 'payload-folders',
data: {
name: 'sharedFolder',
},
})
await payload.create({
collection: 'folderPoly1',
data: {
folderPoly1Title: 'Poly 1 title',
folder: folderDoc.id,
},
depth: 0,
})
await payload.create({
collection: 'folderPoly2',
data: {
folderPoly2Title: 'Poly 2 Title',
folder: folderDoc.id,
},
depth: 0,
})
const result = await payload.find({
collection: 'payload-folders',
joins: {
documentsAndFolders: {
limit: 100_000,
sort: 'name',
where: {
and: [
{
relationTo: {
in: ['folderPoly1', 'folderPoly2'],
},
},
{
folderPoly2Title: {
equals: 'Poly 2 Title',
},
},
],
},
},
},
where: {
id: {
equals: folderDoc.id,
},
},
})
expect(result.docs[0]?.documentsAndFolders.docs).toHaveLength(1)
})
it('should filter joins using where query', async () => {
const categoryWithPosts = await payload.findByID({
id: category.id,