Skip to content

[Pg] Add MERGE statement support - #5749

Open
klotztech wants to merge 2 commits into
drizzle-team:mainfrom
klotztech:main
Open

[Pg] Add MERGE statement support#5749
klotztech wants to merge 2 commits into
drizzle-team:mainfrom
klotztech:main

Conversation

@klotztech

Copy link
Copy Markdown
Contributor

Adds MERGE statement support for PostgreSQL 15+ via a new db.merge() query builder, following the same fluent API style as UPDATE .. FROM and INSERT INTO .. SELECT.

Resolves #1298

API

// Upsert: update on match, insert otherwise
await db.merge(users)
  .using(newUsers, eq(users.id, newUsers.id))
  .whenMatched()
    .update({ name: newUsers.name, email: newUsers.email })
  .whenNotMatched()
    .insert({ id: newUsers.id, name: newUsers.name, email: newUsers.email });
// Multiple conditional clauses
await db.merge(users)
  .using(updates, eq(users.id, updates.id))
  .whenMatched(eq(updates.status, 'deleted'))
    .delete()
  .whenMatched()
    .update({ name: updates.name })
  .whenNotMatched()
    .doNothing();
// RETURNING (requires PostgreSQL 17+)
const result = await db.merge(users)
  .using(newUsers, eq(users.id, newUsers.id))
  .whenMatched()
    .update({ name: newUsers.name })
  .returning();
// Subquery as source
const filtered = qb.select({ id: src.id, name: src.name })
  .from(src)
  .where(isNotNull(src.name))
  .as('filtered_source');

await db.merge(users)
  .using(filtered, eq(users.id, filtered.id))
  .whenMatched()
    .update({ name: filtered.name })
  .whenNotMatched()
    .insert({ id: filtered.id, name: filtered.name });
// Works with .with() CTEs
await db.with(cte).merge(users)
  .using(cte, eq(users.id, cte.id))
  .whenMatched()
    .update({ name: cte.name });

What was changed

  • drizzle-orm/src/pg-core/query-builders/merge.ts — New file. Implements PgMergeBuilder (returned by db.merge()), PgMergeBase (the main builder after .using()), PgMergeMatchedActionBuilder (.whenMatched() actions), and PgMergeNotMatchedActionBuilder (.whenNotMatched() actions). Supports .returning() for PostgreSQL 17+, .toSQL(), .prepare(), and CTE integration via db.with().

  • drizzle-orm/src/pg-core/dialect.ts — Added buildMergeQuery() to PgDialect. Generates the full MERGE INTO … USING … ON … WHEN … THEN … SQL, including conditional predicates on each clause and an optional RETURNING clause.

  • drizzle-orm/src/pg-core/query-builders/index.ts — Re-exports all public types and classes from merge.ts.

  • drizzle-orm/src/pg-core/db.ts — Added db.merge(table) to PgDatabase and to the db.with(…) builder chain.

  • drizzle-orm/tests/merge.test.ts — 16 unit tests covering all clause types (.update(), .delete(), .doNothing(), .insert()), conditional predicates, literal params, column references, SQL expressions, RETURNING, and subquery sources.

  • drizzle-orm/type-tests/pg/merge.ts — Compile-time type tests verifying the return types of all builder combinations.

…am#1298)

Implements db.merge(table).using(source, on)
  .whenMatched([condition]).thenUpdate(set) | .thenDelete() | .thenDoNothing()
  .whenNotMatched([condition]).thenInsert(values) | .thenDoNothing()
  .returning()  -- requires PostgreSQL 17+

Adds PgMergeBuilder, PgMergeBase, and action builders in pg-core/query-builders/merge.ts,
buildMergeQuery in PgDialect, and db.merge() on PgDatabase.
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.

[FEATURE]: Add support for MERGE

1 participant