@ -16,17 +16,14 @@
@@ -16,17 +16,14 @@
package org.springframework.data.relational.core.sql.render ;
import java.util.List ;
import java.util.Set ;
import java.util.function.Function ;
import org.springframework.data.relational.core.sql.IdentifierProcessing ;
import org.springframework.data.relational.core.sql.SqlIdentifier ;
import org.springframework.data.relational.core.sql.Table ;
import org.springframework.util.Assert ;
/ * *
* Oracle MERGE upsert . Uses { @code SELECT . . . FROM DUAL } as the source , which Oracle requires for
* literal / bind - marker - only selects .
* Oracle MERGE upsert . Uses { @code SELECT . . . FROM DUAL } for source values .
*
* @since 4 . x
* /
@ -34,53 +31,56 @@ public enum OracleUpsertRenderContext implements UpsertRenderContext {
@@ -34,53 +31,56 @@ public enum OracleUpsertRenderContext implements UpsertRenderContext {
INSTANCE ;
private static final String SOURCE_FROM_CLAUSE = " FROM DUAL" ;
@Override
public String renderUpsert ( Table table , Columns merge , Function < SqlIdentifier , String > bindMarkerFn ) {
public String renderUpsert ( Table table , Columns columns , Function < SqlIdentifier , String > bindMarkerFn ) {
List < SqlIdentifier > insertColumns = merge . insertColumns ( ) ;
List < SqlIdentifier > conflictColumns = merge . filterColumns ( ) ;
IdentifierProcessing identifierProcessing = merge . identifierProcessing ( ) ;
Assert . notEmpty ( columns . insertColumns ( ) , "Insert columns must not be empty" ) ;
Assert . notEmpty ( columns . filterColumns ( ) , "Filter columns must not be empty" ) ;
Assert . notEmpty ( insertColumns , "Insert columns must not be empty" ) ;
Assert . notEmpty ( conflictColumns , "Conflict columns must not be empty" ) ;
String targetTableAlias = columns . identifierProcessing ( ) . quote ( StandardSqlUpsertRenderContext . targetTableAlias ) ;
String sourceTableAlias = columns . identifierProcessing ( ) . quote ( StandardSqlUpsertRenderContext . sourceTableAlias ) ;
Set < SqlIdentifier > conflictSet = Set . copyOf ( conflictColumns ) ;
String tableSql = table . getName ( ) . toSql ( identifierProcessing ) ;
String tableName = columns . tableName ( table ) ;
String insertColumnNames = String . join ( ", " , columns . insertColumnNames ( ) ) ;
String sourceSelectList = String . join ( ", " ,
columns . insertColumns ( ) . stream ( ) . map ( col - > bindMarkerFn . apply ( col ) + " AS " + columns . column ( col ) ) . toList ( ) ) ;
String sourceSelectList = String . join ( ", " , insertColumns . stream ( )
. map ( col - > bindMarkerFn . apply ( col ) + " AS " + col . toSql ( identifierProcessing ) )
. toList ( ) ) ;
String onCondition = String . join ( " AND " , columns . filterColumns ( ) . stream ( ) . map ( col - > {
String colName = columns . column ( col ) ;
return "%s.%s = %s.%s" . formatted ( targetTableAlias , colName , sourceTableAlias , colName ) ;
} ) . toList ( ) ) ;
String onCondition = "(" + String . join ( " AND " , conflictColumns . stream ( )
. map ( col - > "t." + col . toSql ( identifierProcessing ) + " = s." + col . toSql ( identifierProcessing ) )
. toList ( ) ) + ")" ;
String insertValuesSql = String . join ( ", " ,
columns . insertColumns ( ) . stream ( ) . map ( col - > columns . column ( sourceTableAlias , col ) ) . toList ( ) ) ;
List < SqlIdentifier > updateColumns = insertColumns . stream ( )
. filter ( col - > ! conflictSet . contains ( col ) )
. toList ( ) ;
String insertClause = "WHEN NOT MATCHED THEN INSERT (%s) VALUES (%s)" . formatted ( insertColumnNames ,
insertValuesSql ) ;
Str ing updateSet Cla use ;
List < SqlIden tifier > updateCo lumns = col umn s . updat eColumns ( ) ;
if ( updateColumns . isEmpty ( ) ) {
SqlIdentifier firstConflict = conflictColumns . get ( 0 ) ;
updateSetClause = "t." + firstConflict . toSql ( identifierProcessing ) + " = s."
+ firstConflict . toSql ( identifierProcessing ) ;
} else {
updateSetClause = String . join ( ", " , updateColumns . stream ( )
. map ( col - > "t." + col . toSql ( identifierProcessing ) + " = s." + col . toSql ( identifierProcessing ) )
. toList ( ) ) ;
// ORA-38104: columns referenced in ON cannot be updated; omit WHEN MATCHED so existing rows are left
// unchanged (same as a no-op update of key-only columns).
return "MERGE INTO %s %s USING (SELECT %s FROM DUAL) %s ON (%s) %s" . formatted ( //
tableName , //
targetTableAlias , //
sourceSelectList , //
sourceTableAlias , //
onCondition , //
insertClause ) ;
}
String insertColumnsSql = String . join ( ", " ,
insertColumns . stream ( ) . map ( col - > col . toSql ( identifierProcessing ) ) . toList ( ) ) ;
String insertValuesSql = String . join ( ", " ,
insertColumns . stream ( ) . map ( col - > "s." + col . toSql ( identifierProcessing ) ) . toList ( ) ) ;
return "MERGE INTO " + tableSql + " t USING (SELECT " + sourceSelectList + SOURCE_FROM_CLAUSE + ") s ON "
+ onCondition
+ " WHEN MATCHED THEN UPDATE SET " + updateSetClause
+ " WHEN NOT MATCHED THEN INSERT (" + insertColumnsSql + ") VALUES (" + insertValuesSql + ")" ;
String updateSetClause = String . join ( ", " , updateColumns . stream ( ) . map ( col - > {
String colName = columns . column ( col ) ;
return "%s.%s = %s.%s" . formatted ( targetTableAlias , colName , sourceTableAlias , colName ) ;
} ) . toList ( ) ) ;
return "MERGE INTO %s %s USING (SELECT %s FROM DUAL) %s ON (%s) WHEN MATCHED THEN UPDATE SET %s %s" . formatted ( //
tableName , //
targetTableAlias , //
sourceSelectList , //
sourceTableAlias , //
onCondition , //
updateSetClause , //
insertClause ) ;
}
}