Skip to content

Commit 1bf580f

Browse files
DanRibbensr1tsuu
andauthored
feat: join field works with hasMany relationships (#8493)
Join field works on relationships and uploads having `hasMany: true` --------- Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
1 parent ca77944 commit 1bf580f

File tree

18 files changed

+275
-40
lines changed

18 files changed

+275
-40
lines changed

packages/db-sqlite/src/init.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const init: Init = async function init(this: SQLiteAdapter) {
7070
disableNotNull: !!collection?.versions?.drafts,
7171
disableUnique: false,
7272
fields: collection.fields,
73+
joins: collection.joins,
7374
locales,
7475
tableName,
7576
timestamps: collection.timestamps,

packages/db-sqlite/src/schema/build.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
SQLiteTableWithColumns,
88
UniqueConstraintBuilder,
99
} from 'drizzle-orm/sqlite-core'
10-
import type { Field } from 'payload'
10+
import type { Field, SanitizedJoins } from 'payload'
1111

1212
import { createTableName } from '@payloadcms/drizzle'
1313
import { relations, sql } from 'drizzle-orm'
@@ -58,6 +58,7 @@ type Args = {
5858
disableNotNull: boolean
5959
disableUnique: boolean
6060
fields: Field[]
61+
joins?: SanitizedJoins
6162
locales?: [string, ...string[]]
6263
rootRelationships?: Set<string>
6364
rootRelationsToBuild?: RelationMap
@@ -89,6 +90,7 @@ export const buildTable = ({
8990
disableNotNull,
9091
disableUnique = false,
9192
fields,
93+
joins,
9294
locales,
9395
rootRelationships,
9496
rootRelationsToBuild,
@@ -134,6 +136,7 @@ export const buildTable = ({
134136
disableUnique,
135137
fields,
136138
indexes,
139+
joins,
137140
locales,
138141
localesColumns,
139142
localesIndexes,

packages/db-sqlite/src/schema/traverseFields.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Relation } from 'drizzle-orm'
22
import type { IndexBuilder, SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
3-
import type { Field, TabAsField } from 'payload'
3+
import type { Field, SanitizedJoins, TabAsField } from 'payload'
44

55
import {
66
createTableName,
@@ -41,6 +41,7 @@ type Args = {
4141
fields: (Field | TabAsField)[]
4242
forceLocalized?: boolean
4343
indexes: Record<string, (cols: GenericColumns) => IndexBuilder>
44+
joins?: SanitizedJoins
4445
locales: [string, ...string[]]
4546
localesColumns: Record<string, SQLiteColumnBuilder>
4647
localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder>
@@ -78,6 +79,7 @@ export const traverseFields = ({
7879
fields,
7980
forceLocalized,
8081
indexes,
82+
joins,
8183
locales,
8284
localesColumns,
8385
localesIndexes,
@@ -651,6 +653,7 @@ export const traverseFields = ({
651653
fields: field.fields,
652654
forceLocalized,
653655
indexes,
656+
joins,
654657
locales,
655658
localesColumns,
656659
localesIndexes,
@@ -705,6 +708,7 @@ export const traverseFields = ({
705708
fields: field.fields,
706709
forceLocalized: field.localized,
707710
indexes,
711+
joins,
708712
locales,
709713
localesColumns,
710714
localesIndexes,
@@ -760,6 +764,7 @@ export const traverseFields = ({
760764
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
761765
forceLocalized,
762766
indexes,
767+
joins,
763768
locales,
764769
localesColumns,
765770
localesIndexes,
@@ -815,6 +820,7 @@ export const traverseFields = ({
815820
fields: field.fields,
816821
forceLocalized,
817822
indexes,
823+
joins,
818824
locales,
819825
localesColumns,
820826
localesIndexes,
@@ -905,9 +911,18 @@ export const traverseFields = ({
905911

906912
case 'join': {
907913
// fieldName could be 'posts' or 'group_posts'
908-
// using on as the key for the relation
914+
// using `on` as the key for the relation
909915
const localized = adapter.payload.config.localization && field.localized
910-
const target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${localized ? adapter.localesSuffix : ''}`
916+
const fieldSchemaPath = `${fieldPrefix || ''}${field.name}`
917+
let target: string
918+
const joinConfig = joins[field.collection].find(
919+
({ schemaPath }) => fieldSchemaPath === schemaPath,
920+
)
921+
if (joinConfig.targetField.hasMany) {
922+
target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${adapter.relationshipsSuffix}`
923+
} else {
924+
target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${localized ? adapter.localesSuffix : ''}`
925+
}
911926
relationsToBuild.set(fieldName, {
912927
type: 'many',
913928
// joins are not localized on the parent table

packages/drizzle/src/find/buildFindManyArgs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const buildFindManyArgs = ({
3636
tableName,
3737
}: BuildFindQueryArgs): Record<string, unknown> => {
3838
const result: Result = {
39+
extras: {},
3940
with: {},
4041
}
4142

@@ -44,6 +45,7 @@ export const buildFindManyArgs = ({
4445
id: false,
4546
_parentID: false,
4647
},
48+
extras: {},
4749
with: {},
4850
}
4951

packages/drizzle/src/find/traverseFields.ts

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import type { DBQueryConfig } from 'drizzle-orm'
1+
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
22
import type { Field, JoinQuery } from 'payload'
33

4+
import { and, type DBQueryConfig, eq, sql } from 'drizzle-orm'
45
import { fieldAffectsData, fieldIsVirtual, tabHasName } from 'payload/shared'
56
import toSnakeCase from 'to-snake-case'
67

7-
import type { BuildQueryJoinAliases, DrizzleAdapter } from '../types.js'
8+
import type { BuildQueryJoinAliases, ChainedMethods, DrizzleAdapter } from '../types.js'
89
import type { Result } from './buildFindManyArgs.js'
910

10-
import { buildOrderBy } from '../queries/buildOrderBy.js'
1111
import buildQuery from '../queries/buildQuery.js'
12+
import { chainMethods } from './chainMethods.js'
1213

1314
type TraverseFieldArgs = {
1415
_locales: Result
@@ -241,24 +242,93 @@ export const traverseFields = ({
241242
// get an additional document and slice it later to determine if there is a next page
242243
limit += 1
243244
}
245+
244246
const fields = adapter.payload.collections[field.collection].config.fields
247+
const joinCollectionTableName = adapter.tableNameMap.get(toSnakeCase(field.collection))
245248
const joinTableName = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${
246249
field.localized && adapter.payload.config.localization ? adapter.localesSuffix : ''
247250
}`
251+
252+
if (!adapter.tables[joinTableName][field.on]) {
253+
const db = adapter.drizzle as LibSQLDatabase
254+
const joinTable = `${joinTableName}${adapter.relationshipsSuffix}`
255+
256+
const joins: BuildQueryJoinAliases = [
257+
{
258+
type: 'innerJoin',
259+
condition: and(
260+
eq(adapter.tables[joinTable].parent, adapter.tables[joinTableName].id),
261+
eq(
262+
sql.raw(`"${joinTable}"."${topLevelTableName}_id"`),
263+
adapter.tables[currentTableName].id,
264+
),
265+
),
266+
table: adapter.tables[joinTable],
267+
},
268+
]
269+
270+
const { orderBy, where: subQueryWhere } = buildQuery({
271+
adapter,
272+
fields,
273+
joins,
274+
locale,
275+
sort,
276+
tableName: joinCollectionTableName,
277+
where: {},
278+
})
279+
280+
const chainedMethods: ChainedMethods = []
281+
282+
joins.forEach(({ type, condition, table }) => {
283+
chainedMethods.push({
284+
args: [table, condition],
285+
method: type ?? 'leftJoin',
286+
})
287+
})
288+
289+
const subQuery = chainMethods({
290+
methods: chainedMethods,
291+
query: db
292+
.select({
293+
id: adapter.tables[joinTableName].id,
294+
})
295+
.from(adapter.tables[joinTableName])
296+
.where(subQueryWhere)
297+
.orderBy(orderBy.order(orderBy.column))
298+
.limit(11),
299+
})
300+
301+
const columnName = `${path.replaceAll('.', '_')}${field.name}`
302+
303+
const extras = field.localized ? _locales.extras : currentArgs.extras
304+
305+
if (adapter.name === 'sqlite') {
306+
extras[columnName] = sql`
307+
COALESCE((
308+
SELECT json_group_array("id")
309+
FROM (
310+
${subQuery}
311+
) AS ${sql.raw(`${columnName}_sub`)}
312+
), '[]')
313+
`.as(columnName)
314+
} else {
315+
extras[columnName] = sql`
316+
COALESCE((
317+
SELECT json_agg("id")
318+
FROM (
319+
${subQuery}
320+
) AS ${sql.raw(`${columnName}_sub`)}
321+
), '[]'::json)
322+
`.as(columnName)
323+
}
324+
325+
break
326+
}
327+
248328
const selectFields = {}
249329

250-
const orderBy = buildOrderBy({
251-
adapter,
252-
fields,
253-
joins: [],
254-
locale,
255-
selectFields,
256-
sort,
257-
tableName: joinTableName,
258-
})
259330
const withJoin: DBQueryConfig<'many', true, any, any> = {
260331
columns: selectFields,
261-
orderBy: () => [orderBy.order(orderBy.column)],
262332
}
263333
if (limit) {
264334
withJoin.limit = limit
@@ -269,20 +339,21 @@ export const traverseFields = ({
269339
withJoin.columns._parentID = true
270340
} else {
271341
withJoin.columns.id = true
342+
withJoin.columns.parent = true
272343
}
273-
274-
if (where) {
275-
const { where: joinWhere } = buildQuery({
276-
adapter,
277-
fields,
278-
joins,
279-
locale,
280-
sort,
281-
tableName: joinTableName,
282-
where,
283-
})
344+
const { orderBy, where: joinWhere } = buildQuery({
345+
adapter,
346+
fields,
347+
joins,
348+
locale,
349+
sort,
350+
tableName: joinTableName,
351+
where,
352+
})
353+
if (joinWhere) {
284354
withJoin.where = () => joinWhere
285355
}
356+
withJoin.orderBy = orderBy.order(orderBy.column)
286357
currentArgs.with[`${path.replaceAll('.', '_')}${field.name}`] = withJoin
287358
break
288359
}

packages/drizzle/src/postgres/init.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const init: Init = async function init(this: BasePostgresAdapter) {
5757
disableNotNull: !!collection?.versions?.drafts,
5858
disableUnique: false,
5959
fields: collection.fields,
60+
joins: collection.joins,
6061
tableName,
6162
timestamps: collection.timestamps,
6263
versions: false,

packages/drizzle/src/postgres/schema/build.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
PgColumnBuilder,
66
PgTableWithColumns,
77
} from 'drizzle-orm/pg-core'
8-
import type { Field } from 'payload'
8+
import type { Field, SanitizedJoins } from 'payload'
99

1010
import { relations } from 'drizzle-orm'
1111
import {
@@ -47,6 +47,7 @@ type Args = {
4747
disableNotNull: boolean
4848
disableUnique: boolean
4949
fields: Field[]
50+
joins?: SanitizedJoins
5051
rootRelationships?: Set<string>
5152
rootRelationsToBuild?: RelationMap
5253
rootTableIDColType?: string
@@ -77,6 +78,7 @@ export const buildTable = ({
7778
disableNotNull,
7879
disableUnique = false,
7980
fields,
81+
joins,
8082
rootRelationships,
8183
rootRelationsToBuild,
8284
rootTableIDColType,
@@ -121,6 +123,7 @@ export const buildTable = ({
121123
disableUnique,
122124
fields,
123125
indexes,
126+
joins,
124127
localesColumns,
125128
localesIndexes,
126129
newTableName: tableName,

0 commit comments

Comments
 (0)