diff --git a/packages/db-mongodb/src/queryDrafts.ts b/packages/db-mongodb/src/queryDrafts.ts index 5754905d69b..fca0ec353d4 100644 --- a/packages/db-mongodb/src/queryDrafts.ts +++ b/packages/db-mongodb/src/queryDrafts.ts @@ -6,12 +6,23 @@ import { combineQueries, flattenWhereToOperators } from 'payload' import type { MongooseAdapter } from './index.js' import { buildSortParam } from './queries/buildSortParam.js' +import { buildJoinAggregation } from './utilities/buildJoinAggregation.js' import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js' import { withSession } from './withSession.js' export const queryDrafts: QueryDrafts = async function queryDrafts( this: MongooseAdapter, - { collection, limit, locale, page, pagination, req = {} as PayloadRequest, sort: sortArg, where }, + { + collection, + joins, + limit, + locale, + page, + pagination, + req = {} as PayloadRequest, + sort: sortArg, + where, + }, ) { const VersionModel = this.versions[collection] const collectionConfig = this.payload.collections[collection].config @@ -89,7 +100,29 @@ export const queryDrafts: QueryDrafts = async function queryDrafts( paginationOptions.options.limit = limit } - const result = await VersionModel.paginate(versionQuery, paginationOptions) + let result + + const aggregate = await buildJoinAggregation({ + adapter: this, + collection, + collectionConfig, + joins, + limit, + locale, + query: versionQuery, + versions: true, + }) + + // build join aggregation + if (aggregate) { + result = await VersionModel.aggregatePaginate( + VersionModel.aggregate(aggregate), + paginationOptions, + ) + } else { + result = await VersionModel.paginate(versionQuery, paginationOptions) + } + const docs = JSON.parse(JSON.stringify(result.docs)) return { diff --git a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts index ea11011f019..332e09d7ccb 100644 --- a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts +++ b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts @@ -15,6 +15,8 @@ type BuildJoinAggregationArgs = { locale: string // the where clause for the top collection query?: Where + /** whether the query is from drafts */ + versions?: boolean } export const buildJoinAggregation = async ({ @@ -25,6 +27,7 @@ export const buildJoinAggregation = async ({ limit, locale, query, + versions, }: BuildJoinAggregationArgs): Promise => { if (Object.keys(collectionConfig.joins).length === 0 || joins === false) { return @@ -90,7 +93,7 @@ export const buildJoinAggregation = async ({ if (adapter.payload.config.localization && locale === 'all') { adapter.payload.config.localization.localeCodes.forEach((code) => { - const as = `${join.schemaPath}${code}` + const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${code}` aggregate.push( { @@ -98,7 +101,7 @@ export const buildJoinAggregation = async ({ as: `${as}.docs`, foreignField: `${join.field.on}${code}`, from: slug, - localField: '_id', + localField: versions ? 'parent' : '_id', pipeline, }, }, @@ -131,7 +134,7 @@ export const buildJoinAggregation = async ({ } else { const localeSuffix = join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : '' - const as = `${join.schemaPath}${localeSuffix}` + const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${localeSuffix}` aggregate.push( { @@ -139,7 +142,7 @@ export const buildJoinAggregation = async ({ as: `${as}.docs`, foreignField: `${join.field.on}${localeSuffix}`, from: slug, - localField: '_id', + localField: versions ? 'parent' : '_id', pipeline, }, }, diff --git a/packages/db-sqlite/src/init.ts b/packages/db-sqlite/src/init.ts index 1a3b40a994e..4a2abfcb332 100644 --- a/packages/db-sqlite/src/init.ts +++ b/packages/db-sqlite/src/init.ts @@ -70,7 +70,6 @@ export const init: Init = async function init(this: SQLiteAdapter) { disableNotNull: !!collection?.versions?.drafts, disableUnique: false, fields: collection.fields, - joins: collection.joins, locales, tableName, timestamps: collection.timestamps, diff --git a/packages/db-sqlite/src/schema/build.ts b/packages/db-sqlite/src/schema/build.ts index b65df477c8c..2b10c61d6da 100644 --- a/packages/db-sqlite/src/schema/build.ts +++ b/packages/db-sqlite/src/schema/build.ts @@ -61,7 +61,6 @@ type Args = { disableRelsTableUnique?: boolean disableUnique: boolean fields: Field[] - joins?: SanitizedJoins locales?: [string, ...string[]] rootRelationships?: Set rootRelationsToBuild?: RelationMap @@ -95,7 +94,6 @@ export const buildTable = ({ disableRelsTableUnique, disableUnique = false, fields, - joins, locales, rootRelationships, rootRelationsToBuild, @@ -144,7 +142,6 @@ export const buildTable = ({ disableUnique, fields, indexes, - joins, locales, localesColumns, localesIndexes, diff --git a/packages/db-sqlite/src/schema/traverseFields.ts b/packages/db-sqlite/src/schema/traverseFields.ts index d1896eed7d4..d59c358e82c 100644 --- a/packages/db-sqlite/src/schema/traverseFields.ts +++ b/packages/db-sqlite/src/schema/traverseFields.ts @@ -44,7 +44,6 @@ type Args = { fields: (Field | TabAsField)[] forceLocalized?: boolean indexes: Record IndexBuilder> - joins?: SanitizedJoins locales: [string, ...string[]] localesColumns: Record localesIndexes: Record IndexBuilder> @@ -84,7 +83,6 @@ export const traverseFields = ({ fields, forceLocalized, indexes, - joins, locales, localesColumns, localesIndexes, @@ -669,7 +667,6 @@ export const traverseFields = ({ fields: field.fields, forceLocalized, indexes, - joins, locales, localesColumns, localesIndexes, @@ -725,7 +722,6 @@ export const traverseFields = ({ fields: field.fields, forceLocalized: field.localized, indexes, - joins, locales, localesColumns, localesIndexes, @@ -782,7 +778,6 @@ export const traverseFields = ({ fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })), forceLocalized, indexes, - joins, locales, localesColumns, localesIndexes, @@ -839,7 +834,6 @@ export const traverseFields = ({ fields: field.fields, forceLocalized, indexes, - joins, locales, localesColumns, localesIndexes, @@ -937,30 +931,6 @@ export const traverseFields = ({ break - case 'join': { - // fieldName could be 'posts' or 'group_posts' - // using `on` as the key for the relation - const localized = adapter.payload.config.localization && field.localized - const fieldSchemaPath = `${fieldPrefix || ''}${field.name}` - let target: string - const joinConfig = joins[field.collection].find( - ({ schemaPath }) => fieldSchemaPath === schemaPath, - ) - if (joinConfig.targetField.hasMany) { - target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${adapter.relationshipsSuffix}` - } else { - target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${localized ? adapter.localesSuffix : ''}` - } - relationsToBuild.set(fieldName, { - type: 'many', - // joins are not localized on the parent table - localized: false, - relationName: field.on.replaceAll('.', '_'), - target, - }) - break - } - default: break } diff --git a/packages/drizzle/src/find/buildFindManyArgs.ts b/packages/drizzle/src/find/buildFindManyArgs.ts index 63239975caf..418a3da3baa 100644 --- a/packages/drizzle/src/find/buildFindManyArgs.ts +++ b/packages/drizzle/src/find/buildFindManyArgs.ts @@ -16,6 +16,7 @@ type BuildFindQueryArgs = { joins?: BuildQueryJoinAliases locale?: string tableName: string + versions?: boolean } export type Result = { @@ -34,6 +35,7 @@ export const buildFindManyArgs = ({ joins = [], locale, tableName, + versions, }: BuildFindQueryArgs): Record => { const result: Result = { extras: {}, @@ -97,6 +99,7 @@ export const buildFindManyArgs = ({ tablePath: '', topLevelArgs: result, topLevelTableName: tableName, + versions, }) return result diff --git a/packages/drizzle/src/find/findMany.ts b/packages/drizzle/src/find/findMany.ts index a5500642b88..a3a8a42ba7f 100644 --- a/packages/drizzle/src/find/findMany.ts +++ b/packages/drizzle/src/find/findMany.ts @@ -14,6 +14,7 @@ type Args = { adapter: DrizzleAdapter fields: Field[] tableName: string + versions?: boolean } & Omit export const findMany = async function find({ @@ -28,6 +29,7 @@ export const findMany = async function find({ skip, sort, tableName, + versions, where: whereArg, }: Args) { const db = adapter.sessions[await req.transactionID]?.db || adapter.drizzle @@ -71,6 +73,7 @@ export const findMany = async function find({ joinQuery, joins, tableName, + versions, }) selectDistinctMethods.push({ args: [offset], method: 'offset' }) diff --git a/packages/drizzle/src/find/traverseFields.ts b/packages/drizzle/src/find/traverseFields.ts index 9c092a439b4..35f7a787508 100644 --- a/packages/drizzle/src/find/traverseFields.ts +++ b/packages/drizzle/src/find/traverseFields.ts @@ -1,4 +1,3 @@ -import type { DBQueryConfig } from 'drizzle-orm' import type { LibSQLDatabase } from 'drizzle-orm/libsql' import type { Field, JoinQuery } from 'payload' @@ -26,6 +25,7 @@ type TraverseFieldArgs = { tablePath: string topLevelArgs: Record topLevelTableName: string + versions?: boolean } export const traverseFields = ({ @@ -42,6 +42,7 @@ export const traverseFields = ({ tablePath, topLevelArgs, topLevelTableName, + versions, }: TraverseFieldArgs) => { fields.forEach((field) => { if (fieldIsVirtual(field)) { @@ -99,6 +100,7 @@ export const traverseFields = ({ tablePath: tabTablePath, topLevelArgs, topLevelTableName, + versions, }) }) @@ -223,6 +225,7 @@ export const traverseFields = ({ tablePath: `${tablePath}${toSnakeCase(field.name)}_`, topLevelArgs, topLevelTableName, + versions, }) break @@ -233,87 +236,156 @@ export const traverseFields = ({ if (joinQuery === false) { break } + const { limit: limitArg = 10, sort, where, } = joinQuery[`${path.replaceAll('_', '.')}${field.name}`] || {} let limit = limitArg + if (limit !== 0) { // get an additional document and slice it later to determine if there is a next page limit += 1 } const fields = adapter.payload.collections[field.collection].config.fields + const joinCollectionTableName = adapter.tableNameMap.get(toSnakeCase(field.collection)) - let joinTableName = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${ - field.localized && adapter.payload.config.localization ? adapter.localesSuffix : '' - }` + const joins: BuildQueryJoinAliases = [] + + const buildQueryResult = buildQuery({ + adapter, + fields, + joins, + locale, + sort, + tableName: joinCollectionTableName, + where, + }) + + let subQueryWhere = buildQueryResult.where + const orderBy = buildQueryResult.orderBy + + let joinLocalesCollectionTableName: string | undefined + + const currentIDColumn = versions + ? adapter.tables[currentTableName].parent + : adapter.tables[currentTableName].id + + // Handle hasMany _rels table if (field.hasMany) { - const db = adapter.drizzle as LibSQLDatabase + const joinRelsCollectionTableName = `${joinCollectionTableName}${adapter.relationshipsSuffix}` + if (field.localized) { - joinTableName = adapter.tableNameMap.get(toSnakeCase(field.collection)) + joinLocalesCollectionTableName = joinRelsCollectionTableName } - const joinTable = `${joinTableName}${adapter.relationshipsSuffix}` - const joins: BuildQueryJoinAliases = [ - { + let columnReferenceToCurrentID: string + + if (versions) { + columnReferenceToCurrentID = `${topLevelTableName.replace('_', '').replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id` + } else { + columnReferenceToCurrentID = `${topLevelTableName}_id` + } + + joins.push({ + type: 'innerJoin', + condition: and( + eq( + adapter.tables[joinRelsCollectionTableName].parent, + adapter.tables[joinCollectionTableName].id, + ), + eq( + sql.raw(`"${joinRelsCollectionTableName}"."${columnReferenceToCurrentID}"`), + currentIDColumn, + ), + eq(adapter.tables[joinRelsCollectionTableName].path, field.on), + ), + table: adapter.tables[joinRelsCollectionTableName], + }) + } else { + // Handle localized without hasMany + + const foreignColumn = field.on.replaceAll('.', '_') + + if (field.localized) { + joinLocalesCollectionTableName = `${joinCollectionTableName}${adapter.localesSuffix}` + + joins.push({ type: 'innerJoin', condition: and( - eq(adapter.tables[joinTable].parent, adapter.tables[joinTableName].id), eq( - sql.raw(`"${joinTable}"."${topLevelTableName}_id"`), - adapter.tables[currentTableName].id, + adapter.tables[joinLocalesCollectionTableName]._parentID, + adapter.tables[joinCollectionTableName].id, + ), + eq( + adapter.tables[joinLocalesCollectionTableName][foreignColumn], + currentIDColumn, ), - eq(adapter.tables[joinTable].path, field.on), ), - table: adapter.tables[joinTable], - }, - ] - - const { orderBy, where: subQueryWhere } = buildQuery({ - adapter, - fields, - joins, - locale, - sort, - tableName: joinCollectionTableName, - where: {}, - }) + table: adapter.tables[joinLocalesCollectionTableName], + }) + // Handle without localized and without hasMany, just a condition append to where. With localized the inner join handles eq. + } else { + const constraint = eq( + adapter.tables[joinCollectionTableName][foreignColumn], + currentIDColumn, + ) - const chainedMethods: ChainedMethods = [] + if (subQueryWhere) { + subQueryWhere = and(subQueryWhere, constraint) + } else { + subQueryWhere = constraint + } + } + } - joins.forEach(({ type, condition, table }) => { - chainedMethods.push({ - args: [table, condition], - method: type ?? 'leftJoin', - }) + const chainedMethods: ChainedMethods = [] + + joins.forEach(({ type, condition, table }) => { + chainedMethods.push({ + args: [table, condition], + method: type ?? 'leftJoin', }) + }) - const subQuery = chainMethods({ - methods: chainedMethods, - query: db - .select({ - id: adapter.tables[joinTableName].id, - ...(field.localized && { - locale: adapter.tables[joinTable].locale, - }), - }) - .from(adapter.tables[joinTableName]) - .where(subQueryWhere) - .orderBy(orderBy.order(orderBy.column)) - .limit(limit), + if (limit !== 0) { + chainedMethods.push({ + args: [limit], + method: 'limit', }) + } + + const db = adapter.drizzle as LibSQLDatabase + + const subQuery = chainMethods({ + methods: chainedMethods, + query: db + .select({ + id: adapter.tables[joinCollectionTableName].id, + ...(joinLocalesCollectionTableName && { + locale: + adapter.tables[joinLocalesCollectionTableName].locale || + adapter.tables[joinLocalesCollectionTableName]._locale, + }), + }) + .from(adapter.tables[joinCollectionTableName]) + .where(subQueryWhere) + .orderBy(orderBy.order(orderBy.column)), + }) - const columnName = `${path.replaceAll('.', '_')}${field.name}` + const columnName = `${path.replaceAll('.', '_')}${field.name}` - const jsonObjectSelect = field.localized - ? sql.raw(`'_parentID', "id", '_locale', "locale"`) - : sql.raw(`'id', "id"`) + const jsonObjectSelect = field.localized + ? sql.raw( + `'_parentID', "id", '_locale', "${adapter.tables[joinLocalesCollectionTableName].locale ? 'locale' : '_locale'}"`, + ) + : sql.raw(`'id', "id"`) - if (adapter.name === 'sqlite') { - currentArgs.extras[columnName] = sql` + if (adapter.name === 'sqlite') { + currentArgs.extras[columnName] = sql` COALESCE(( SELECT json_group_array(json_object(${jsonObjectSelect})) FROM ( @@ -321,8 +393,8 @@ export const traverseFields = ({ ) AS ${sql.raw(`${columnName}_sub`)} ), '[]') `.as(columnName) - } else { - currentArgs.extras[columnName] = sql` + } else { + currentArgs.extras[columnName] = sql` COALESCE(( SELECT json_agg(json_build_object(${jsonObjectSelect})) FROM ( @@ -330,41 +402,8 @@ export const traverseFields = ({ ) AS ${sql.raw(`${columnName}_sub`)} ), '[]'::json) `.as(columnName) - } - - break - } - - const selectFields = {} - - const withJoin: DBQueryConfig<'many', true, any, any> = { - columns: selectFields, - } - if (limit) { - withJoin.limit = limit } - if (field.localized) { - withJoin.columns._locale = true - withJoin.columns._parentID = true - } else { - withJoin.columns.id = true - withJoin.columns.parent = true - } - const { orderBy, where: joinWhere } = buildQuery({ - adapter, - fields, - joins, - locale, - sort, - tableName: joinTableName, - where, - }) - if (joinWhere) { - withJoin.where = () => joinWhere - } - withJoin.orderBy = orderBy.order(orderBy.column) - currentArgs.with[`${path.replaceAll('.', '_')}${field.name}`] = withJoin break } diff --git a/packages/drizzle/src/postgres/init.ts b/packages/drizzle/src/postgres/init.ts index c9ed8996d64..c76f4d9457f 100644 --- a/packages/drizzle/src/postgres/init.ts +++ b/packages/drizzle/src/postgres/init.ts @@ -57,7 +57,6 @@ export const init: Init = async function init(this: BasePostgresAdapter) { disableNotNull: !!collection?.versions?.drafts, disableUnique: false, fields: collection.fields, - joins: collection.joins, tableName, timestamps: collection.timestamps, versions: false, diff --git a/packages/drizzle/src/postgres/schema/build.ts b/packages/drizzle/src/postgres/schema/build.ts index c374b329f8a..e5a69ff2083 100644 --- a/packages/drizzle/src/postgres/schema/build.ts +++ b/packages/drizzle/src/postgres/schema/build.ts @@ -50,7 +50,6 @@ type Args = { disableRelsTableUnique?: boolean disableUnique: boolean fields: Field[] - joins?: SanitizedJoins rootRelationships?: Set rootRelationsToBuild?: RelationMap rootTableIDColType?: string @@ -83,7 +82,6 @@ export const buildTable = ({ disableRelsTableUnique = false, disableUnique = false, fields, - joins, rootRelationships, rootRelationsToBuild, rootTableIDColType, @@ -133,7 +131,6 @@ export const buildTable = ({ disableUnique, fields, indexes, - joins, localesColumns, localesIndexes, newTableName: tableName, diff --git a/packages/drizzle/src/postgres/schema/traverseFields.ts b/packages/drizzle/src/postgres/schema/traverseFields.ts index 81f8a82378b..4a53ea784c7 100644 --- a/packages/drizzle/src/postgres/schema/traverseFields.ts +++ b/packages/drizzle/src/postgres/schema/traverseFields.ts @@ -50,7 +50,6 @@ type Args = { fields: (Field | TabAsField)[] forceLocalized?: boolean indexes: Record IndexBuilder> - joins?: SanitizedJoins localesColumns: Record localesIndexes: Record IndexBuilder> newTableName: string @@ -89,7 +88,6 @@ export const traverseFields = ({ fields, forceLocalized, indexes, - joins, localesColumns, localesIndexes, newTableName, @@ -672,7 +670,6 @@ export const traverseFields = ({ fields: field.fields, forceLocalized, indexes, - joins, localesColumns, localesIndexes, newTableName, @@ -727,7 +724,6 @@ export const traverseFields = ({ fields: field.fields, forceLocalized: field.localized, indexes, - joins, localesColumns, localesIndexes, newTableName: `${parentTableName}_${columnName}`, @@ -783,7 +779,6 @@ export const traverseFields = ({ fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })), forceLocalized, indexes, - joins, localesColumns, localesIndexes, newTableName, @@ -839,7 +834,6 @@ export const traverseFields = ({ fields: field.fields, forceLocalized, indexes, - joins, localesColumns, localesIndexes, newTableName, @@ -936,30 +930,6 @@ export const traverseFields = ({ break - case 'join': { - // fieldName could be 'posts' or 'group_posts' - // using `on` as the key for the relation - const localized = adapter.payload.config.localization && field.localized - const fieldSchemaPath = `${fieldPrefix || ''}${field.name}` - let target: string - const joinConfig = joins[field.collection].find( - ({ schemaPath }) => fieldSchemaPath === schemaPath, - ) - if (joinConfig.targetField.hasMany) { - target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${adapter.relationshipsSuffix}` - } else { - target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${localized ? adapter.localesSuffix : ''}` - } - relationsToBuild.set(fieldName, { - type: 'many', - // joins are not localized on the parent table - localized: false, - relationName: field.on.replaceAll('.', '_'), - target, - }) - break - } - default: break } diff --git a/packages/drizzle/src/queryDrafts.ts b/packages/drizzle/src/queryDrafts.ts index 00386a2a503..9c32f1fbd7d 100644 --- a/packages/drizzle/src/queryDrafts.ts +++ b/packages/drizzle/src/queryDrafts.ts @@ -1,4 +1,4 @@ -import type { PayloadRequest, QueryDrafts, SanitizedCollectionConfig } from 'payload' +import type { JoinQuery, PayloadRequest, QueryDrafts, SanitizedCollectionConfig } from 'payload' import { buildVersionCollectionFields, combineQueries } from 'payload' import toSnakeCase from 'to-snake-case' @@ -9,7 +9,17 @@ import { findMany } from './find/findMany.js' export const queryDrafts: QueryDrafts = async function queryDrafts( this: DrizzleAdapter, - { collection, limit, locale, page = 1, pagination, req = {} as PayloadRequest, sort, where }, + { + collection, + joins, + limit, + locale, + page = 1, + pagination, + req = {} as PayloadRequest, + sort, + where, + }, ) { const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config const tableName = this.tableNameMap.get( @@ -22,6 +32,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts( const result = await findMany({ adapter: this, fields, + joins, limit, locale, page, @@ -29,6 +40,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts( req, sort, tableName, + versions: true, where: combinedWhere, }) diff --git a/packages/payload/src/collections/operations/find.ts b/packages/payload/src/collections/operations/find.ts index ccb41f8119b..cb90b774e8c 100644 --- a/packages/payload/src/collections/operations/find.ts +++ b/packages/payload/src/collections/operations/find.ts @@ -126,6 +126,7 @@ export const findOperation = async ( result = await payload.db.queryDrafts>({ collection: collectionConfig.slug, + joins: req.payloadAPI === 'GraphQL' ? false : joins, limit: sanitizedLimit, locale, page: sanitizedPage, diff --git a/packages/payload/src/database/types.ts b/packages/payload/src/database/types.ts index e82564d3e69..03b0967ddbd 100644 --- a/packages/payload/src/database/types.ts +++ b/packages/payload/src/database/types.ts @@ -174,6 +174,7 @@ export type CommitTransaction = (id: number | Promise | string) export type QueryDraftsArgs = { collection: string + joins?: JoinQuery limit?: number locale?: string page?: number diff --git a/test/joins/collections/CategoriesVersions.ts b/test/joins/collections/CategoriesVersions.ts new file mode 100644 index 00000000000..521cd2b92b5 --- /dev/null +++ b/test/joins/collections/CategoriesVersions.ts @@ -0,0 +1,20 @@ +import type { CollectionConfig } from 'payload' + +import { versionsSlug } from './Versions.js' + +export const categoriesVersionsSlug = 'categories-versions' + +export const CategoriesVersions: CollectionConfig = { + slug: categoriesVersionsSlug, + fields: [ + { + name: 'relatedVersions', + type: 'join', + collection: versionsSlug, + on: 'categoryVersion', + }, + ], + versions: { + drafts: true, + }, +} diff --git a/test/joins/collections/Versions.ts b/test/joins/collections/Versions.ts new file mode 100644 index 00000000000..5c1f2d78482 --- /dev/null +++ b/test/joins/collections/Versions.ts @@ -0,0 +1,22 @@ +import type { CollectionConfig } from 'payload' + +export const versionsSlug = 'versions' + +export const Versions: CollectionConfig = { + slug: versionsSlug, + fields: [ + { + name: 'category', + relationTo: 'categories', + type: 'relationship', + }, + { + name: 'categoryVersion', + relationTo: 'categories-versions', + type: 'relationship', + }, + ], + versions: { + drafts: true, + }, +} diff --git a/test/joins/config.ts b/test/joins/config.ts index 4a083adda33..8d56b04e5fe 100644 --- a/test/joins/config.ts +++ b/test/joins/config.ts @@ -3,8 +3,10 @@ import path from 'path' import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' import { Categories } from './collections/Categories.js' +import { CategoriesVersions } from './collections/CategoriesVersions.js' import { Posts } from './collections/Posts.js' import { Uploads } from './collections/Uploads.js' +import { Versions } from './collections/Versions.js' import { seed } from './seed.js' import { localizedCategoriesSlug, localizedPostsSlug } from './shared.js' @@ -16,6 +18,8 @@ export default buildConfigWithDefaults({ Posts, Categories, Uploads, + Versions, + CategoriesVersions, { slug: localizedPostsSlug, admin: { diff --git a/test/joins/int.spec.ts b/test/joins/int.spec.ts index e5b0857f729..d700379dda8 100644 --- a/test/joins/int.spec.ts +++ b/test/joins/int.spec.ts @@ -1,4 +1,4 @@ -import type { Payload } from 'payload' +import type { Payload, TypeWithID } from 'payload' import path from 'path' import { getFileByPath } from 'payload' @@ -373,6 +373,42 @@ describe('Joins Field', () => { }) }) + describe('Joins with versions', () => { + afterEach(async () => { + await payload.delete({ collection: 'versions', where: {} }) + await payload.delete({ collection: 'categories-versions', where: {} }) + }) + + it('should populate joins 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: { categoryVersion: category.id }, + }) + + const res = await payload.find({ collection: 'categories-versions', draft: false }) + + 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: { categoryVersion: category.id }, + }) + + const res = await payload.find({ + collection: 'categories-versions', + draft: true, + }) + + expect(res.docs[0].relatedVersions.docs[0].id).toBe(version.id) + }) + }) + describe('REST', () => { it('should have simple paginate for joins', async () => { const query = { diff --git a/test/joins/payload-types.ts b/test/joins/payload-types.ts index b792dfa96d3..075594ca7df 100644 --- a/test/joins/payload-types.ts +++ b/test/joins/payload-types.ts @@ -14,6 +14,8 @@ export interface Config { posts: Post; categories: Category; uploads: Upload; + versions: Version; + 'categories-versions': CategoriesVersion; 'localized-posts': LocalizedPost; 'localized-categories': LocalizedCategory; users: User; @@ -22,7 +24,7 @@ export interface Config { 'payload-migrations': PayloadMigration; }; db: { - defaultIDType: number; + defaultIDType: string; }; globals: {}; locale: 'en' | 'es'; @@ -53,15 +55,15 @@ export interface UserAuthOperations { * via the `definition` "posts". */ export interface Post { - id: number; + id: string; title?: string | null; - upload?: (number | null) | Upload; - category?: (number | null) | Category; - categories?: (number | Category)[] | null; - categoriesLocalized?: (number | Category)[] | null; + upload?: (string | null) | Upload; + category?: (string | null) | Category; + categories?: (string | Category)[] | null; + categoriesLocalized?: (string | Category)[] | null; group?: { - category?: (number | null) | Category; - camelCaseCategory?: (number | null) | Category; + category?: (string | null) | Category; + camelCaseCategory?: (string | null) | Category; }; updatedAt: string; createdAt: string; @@ -71,9 +73,9 @@ export interface Post { * via the `definition` "uploads". */ export interface Upload { - id: number; + id: string; relatedPosts?: { - docs?: (number | Post)[] | null; + docs?: (string | Post)[] | null; hasNextPage?: boolean | null; } | null; updatedAt: string; @@ -93,41 +95,67 @@ export interface Upload { * via the `definition` "categories". */ export interface Category { - id: number; + id: string; name?: string | null; relatedPosts?: { - docs?: (number | Post)[] | null; + docs?: (string | Post)[] | null; hasNextPage?: boolean | null; } | null; hasManyPosts?: { - docs?: (number | Post)[] | null; + docs?: (string | Post)[] | null; hasNextPage?: boolean | null; } | null; hasManyPostsLocalized?: { - docs?: (number | Post)[] | null; + docs?: (string | Post)[] | null; hasNextPage?: boolean | null; } | null; group?: { relatedPosts?: { - docs?: (number | Post)[] | null; + docs?: (string | Post)[] | null; hasNextPage?: boolean | null; } | null; camelCasePosts?: { - docs?: (number | Post)[] | null; + docs?: (string | Post)[] | null; hasNextPage?: boolean | null; } | null; }; updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "versions". + */ +export interface Version { + id: string; + category?: (string | null) | Category; + categoryVersion?: (string | null) | CategoriesVersion; + updatedAt: string; + createdAt: string; + _status?: ('draft' | 'published') | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "categories-versions". + */ +export interface CategoriesVersion { + id: string; + relatedVersions?: { + docs?: (string | Version)[] | null; + hasNextPage?: boolean | null; + } | null; + updatedAt: string; + createdAt: string; + _status?: ('draft' | 'published') | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "localized-posts". */ export interface LocalizedPost { - id: number; + id: string; title?: string | null; - category?: (number | null) | LocalizedCategory; + category?: (string | null) | LocalizedCategory; updatedAt: string; createdAt: string; } @@ -136,10 +164,10 @@ export interface LocalizedPost { * via the `definition` "localized-categories". */ export interface LocalizedCategory { - id: number; + id: string; name?: string | null; relatedPosts?: { - docs?: (number | LocalizedPost)[] | null; + docs?: (string | LocalizedPost)[] | null; hasNextPage?: boolean | null; } | null; updatedAt: string; @@ -150,7 +178,7 @@ export interface LocalizedCategory { * via the `definition` "users". */ export interface User { - id: number; + id: string; updatedAt: string; createdAt: string; email: string; @@ -167,36 +195,44 @@ export interface User { * via the `definition` "payload-locked-documents". */ export interface PayloadLockedDocument { - id: number; + id: string; document?: | ({ relationTo: 'posts'; - value: number | Post; + value: string | Post; } | null) | ({ relationTo: 'categories'; - value: number | Category; + value: string | Category; } | null) | ({ relationTo: 'uploads'; - value: number | Upload; + value: string | Upload; + } | null) + | ({ + relationTo: 'versions'; + value: string | Version; + } | null) + | ({ + relationTo: 'categories-versions'; + value: string | CategoriesVersion; } | null) | ({ relationTo: 'localized-posts'; - value: number | LocalizedPost; + value: string | LocalizedPost; } | null) | ({ relationTo: 'localized-categories'; - value: number | LocalizedCategory; + value: string | LocalizedCategory; } | null) | ({ relationTo: 'users'; - value: number | User; + value: string | User; } | null); globalSlug?: string | null; user: { relationTo: 'users'; - value: number | User; + value: string | User; }; updatedAt: string; createdAt: string; @@ -206,10 +242,10 @@ export interface PayloadLockedDocument { * via the `definition` "payload-preferences". */ export interface PayloadPreference { - id: number; + id: string; user: { relationTo: 'users'; - value: number | User; + value: string | User; }; key?: string | null; value?: @@ -229,7 +265,7 @@ export interface PayloadPreference { * via the `definition` "payload-migrations". */ export interface PayloadMigration { - id: number; + id: string; name?: string | null; batch?: number | null; updatedAt: string;