fix(drizzle): equals polymorphic querying with object notation (#8316)
Previously, this wasn't valid in Postgres / SQLite:
```ts
const res = await payload.find({
collection: 'polymorphic-relationships',
where: {
polymorphic: {
equals: {
relationTo: 'movies',
value: movie.id,
},
},
},
})
```
Now it works and actually in more performant way than this:
```ts
const res = await payload.find({
collection: 'polymorphic-relationships',
where: {
and: [
{
'polymorphic.relationTo': {
equals: 'movies',
},
},
{
'polymorphic.value': {
equals: 'movies',
},
},
],
},
})
```
Why? Because with the object notation, the output SQL is: `movies_id =
1` - checks exactly 1 column in the `*_rels` table, while with the
separate query by `relationTo` and `value` we need to check against
_each_ possible relationship collection with OR.
This commit is contained in:
@@ -12,6 +12,7 @@ import { validate as uuidValidate } from 'uuid'
|
||||
import type { DrizzleAdapter, GenericColumn } from '../types.js'
|
||||
import type { BuildQueryJoinAliases } from './buildQuery.js'
|
||||
|
||||
import { isPolymorphicRelationship } from '../utilities/isPolymorphicRelationship.js'
|
||||
import { getTableAlias } from './getTableAlias.js'
|
||||
|
||||
type Constraint = {
|
||||
@@ -603,6 +604,19 @@ export const getTableColumnFromPath = ({
|
||||
},
|
||||
table: aliasRelationshipTable,
|
||||
}
|
||||
} else if (isPolymorphicRelationship(value)) {
|
||||
const { relationTo } = value
|
||||
|
||||
const relationTableName = adapter.tableNameMap.get(
|
||||
toSnakeCase(adapter.payload.collections[relationTo].config.slug),
|
||||
)
|
||||
|
||||
return {
|
||||
constraints,
|
||||
field,
|
||||
rawColumn: sql.raw(`"${aliasRelationshipTableName}"."${relationTableName}_id"`),
|
||||
table: aliasRelationshipTable,
|
||||
}
|
||||
} else {
|
||||
throw new APIError('Not supported')
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import { validate as uuidValidate } from 'uuid'
|
||||
|
||||
import type { DrizzleAdapter } from '../types.js'
|
||||
|
||||
import { getCollectionIdType } from '../utilities/getCollectionIdType.js'
|
||||
import { isPolymorphicRelationship } from '../utilities/isPolymorphicRelationship.js'
|
||||
|
||||
type SanitizeQueryValueArgs = {
|
||||
adapter: DrizzleAdapter
|
||||
columns?: {
|
||||
@@ -107,17 +110,28 @@ export const sanitizeQueryValue = ({
|
||||
// convert the value to the idType of the relationship
|
||||
let idType: 'number' | 'text'
|
||||
if (typeof field.relationTo === 'string') {
|
||||
const collection = adapter.payload.collections[field.relationTo]
|
||||
const mixedType: 'number' | 'serial' | 'text' | 'uuid' =
|
||||
collection.customIDType || adapter.idType
|
||||
const typeMap: Record<string, 'number' | 'text'> = {
|
||||
number: 'number',
|
||||
serial: 'number',
|
||||
text: 'text',
|
||||
uuid: 'text',
|
||||
}
|
||||
idType = typeMap[mixedType]
|
||||
idType = getCollectionIdType({
|
||||
adapter,
|
||||
collection: adapter.payload.collections[field.relationTo],
|
||||
})
|
||||
} else {
|
||||
if (isPolymorphicRelationship(val)) {
|
||||
if (operator !== 'equals') {
|
||||
throw new APIError(
|
||||
`Only 'equals' operator is supported for polymorphic relationship object notation. Given - ${operator}`,
|
||||
)
|
||||
}
|
||||
idType = getCollectionIdType({
|
||||
adapter,
|
||||
collection: adapter.payload.collections[val.relationTo],
|
||||
})
|
||||
|
||||
return {
|
||||
operator,
|
||||
value: idType === 'number' ? Number(val.value) : String(val.value),
|
||||
}
|
||||
}
|
||||
|
||||
formattedColumns = columns
|
||||
.map(({ idType, rawColumn }) => {
|
||||
let formattedValue: number | number[] | string | string[]
|
||||
|
||||
20
packages/drizzle/src/utilities/getCollectionIdType.ts
Normal file
20
packages/drizzle/src/utilities/getCollectionIdType.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { Collection } from 'payload'
|
||||
|
||||
import type { DrizzleAdapter } from '../types.js'
|
||||
|
||||
const typeMap: Record<string, 'number' | 'text'> = {
|
||||
number: 'number',
|
||||
serial: 'number',
|
||||
text: 'text',
|
||||
uuid: 'text',
|
||||
}
|
||||
|
||||
export const getCollectionIdType = ({
|
||||
adapter,
|
||||
collection,
|
||||
}: {
|
||||
adapter: DrizzleAdapter
|
||||
collection: Collection
|
||||
}) => {
|
||||
return collection.customIDType ?? typeMap[adapter.idType]
|
||||
}
|
||||
16
packages/drizzle/src/utilities/isPolymorphicRelationship.ts
Normal file
16
packages/drizzle/src/utilities/isPolymorphicRelationship.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { CollectionSlug } from 'payload'
|
||||
|
||||
export const isPolymorphicRelationship = (
|
||||
value: unknown,
|
||||
): value is {
|
||||
relationTo: CollectionSlug
|
||||
value: number | string
|
||||
} => {
|
||||
return (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
'relationTo' in value &&
|
||||
typeof value.relationTo === 'string' &&
|
||||
'value' in value
|
||||
)
|
||||
}
|
||||
@@ -186,12 +186,12 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null);
|
||||
editedAt?: string | null;
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
editedAt?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
|
||||
import { randomBytes } from 'crypto'
|
||||
import { randomBytes, randomUUID } from 'crypto'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -1168,6 +1168,51 @@ describe('Relationships', () => {
|
||||
expect(queryOne.docs).toHaveLength(1)
|
||||
expect(queryTwo.docs).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should allow querying on polymorphic relationships with an object syntax', async () => {
|
||||
const movie = await payload.create({
|
||||
collection: 'movies',
|
||||
data: {
|
||||
name: 'Pulp Fiction 2',
|
||||
},
|
||||
})
|
||||
await payload.create({
|
||||
collection: polymorphicRelationshipsSlug,
|
||||
data: {
|
||||
polymorphic: {
|
||||
relationTo: 'movies',
|
||||
value: movie.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const res = await payload.find({
|
||||
collection: 'polymorphic-relationships',
|
||||
where: {
|
||||
polymorphic: {
|
||||
equals: {
|
||||
relationTo: 'movies',
|
||||
value: movie.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(res.docs).toHaveLength(1)
|
||||
|
||||
const res_2 = await payload.find({
|
||||
collection: 'polymorphic-relationships',
|
||||
where: {
|
||||
polymorphic: {
|
||||
equals: {
|
||||
relationTo: 'movies',
|
||||
value: payload.db.idType === 'uuid' ? randomUUID() : 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(res_2.docs).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user