fix: join field works on collections with versions enabled (#8715)
- Fixes errors with drizzle when building the schema https://github.com/payloadcms/payload/issues/8680 - Adds `joins` to `db.queryDrafts` to have them when doing `.find` with `draft: true`
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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<PipelineStage[] | undefined> => {
|
||||
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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -61,7 +61,6 @@ type Args = {
|
||||
disableRelsTableUnique?: boolean
|
||||
disableUnique: boolean
|
||||
fields: Field[]
|
||||
joins?: SanitizedJoins
|
||||
locales?: [string, ...string[]]
|
||||
rootRelationships?: Set<string>
|
||||
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,
|
||||
|
||||
@@ -44,7 +44,6 @@ type Args = {
|
||||
fields: (Field | TabAsField)[]
|
||||
forceLocalized?: boolean
|
||||
indexes: Record<string, (cols: GenericColumns) => IndexBuilder>
|
||||
joins?: SanitizedJoins
|
||||
locales: [string, ...string[]]
|
||||
localesColumns: Record<string, SQLiteColumnBuilder>
|
||||
localesIndexes: Record<string, (cols: GenericColumns) => 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
|
||||
}
|
||||
|
||||
@@ -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<string, unknown> => {
|
||||
const result: Result = {
|
||||
extras: {},
|
||||
@@ -97,6 +99,7 @@ export const buildFindManyArgs = ({
|
||||
tablePath: '',
|
||||
topLevelArgs: result,
|
||||
topLevelTableName: tableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -14,6 +14,7 @@ type Args = {
|
||||
adapter: DrizzleAdapter
|
||||
fields: Field[]
|
||||
tableName: string
|
||||
versions?: boolean
|
||||
} & Omit<FindArgs, 'collection'>
|
||||
|
||||
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' })
|
||||
|
||||
@@ -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<string, unknown>
|
||||
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
|
||||
if (field.localized) {
|
||||
joinTableName = adapter.tableNameMap.get(toSnakeCase(field.collection))
|
||||
}
|
||||
const joinTable = `${joinTableName}${adapter.relationshipsSuffix}`
|
||||
const joinRelsCollectionTableName = `${joinCollectionTableName}${adapter.relationshipsSuffix}`
|
||||
|
||||
const joins: BuildQueryJoinAliases = [
|
||||
{
|
||||
if (field.localized) {
|
||||
joinLocalesCollectionTableName = joinRelsCollectionTableName
|
||||
}
|
||||
|
||||
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: {},
|
||||
})
|
||||
|
||||
const chainedMethods: ChainedMethods = []
|
||||
|
||||
joins.forEach(({ type, condition, table }) => {
|
||||
chainedMethods.push({
|
||||
args: [table, condition],
|
||||
method: type ?? 'leftJoin',
|
||||
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,
|
||||
)
|
||||
|
||||
if (subQueryWhere) {
|
||||
subQueryWhere = and(subQueryWhere, constraint)
|
||||
} else {
|
||||
subQueryWhere = constraint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 columnName = `${path.replaceAll('.', '_')}${field.name}`
|
||||
const db = adapter.drizzle as LibSQLDatabase
|
||||
|
||||
const jsonObjectSelect = field.localized
|
||||
? sql.raw(`'_parentID', "id", '_locale', "locale"`)
|
||||
: sql.raw(`'id', "id"`)
|
||||
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)),
|
||||
})
|
||||
|
||||
if (adapter.name === 'sqlite') {
|
||||
currentArgs.extras[columnName] = sql`
|
||||
const columnName = `${path.replaceAll('.', '_')}${field.name}`
|
||||
|
||||
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`
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -50,7 +50,6 @@ type Args = {
|
||||
disableRelsTableUnique?: boolean
|
||||
disableUnique: boolean
|
||||
fields: Field[]
|
||||
joins?: SanitizedJoins
|
||||
rootRelationships?: Set<string>
|
||||
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,
|
||||
|
||||
@@ -50,7 +50,6 @@ type Args = {
|
||||
fields: (Field | TabAsField)[]
|
||||
forceLocalized?: boolean
|
||||
indexes: Record<string, (cols: GenericColumns) => IndexBuilder>
|
||||
joins?: SanitizedJoins
|
||||
localesColumns: Record<string, PgColumnBuilder>
|
||||
localesIndexes: Record<string, (cols: GenericColumns) => 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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ export const findOperation = async <TSlug extends CollectionSlug>(
|
||||
|
||||
result = await payload.db.queryDrafts<DataFromCollectionSlug<TSlug>>({
|
||||
collection: collectionConfig.slug,
|
||||
joins: req.payloadAPI === 'GraphQL' ? false : joins,
|
||||
limit: sanitizedLimit,
|
||||
locale,
|
||||
page: sanitizedPage,
|
||||
|
||||
@@ -174,6 +174,7 @@ export type CommitTransaction = (id: number | Promise<number | string> | string)
|
||||
|
||||
export type QueryDraftsArgs = {
|
||||
collection: string
|
||||
joins?: JoinQuery
|
||||
limit?: number
|
||||
locale?: string
|
||||
page?: number
|
||||
|
||||
20
test/joins/collections/CategoriesVersions.ts
Normal file
20
test/joins/collections/CategoriesVersions.ts
Normal file
@@ -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,
|
||||
},
|
||||
}
|
||||
22
test/joins/collections/Versions.ts
Normal file
22
test/joins/collections/Versions.ts
Normal file
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user