You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(drizzle): error when using contains operator on hasMany select fields (#15865)
Fixes `contains` operator on hasMany `select` fields in PostgreSQL. This
was a regression introduced in #15671.
## The problem
Given a collection with a hasMany `select` field:
```typescript
{
slug: 'users',
fields: [
{
name: 'roles',
type: 'select',
hasMany: true,
options: ['user', 'admin', 'editor'],
},
],
}
```
Payload stores this across two PostgreSQL tables. For a user created
with `roles: ['admin', 'editor']`:
**`users`**
| id | created_at | updated_at |
|----|------------|------------|
| 1 | 2026-03-05 | 2026-03-05 |
**`users_roles`**
| id | parent_id | value | order |
|----|-----------|----------|-------|
| 1 | 1 | `admin` | 1 |
| 2 | 1 | `editor` | 2 |
The `value` column is a PostgreSQL **enum type**, not a `varchar`.
When querying `{ roles: { contains: 'admin' } }`, Payload generated:
```sql
SELECT DISTINCT "users"."id"
FROM "users"
LEFT JOIN "users_roles"
ON "users"."id" = "users_roles"."parent_id"
WHERE "users_roles"."value" ILIKE '%admin%'
-- ❌ PostgreSQL error: operator does not exist: enum_users_roles ~~* unknown
```
`ILIKE` does not work on PostgreSQL enum types.
## The fix
The LEFT JOIN already flattens the array into individual rows:
| users.id | users_roles.value |
|----------|-------------------|
| 1 | `admin` |
| 1 | `editor` |
So "contains admin" just means "has a row where value equals admin". The
fix converts `contains` to `equals` for hasMany `select` fields, the
same way it already works for hasMany `relationship` and `upload`
fields:
```sql
SELECT DISTINCT "users"."id"
FROM "users"
LEFT JOIN "users_roles"
ON "users"."id" = "users_roles"."parent_id"
WHERE "users_roles"."value" = 'admin'
-- ✅ Matches row 1, returns user id=1
```
## Why hasMany `text` fields are different
HasMany `text` fields store values as `varchar`, not as an enum. `ILIKE`
works fine on `varchar` columns, so substring matching (`contains:
'adm'` matching `'admin'`) is valid there. Whether it's _desirable_ is a
problem separate from this PR, being discussed internally
[here](https://payloadcms.slack.com/archives/C049BR3QBHC/p1772680331359859)
0 commit comments