From 231c24c7222a1e3a3fa673780c90f2be62dc2067 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:17:49 +0000 Subject: [PATCH 1/4] Initial plan From 60a2311b696357bedf7fa2060abd45e22b13010b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:23:58 +0000 Subject: [PATCH 2/4] Fix Oracle MERGE syntax by removing AS keyword and OUTPUT clause Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com> --- .../OracleDialectBuilder.cs | 29 +++++++++++-------- .../Tests/Merge/MergeTestsOracle.cs | 12 ++++++++ 2 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Merge/MergeTestsOracle.cs diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs index 60d4b3b..3110b84 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs @@ -49,13 +49,12 @@ public override string BuildMoveDataSql( throw new InvalidOperationException("Table has no primary key that can be used for conflict detection."); } - q.AppendLine($"MERGE INTO {target.QuotedTableName} AS {PseudoTableInserted}"); + q.AppendLine($"MERGE INTO {target.QuotedTableName} {PseudoTableInserted}"); q.Append("USING (SELECT "); q.AppendColumns(insertedColumns); - q.Append($" FROM {source}) AS {PseudoTableExcluded} ("); - q.AppendColumns(insertedColumns); - q.AppendLine(")"); + q.Append($" FROM {source}) {PseudoTableExcluded}"); + q.AppendLine(); q.Append("ON "); q.AppendJoin(" AND ", matchColumns, (b, col) => b.Append($"{PseudoTableInserted}.{col} = {PseudoTableExcluded}.{col}")); @@ -65,7 +64,20 @@ public override string BuildMoveDataSql( { var columns = target.GetColumns(false); - q.AppendLine("WHEN MATCHED THEN UPDATE SET "); + q.Append("WHEN MATCHED"); + + if (onConflictTyped.RawWhere != null || onConflictTyped.Where != null) + { + if (onConflictTyped is { RawWhere: not null, Where: not null }) + { + throw new ArgumentException("Cannot specify both RawWhere and Where in OnConflictOptions."); + } + + q.Append(" AND "); + AppendConflictCondition(q, target, context, onConflictTyped); + } + + q.AppendLine(" THEN UPDATE SET "); q.AppendJoin(", ", GetUpdates(context, target, columns, onConflictTyped.Update)); q.AppendLine(); } @@ -77,13 +89,6 @@ public override string BuildMoveDataSql( q.Append("VALUES ("); q.AppendJoin(", ", insertedColumns, (b, col) => b.Append($"{PseudoTableExcluded}.{col.QuotedColumName}")); q.AppendLine(")"); - - if (returnedColumns.Count != 0) - { - q.Append("OUTPUT "); - q.AppendJoin(", ", returnedColumns, (b, col) => b.Append($"{PseudoTableInserted}.{col.QuotedColumName} AS {col.QuotedColumName}")); - q.AppendLine(); - } } // No conflict handling diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Merge/MergeTestsOracle.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Merge/MergeTestsOracle.cs new file mode 100644 index 0000000..86dc10d --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Merge/MergeTestsOracle.cs @@ -0,0 +1,12 @@ +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +using Xunit; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Merge; + +[Trait("Category", "Oracle")] +[Collection(TestDbContainerOracleCollection.Name)] +public class MergeTestsOracle(TestDbContainerOracle dbContainer) : MergeTestsBase(dbContainer) +{ +} From 80f2ec87753090670b1b015856afddf0403cb444 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:25:25 +0000 Subject: [PATCH 3/4] Add parentheses to Oracle MERGE ON clause Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com> --- .../OracleDialectBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs index 3110b84..b9d8abe 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs @@ -56,9 +56,9 @@ public override string BuildMoveDataSql( q.Append($" FROM {source}) {PseudoTableExcluded}"); q.AppendLine(); - q.Append("ON "); + q.Append("ON ("); q.AppendJoin(" AND ", matchColumns, (b, col) => b.Append($"{PseudoTableInserted}.{col} = {PseudoTableExcluded}.{col}")); - q.AppendLine(); + q.AppendLine(")"); if (onConflictTyped.Update != null) { From 559c5fa027ffd59484efec4253ba26874aa149d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:52:28 +0000 Subject: [PATCH 4/4] Fix Oracle MERGE syntax: reorder WHEN clauses and fix spacing Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com> --- .../OracleDialectBuilder.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs index b9d8abe..40de402 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs @@ -60,11 +60,20 @@ public override string BuildMoveDataSql( q.AppendJoin(" AND ", matchColumns, (b, col) => b.Append($"{PseudoTableInserted}.{col} = {PseudoTableExcluded}.{col}")); q.AppendLine(")"); + // WHEN NOT MATCHED clause should come before WHEN MATCHED in Oracle + q.Append("WHEN NOT MATCHED THEN INSERT ("); + q.AppendColumns(insertedColumns); + q.AppendLine(")"); + + q.Append("VALUES ("); + q.AppendJoin(", ", insertedColumns, (b, col) => b.Append($"{PseudoTableExcluded}.{col.QuotedColumName}")); + q.Append(")"); + if (onConflictTyped.Update != null) { var columns = target.GetColumns(false); - q.Append("WHEN MATCHED"); + q.Append(" WHEN MATCHED"); if (onConflictTyped.RawWhere != null || onConflictTyped.Where != null) { @@ -77,18 +86,11 @@ public override string BuildMoveDataSql( AppendConflictCondition(q, target, context, onConflictTyped); } - q.AppendLine(" THEN UPDATE SET "); + q.Append(" THEN UPDATE SET "); q.AppendJoin(", ", GetUpdates(context, target, columns, onConflictTyped.Update)); - q.AppendLine(); } - q.Append("WHEN NOT MATCHED THEN INSERT ("); - q.AppendColumns(insertedColumns); - q.AppendLine(")"); - - q.Append("VALUES ("); - q.AppendJoin(", ", insertedColumns, (b, col) => b.Append($"{PseudoTableExcluded}.{col.QuotedColumName}")); - q.AppendLine(")"); + q.AppendLine(); } // No conflict handling