From a921cb884a00bd98247b586992905f9fbed2aefe Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 22 Aug 2026 05:44:24 -0600 Subject: [PATCH] Support upserts through Quick entity queries Closes #63 --- models/QuickQB.cfc | 19 +++++++++++--- .../integration/BaseEntity/QuerySpec.cfc | 25 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/models/QuickQB.cfc b/models/QuickQB.cfc index b1a15826..fc132a68 100644 --- a/models/QuickQB.cfc +++ b/models/QuickQB.cfc @@ -331,14 +331,27 @@ component return super.update( argumentCollection = arguments ); } + /** + * Inserts rows that do not exist and updates rows matching the target columns. + * + * @values The values to insert or the columns selected by the source query. + * @target The columns used to determine whether a row already exists. + * @update The columns or explicit values to update when a row matches. + * @source An optional query builder or callback used as the source rows. + * @deleteUnmatched Whether to delete target rows missing from the source, or a callback constraining those deletes. + * @options Options passed to `queryExecute`. + * @toSql Whether to return SQL instead of executing the query. + * @matchNulls Whether two NULL target values should be considered a match. Supported by MERGE grammars. + */ public any function upsert( required any values, required any target, any update, any source, - boolean deleteUnmatched = false, - struct options = {}, - boolean toSql = false + any deleteUnmatched = false, + struct options = {}, + boolean toSql = false, + boolean matchNulls = false ) { if ( !isNull( arguments.source ) && isStruct( arguments.source ) && structKeyExists( diff --git a/tests/specs/integration/BaseEntity/QuerySpec.cfc b/tests/specs/integration/BaseEntity/QuerySpec.cfc index 0592f37d..508a129c 100644 --- a/tests/specs/integration/BaseEntity/QuerySpec.cfc +++ b/tests/specs/integration/BaseEntity/QuerySpec.cfc @@ -41,6 +41,31 @@ component extends="tests.resources.ModuleIntegrationSpec" { expect( users[ 2 ].getId() ).toBe( 4 ); expect( users[ 2 ].getUsername() ).toBe( "elpete2" ); } ); + + it( "can upsert records through the entity query API", function() { + getInstance( "User" ).upsert( + values = [ + { + "id" : 1, + "username" : "elpete", + "firstName" : "Updated", + "lastName" : "Peterson" + }, + { + "id" : 99, + "username" : "new-user", + "firstName" : "New", + "lastName" : "User" + } + ], + target = "id", + update = [ "firstName" ], + matchNulls = false + ); + + expect( getInstance( "User" ).findOrFail( 1 ).getFirstName() ).toBe( "Updated" ); + expect( getInstance( "User" ).findOrFail( 99 ).getFirstName() ).toBe( "New" ); + } ); } ); }