fix(db-postgres): joins with versions and hasMany relationship (#9370)

Fixes errors when having joins with versions +drafts on `hasMany: true`
relationships.
Removes `joinQuery` overhead if we don't need it for the current
operation. Right now, in all adapters we support joins only for `find`,
`findOne`, and `queryDrafts`.


Fixes https://github.com/payloadcms/payload/issues/9369
This commit is contained in:
Sasha
2024-11-21 17:42:05 +02:00
committed by GitHub
parent 3d0424bc77
commit d499de1e0f
11 changed files with 60 additions and 11 deletions

View File

@@ -19,6 +19,7 @@ export const deleteMany: DeleteMany = async function deleteMany(
const result = await findMany({ const result = await findMany({
adapter: this, adapter: this,
fields: collectionConfig.fields, fields: collectionConfig.fields,
joins: false,
limit: 0, limit: 0,
locale: req.locale, locale: req.locale,
page: 1, page: 1,

View File

@@ -12,13 +12,7 @@ import { transform } from './transform/read/index.js'
export const deleteOne: DeleteOne = async function deleteOne( export const deleteOne: DeleteOne = async function deleteOne(
this: DrizzleAdapter, this: DrizzleAdapter,
{ { collection: collectionSlug, req = {} as PayloadRequest, select, where: whereArg },
collection: collectionSlug,
joins: joinQuery,
req = {} as PayloadRequest,
select,
where: whereArg,
},
) { ) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config const collection = this.payload.collections[collectionSlug].config
@@ -54,7 +48,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
adapter: this, adapter: this,
depth: 0, depth: 0,
fields: collection.fields, fields: collection.fields,
joinQuery, joinQuery: false,
select, select,
tableName, tableName,
}) })
@@ -69,7 +63,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
config: this.payload.config, config: this.payload.config,
data: docToDelete, data: docToDelete,
fields: collection.fields, fields: collection.fields,
joinQuery, joinQuery: false,
}) })
await this.deleteWhere({ await this.deleteWhere({

View File

@@ -24,6 +24,7 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
const { docs } = await findMany({ const { docs } = await findMany({
adapter: this, adapter: this,
fields, fields,
joins: false,
limit: 0, limit: 0,
locale, locale,
page: 1, page: 1,

View File

@@ -330,7 +330,6 @@ export const traverseFields = ({
} }
case 'group': case 'group':
case 'tab': { case 'tab': {
const fieldSelect = select?.[field.name] const fieldSelect = select?.[field.name]
@@ -364,6 +363,7 @@ export const traverseFields = ({
break break
} }
case 'join': { case 'join': {
// when `joinsQuery` is false, do not join // when `joinsQuery` is false, do not join
if (joinQuery === false) { if (joinQuery === false) {

View File

@@ -34,6 +34,7 @@ export const findVersions: FindVersions = async function findVersions(
return findMany({ return findMany({
adapter: this, adapter: this,
fields, fields,
joins: false,
limit, limit,
locale, locale,
page, page,

View File

@@ -49,6 +49,7 @@ export async function updateVersion<T extends TypeWithID>(
data: versionData, data: versionData,
db, db,
fields, fields,
joinQuery: false,
operation: 'update', operation: 'update',
req, req,
select, select,

View File

@@ -411,6 +411,8 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
// RETRIEVE NEWLY UPDATED ROW // RETRIEVE NEWLY UPDATED ROW
// ////////////////////////////////// // //////////////////////////////////
joinQuery = operation === 'create' ? false : joinQuery
const findManyArgs = buildFindManyArgs({ const findManyArgs = buildFindManyArgs({
adapter, adapter,
depth: 0, depth: 0,

View File

@@ -13,6 +13,12 @@ export const CategoriesVersions: CollectionConfig = {
collection: versionsSlug, collection: versionsSlug,
on: 'categoryVersion', on: 'categoryVersion',
}, },
{
name: 'relatedVersionsMany',
type: 'join',
collection: versionsSlug,
on: 'categoryVersions',
},
], ],
versions: { versions: {
drafts: true, drafts: true,

View File

@@ -15,6 +15,12 @@ export const Versions: CollectionConfig = {
relationTo: 'categories-versions', relationTo: 'categories-versions',
type: 'relationship', type: 'relationship',
}, },
{
name: 'categoryVersions',
relationTo: 'categories-versions',
type: 'relationship',
hasMany: true,
},
], ],
versions: { versions: {
drafts: true, drafts: true,

View File

@@ -480,7 +480,20 @@ describe('Joins Field', () => {
expect(res.docs[0].relatedVersions.docs[0].id).toBe(version.id) expect(res.docs[0].relatedVersions.docs[0].id).toBe(version.id)
}) })
it('should populate joins when versions on both sides draft true payload.db.queryDrafts', async () => { it('should populate joins with hasMany relationships when versions on both sides draft false', async () => {
const category = await payload.create({ collection: 'categories-versions', data: {} })
const version = await payload.create({
collection: 'versions',
data: { categoryVersions: [category.id] },
})
const res = await payload.find({ collection: 'categories-versions', draft: false })
expect(res.docs[0].relatedVersionsMany.docs[0].id).toBe(version.id)
})
it('should populate joins with hasMany relationships when versions on both sides draft true payload.db.queryDrafts', async () => {
const category = await payload.create({ collection: 'categories-versions', data: {} }) const category = await payload.create({ collection: 'categories-versions', data: {} })
const version = await payload.create({ const version = await payload.create({
@@ -495,6 +508,22 @@ describe('Joins Field', () => {
expect(res.docs[0].relatedVersions.docs[0].id).toBe(version.id) expect(res.docs[0].relatedVersions.docs[0].id).toBe(version.id)
}) })
it('should populate joins when versions on both sides draft true payload.db.queryDrafts', async () => {
const category = await payload.create({ collection: 'categories-versions', data: {} })
const version = await payload.create({
collection: 'versions',
data: { categoryVersions: [category.id] },
})
const res = await payload.find({
collection: 'categories-versions',
draft: true,
})
expect(res.docs[0].relatedVersionsMany.docs[0].id).toBe(version.id)
})
}) })
describe('REST', () => { describe('REST', () => {

View File

@@ -41,6 +41,7 @@ export interface Config {
}; };
'categories-versions': { 'categories-versions': {
relatedVersions: 'versions'; relatedVersions: 'versions';
relatedVersionsMany: 'versions';
}; };
'localized-categories': { 'localized-categories': {
relatedPosts: 'localized-posts'; relatedPosts: 'localized-posts';
@@ -197,6 +198,7 @@ export interface Version {
id: string; id: string;
category?: (string | null) | Category; category?: (string | null) | Category;
categoryVersion?: (string | null) | CategoriesVersion; categoryVersion?: (string | null) | CategoriesVersion;
categoryVersions?: (string | CategoriesVersion)[] | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: ('draft' | 'published') | null; _status?: ('draft' | 'published') | null;
@@ -211,6 +213,10 @@ export interface CategoriesVersion {
docs?: (string | Version)[] | null; docs?: (string | Version)[] | null;
hasNextPage?: boolean | null; hasNextPage?: boolean | null;
} | null; } | null;
relatedVersionsMany?: {
docs?: (string | Version)[] | null;
hasNextPage?: boolean | null;
} | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: ('draft' | 'published') | null; _status?: ('draft' | 'published') | null;
@@ -442,6 +448,7 @@ export interface UploadsSelect<T extends boolean = true> {
export interface VersionsSelect<T extends boolean = true> { export interface VersionsSelect<T extends boolean = true> {
category?: T; category?: T;
categoryVersion?: T; categoryVersion?: T;
categoryVersions?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
_status?: T; _status?: T;
@@ -452,6 +459,7 @@ export interface VersionsSelect<T extends boolean = true> {
*/ */
export interface CategoriesVersionsSelect<T extends boolean = true> { export interface CategoriesVersionsSelect<T extends boolean = true> {
relatedVersions?: T; relatedVersions?: T;
relatedVersionsMany?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
_status?: T; _status?: T;