Skip to content

feat(upsert): add onConflict option for custom conflict columns#5494

Closed
jbingen wants to merge 2 commits intorocicorp:mainfrom
jbingen:feat/upsert-on-conflict
Closed

feat(upsert): add onConflict option for custom conflict columns#5494
jbingen wants to merge 2 commits intorocicorp:mainfrom
jbingen:feat/upsert-on-conflict

Conversation

@jbingen
Copy link
Contributor

@jbingen jbingen commented Jan 30, 2026

Summary

Allows specifying custom columns for ON CONFLICT in upsert operations, instead of defaulting to the primary key. This is useful when upserting based on unique constraints other than the primary key.

Motivation

When you have a table with a unique constraint on columns other than the primary key, the current upsert always uses ON CONFLICT (primary_key). This PR adds an onConflict option to specify which columns should be used for conflict detection.

Example

// Upsert based on unique constraint (workspace_id, email) instead of primary key (id)
await tx.mutate.workspace_invitations.upsert(
  { id: '123', workspace_id: 'ws1', email: 'user@example.com', role: 'member' },
  { onConflict: ['workspace_id', 'email'] }
)

This generates SQL like:

INSERT INTO workspace_invitations (id, workspace_id, email, role)
VALUES ('123', 'ws1', 'user@example.com', 'member')
ON CONFLICT (workspace_id, email) DO UPDATE SET
  id = '123',
  workspace_id = 'ws1',
  email = 'user@example.com',
  role = 'member'

Changes

  • zero-protocol/push.ts: Add optional conflictColumns field to UpsertOp schema
  • zql/mutate/crud.ts: Add UpsertOptions type with onConflict field, update TableCRUD and TableMutator types
  • zero-server/custom.ts: Use custom conflict columns in SQL generation when provided
  • zero-client/crud.ts: Pass options through the CRUD executor

Allows specifying custom columns for ON CONFLICT in upsert operations,
instead of defaulting to primary key. This is useful when upserting
based on unique constraints other than the primary key.

Usage:
```typescript
await tx.mutate.table.upsert(
  { id: '123', workspace_id: 'ws1', email: 'user@example.com', role: 'member' },
  { onConflict: ['workspace_id', 'email'] }
)
```

This generates SQL like:
```sql
INSERT INTO table (...) VALUES (...)
ON CONFLICT (workspace_id, email) DO UPDATE SET ...
```

Changes:
- Add conflictColumns to UpsertOp in protocol schema
- Add UpsertOptions type with onConflict field
- Update server SQL generation to use custom conflict columns
- Pass options through client CRUD executor
@vercel
Copy link

vercel bot commented Jan 30, 2026

@jbingen is attempting to deploy a commit to the Rocicorp Team on Vercel.

A member of the Team first needs to authorize it.

@jbingen jbingen closed this Feb 19, 2026
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