database icon indicating copy to clipboard operation
database copied to clipboard

Upsert feature

Open puzzledpolymath opened this issue 6 months ago • 1 comments

This pull request adds support for performing upsert queries. Ticket #50 is what sparked interest for this feature.

🔍 What was changed

  • Each driver (MySQL, Postgres, SQLite and SQLServer) compiler supports an UpsertQuery.
  • Differences between SQL dialects have been accounted for.

🤔 Why?

  1. Upsert allows developers to combine insert and update operations into a single command, reducing the need for explicit existence checks and multiple database calls.
  2. Modern databases support atomic upsert operations, which help maintain data integrity even in concurrent environments.
  3. Upsert reduces the number of database round-trips, eliminating separate existence checks followed by insert or update, likely leading to better performance, especially in high-volume scenarios.

📝 Checklist

  • Closes #50
  • How was this tested:
    • [x] Unit tests added

📃 Documentation

Note that specifying conflict index column names is redundant and not required for MySQL.

PHP

$upsert = $this->database->upsert('users')
    ->conflicts('email')
    ->columns('email', 'name')
    ->values('[email protected]', 'Adam')
    ->values('[email protected]', 'Bill');
$upsert = $this->database->upsert('users')
    ->conflicts(['email'])
    ->values([
        ['email' => '[email protected]', 'name' => 'Adam'],
        ['email' => '[email protected]', 'name' => 'Bill'],
    ]);

MySQL

INSERT INTO `users` (`email`, `name`)
VALUES
    ('[email protected]', 'Adam'),
    ('[email protected]', 'Bill')
ON DUPLICATE KEY UPDATE
    `email` = VALUES(`email`),
    `name` = VALUES(`name`);

Postgres / SQLite

INSERT INTO `users` (`email`, `name`)
VALUES
    ('[email protected]', 'Adam'),
    ('[email protected]', 'Bill')
ON CONFLICT (`email`) DO UPDATE SET
    `email` = EXCLUDED.`email`,
    `name` = EXCLUDED.`name`

MSSQL

MERGE INTO [users] WITH (holdlock) AS [target] 
USING ( VALUES 
    ('[email protected]', 'Adam'),
    ('[email protected]', 'Bill') 
) AS [source] ([email], [name]) 
ON [target].[email] = [source].[email] 
WHEN MATCHED THEN 
    UPDATE SET 
        [target].[email] = [source].[email], 
        [target].[name] = [source].[name] 
WHEN NOT MATCHED THEN 
    INSERT ([email], [name]) 
    VALUES ([source].[email], [source].[name])

puzzledpolymath avatar Jul 11 '25 02:07 puzzledpolymath

Codecov Report

:white_check_mark: All modified and coverable lines are covered by tests. :white_check_mark: Project coverage is 95.61%. Comparing base (3d7ee35) to head (c53a6f0).

Additional details and impacted files
@@             Coverage Diff              @@
##                2.x     #231      +/-   ##
============================================
+ Coverage     95.42%   95.61%   +0.19%     
- Complexity     1896     1957      +61     
============================================
  Files           131      134       +3     
  Lines          5285     5523     +238     
============================================
+ Hits           5043     5281     +238     
  Misses          242      242              

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov[bot] avatar Jul 11 '25 09:07 codecov[bot]