drizzle-team / drizzle-team/drizzle-orm

[FEATURE]: RQBv2 support custom SQL expressions in relation `from`/`to`

Open
#5,285 2 comments 4 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.