drizzle-team / drizzle-team/drizzle-orm
[FEATURE]: RQBv2 support custom SQL expressions in relation `from`/`to`
- Dominant language
- TypeScript
- Stars
- 35.8k
- Forks
- 1.6k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 4
Description
### Feature hasn't been suggested before.
- [x] I have verified this feature I'm about to request hasn't been suggested before.
### Describe the enhancement you want to request
#### Problem
When defining relations between columns of incompatible types (e.g., `bigint` to `varchar`), PostgreSQL throws:
```
error: operator does not exist: bigint = character varying
```
This commonly occurs with **polymorphic-style associations** where a single table references multiple parent tables with different ID formats. The foreign key is stored as `varchar` to accommodate IDs from different sources (bigint, UUID, external string IDs).
#### Example: Notifications referencing multiple entity types with mixed ID formats
A notification system that can reference users (bigint), organizations (UUID), and external Stripe customers (string):
```typescript
const users = pgTable('users', {
id: bigserial('id', { mode: 'bigint' }).primaryKey(),
name: text('name').notNull(),
});
const organizations = pgTable('organizations', {
id: uuid('id').defaultRandom().primaryKey(),
name: text('name').notNull(),
});
// Notifications can reference any entity type (mixed ID formats)
const notifications = pgTable('notifications', {
id: bigserial('id', { mode: 'bigint' }).primaryKey(),
message: text('message').notNull(),
targetId: varchar('target_id', { length: 255 }), // varchar to support bigint, UUID, or external IDs
targetType: varchar('target_type', { length: 255 }), // 'User' | 'Organization' | 'StripeCustomer'
});
// ❌ This fails - bigint ≠ varchar, uuid ≠ varchar
const relations = defineRelations({ users, organizations, notifications }, (r) => ({
users: {
notifications: r.many.notifications({
from: r.users.id, // bigint
to: r.notifications.targetId, // varchar ← TYPE MISMATCH
where: { targetType: 'User' },
}),
},
organizations: {
notifications: r.many.notifications({
from: r.organizations.id, // uuid
to: r.notifications.targetId, // varchar ← TYPE MISMATCH
where: { targetType: 'Organization' },
}),
},
}));
// Runtime error: operator does not exist: bigint = character varying
```
#### Proposed solution
Allow passing a builder function to `from`/`to` that receives the aliased table and returns custom SQL:
```typescript
const relations = defineRelations({ users, organizations, notifications }, (r) => ({
users: {
notifications: r.many.notifications({
from: (table) => sql`${table}.${sql.identifier('id')}::varchar`,
to: r.notifications.targetId,
where: { targetType: 'User' },
}),
},
organizations: {
notifications: r.many.notifications({
from: (table) => sql`${table}.${sql.identifier('id')}::varchar`,
to: r.notifications.targetId,
where: { targetType: 'Organization' },
}),
},
}));
// ✅ Works! Generates: WHERE s0."id"::varchar = t0."target_id"
```
#### Additional use cases
An alternative would be to extend the builder with a `.cast()` but using custom SQL allow for other use cases
```typescript
const relations = defineRelations({ users, organizations, notifications }, (r) => ({
users: {
notifications: r.many.notifications({
from: r.users.id.cast("varchar"),
to: r.notifications.targetId,
where: { targetType: 'User' },
}),
},
organizations: {
notifications: r.many.notifications({
from: r.organizations.cast("varchar"),
to: r.notifications.targetId,
where: { targetType: 'Organization' },
}),
},
}));
```
```typescript
// JSONB metadata containing references
from: (table) => sql`${table}.${sql.identifier('metadata')}->>'authorId'`
// Case-insensitive email matching
from: (table) => sql`LOWER(${table}.${sql.identifier('email')})`
```
I opened a [PR](https://github.com/drizzle-team/drizzle-orm/pull/5284), what do you think of this approach ?
Contributor guide
Assessment
This issue has not been assessed yet.