Skip to content

Conversation

@waltoss
Copy link

@waltoss waltoss commented Jan 21, 2026

Summary

This PR adds support for custom SQL builder functions in relation from/to configuration. This enables type casting, JSON extraction, and other SQL transformations when joining columns of incompatible types.

Problem

When relations join columns of different types (e.g., bigint to varchar in polymorphic associations), PostgreSQL throws:

error: operator does not exist: bigint = character varying

This is common in systems with mixed ID formats (bigint, UUID, external string IDs) or legacy databases.

Solution

Allow passing a builder function to from/to that receives the aliased table and returns custom SQL:

const relations = defineRelations({ users, notifications }, (r) => ({
  users: {
    notifications: r.many.notifications({
      // Function receives the aliased table (e.g., s0, t0)
      from: (table) => sql`${table}.${sql.identifier('id')}::varchar`,
      to: r.notifications.targetId,
      where: { targetType: 'User' },
    }),
  },
  notifications: {
    user: r.one.users({
      from: r.notifications.targetId,
      to: (table) => sql`${table}.${sql.identifier('id')}::varchar`,
      where: { targetType: 'User' },
      optional: true,
    }),
  },
}));

Also works for other transformations:

// JSONB field extraction
from: (table) => sql`${table}.${sql.identifier('metadata')}->>'authorId'`

// Case-insensitive matching
from: (table) => sql`LOWER(${table}.${sql.identifier('email')})`

Changes

File Description
drizzle-orm/src/relations.ts Core implementation (+70/-36 lines)
integration-tests/tests/pg/common-rqb.ts Integration tests (+129 lines)

Implementation details

  • New types:

    • RelationColumnBuilder = (table: SQL) => SQL - Builder function type
    • RelationColumnValue = Column<any> \| RelationColumnBuilder - Union type for columns
    • RelationConfigValue = RelationsBuilderColumnBase \| RelationColumnBuilder - Config value type
  • Updated types:

    • Relation.sourceColumns / targetColumns: Column<any>[]RelationColumnValue[]
    • OneConfig.from / to: Now accepts RelationConfigValue
    • ManyConfig.from / to: Now accepts RelationConfigValue
  • New helpers:

    • isRelationsBuilderColumnBase(): Type guard to distinguish column refs from functions
    • buildColumnSQL(): Builds SQL for a column, calling the function if provided
  • Updated functions:

    • One / Many constructors: Detect and pass through functions
    • relationToSQL(): Uses buildColumnSQL() to handle both cases

Test plan

  • Added test: RQB v2 relation with custom SQL builder function (type casting)

    • Tests bigint → varchar casting for user/posts relation
    • Verifies both findFirst with nested relations and reverse lookups
  • Added test: RQB v2 relation with custom SQL builder for JSON extraction

    • Tests JSONB ->>'field' extraction for product/category relation
    • Verifies optional relations work correctly

To run tests:

cd integration-tests && pnpm test

@waltoss waltoss changed the base branch from main to beta January 21, 2026 15:52
@waltoss waltoss changed the title feat(RQB): add custom SQL builder functions for relation columns feat(RQBv2): add custom SQL builder functions for relation columns Jan 21, 2026
Allow passing a function to `from`/`to` in relation config that receives
the aliased table and returns custom SQL. This enables type casting,
JSON extraction, and other SQL transformations in relation joins.

Example usage:
```ts
r.one.addresses({
  from: (table) => sql`${table}.${sql.identifier('id')}::varchar`,
  to: r.addresses.addressableId,
})
```

Changes:
- Add RelationColumnBuilder type for builder functions
- Update OneConfig/ManyConfig to accept functions
- Update One/Many constructors to handle functions
- Add buildColumnSQL helper in relationToSQL
- Add integration tests for type casting and JSON extraction
@waltoss waltoss force-pushed the feat/relations-custom-sql-builder branch from d1ac398 to d6695b0 Compare January 21, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant