Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ public MySqlBulkInsertProvider(ILogger<MySqlBulkInsertProvider>? logger = null)
{
}

//language=sql
/// <inheritdoc />
protected override string CreateTableCopySql => "CREATE TEMPORARY TABLE {0} SELECT * FROM {1} WHERE 1 = 0;";

//language=sql
/// <inheritdoc />
protected override string AddTableCopyBulkInsertId => $"ALTER TABLE {{0}} ADD {BulkInsertId} INT AUTO_INCREMENT PRIMARY KEY;";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ internal class MySqlServerDialectBuilder : SqlDialectBuilder

protected override bool SupportsMoveRows => false;

public override string CreateTableCopySql(string tempNameName, TableMetadata tableInfo, IReadOnlyList<PropertyMetadata> columns)
{
return $"CREATE TEMPORARY TABLE {tempNameName} SELECT * FROM {tableInfo.QuotedTableName} WHERE 1 = 0;";
}

protected override void AppendConflictCondition<T>(StringBuilder sql, OnConflictOptions<T> onConflictTyped)
{
throw new NotSupportedException("Conflict conditions are not supported in MYSQL");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text;

using JetBrains.Annotations;

using Microsoft.EntityFrameworkCore;
Expand All @@ -17,19 +19,17 @@ public PostgreSqlBulkInsertProvider(ILogger<PostgreSqlBulkInsertProvider>? logge
{
}

//language=sql
/// <inheritdoc />
protected override string CreateTableCopySql => "CREATE TEMPORARY TABLE {0} AS TABLE {1} WITH NO DATA;";

//language=sql
/// <inheritdoc />
protected override string AddTableCopyBulkInsertId => $"ALTER TABLE {{0}} ADD COLUMN {BulkInsertId} SERIAL PRIMARY KEY;";

private static string GetBinaryImportCommand(TableMetadata tableInfo, string tableName)
private static string GetBinaryImportCommand(IReadOnlyList<PropertyMetadata> properties, string tableName)
{
var columns = tableInfo.GetProperties(false).Select(X => X.QuotedColumName);

return $"COPY {tableName} ({string.Join(", ", columns)}) FROM STDIN (FORMAT BINARY)";
var sql = new StringBuilder();
sql.Append($"COPY {tableName} (");
sql.AppendColumns(properties);
sql.Append(") FROM STDIN (FORMAT BINARY)");
return sql.ToString();
}

/// <inheritdoc />
Expand All @@ -45,7 +45,7 @@ protected override async Task BulkInsert<T>(
{
var connection = (NpgsqlConnection)context.Database.GetDbConnection();

var importCommand = GetBinaryImportCommand(tableInfo, tableName);
var importCommand = GetBinaryImportCommand(properties, tableName);

var writer = sync
// ReSharper disable once MethodHasAsyncOverloadWithCancellation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
using PhenX.EntityFrameworkCore.BulkInsert.Dialect;
using System.Text;

using PhenX.EntityFrameworkCore.BulkInsert.Dialect;
using PhenX.EntityFrameworkCore.BulkInsert.Metadata;
using PhenX.EntityFrameworkCore.BulkInsert.Options;

namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql;

internal class PostgreSqlDialectBuilder : SqlDialectBuilder
{
protected override string OpenDelimiter => "\"";
protected override string CloseDelimiter => "\"";

public override string CreateTableCopySql(string tempNameName, TableMetadata tableInfo, IReadOnlyList<PropertyMetadata> columns)
{
return $"CREATE TEMPORARY TABLE {tempNameName} AS TABLE {tableInfo.QuotedTableName} WITH NO DATA;";
}

protected override void AppendConflictMatch<T>(StringBuilder sql, TableMetadata target, OnConflictOptions<T> conflict)
{
if (conflict.Match != null)
{
base.AppendConflictMatch(sql, target, conflict);
}
else if (target.PrimaryKey.Count > 0)
{
sql.Append(' ');
sql.AppendLine("(");
sql.AppendColumns(target.PrimaryKey);
sql.AppendLine(")");
}
else
{
throw new InvalidOperationException("Table has no primary key that can be used for conflict detection.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ public SqlServerBulkInsertProvider(ILogger<SqlServerBulkInsertProvider>? logger
{
}

//language=sql
/// <inheritdoc />
protected override string CreateTableCopySql => "SELECT {2} INTO {0} FROM {1} WHERE 1 = 0;";

//language=sql
/// <inheritdoc />
protected override string AddTableCopyBulkInsertId => $"ALTER TABLE {{0}} ADD {BulkInsertId} INT IDENTITY PRIMARY KEY;";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,24 @@ internal class SqlServerDialectBuilder : SqlDialectBuilder

protected override bool SupportsMoveRows => false;

public override string CreateTableCopySql(string templNameName, TableMetadata tableInfo, IReadOnlyList<PropertyMetadata> columns)
{
var q = new StringBuilder();
q.Append("SELECT");
q.AppendColumns(columns);
q.Append($"INTO {templNameName} FROM {tableInfo.QuotedTableName} WHERE 1 = 0;");

return q.ToString();
}

public override string BuildMoveDataSql<T>(
TableMetadata target,
string source,
IReadOnlyList<PropertyMetadata> insertedProperties,
IReadOnlyList<PropertyMetadata> properties,
IReadOnlyList<PropertyMetadata> returnedProperties,
BulkInsertOptions options,
OnConflictOptions? onConflict = null)
{
var insertedColumns = insertedProperties.Select(x => x.QuotedColumName);
var insertedColumnList = string.Join(", ", insertedColumns);

var returnedColumns = properties.Select(p => $"INSERTED.{p.ColumnName} AS {p.ColumnName}");
var columnList = string.Join(", ", returnedColumns);

var q = new StringBuilder();

if (options.CopyGeneratedColumns)
Expand All @@ -36,49 +40,76 @@ public override string BuildMoveDataSql<T>(
}

// Merge handling
if (onConflict is OnConflictOptions<T> onConflictTyped && onConflictTyped.Match != null)
if (onConflict is OnConflictOptions<T> onConflictTyped)
{
var matchColumns = GetColumns(target, onConflictTyped.Match);
var matchOn = string.Join(" AND ",
matchColumns.Select(col => $"TARGET.{col} = SOURCE.{col}"));

var updateSet = onConflictTyped.Update != null
? string.Join(", ", GetUpdates(target, insertedProperties, onConflictTyped.Update))
: null;
IEnumerable<string> matchColumns;
if (onConflictTyped.Match != null)
{
matchColumns = GetColumns(target, onConflictTyped.Match);
}
else if (target.PrimaryKey.Count > 0)
{
matchColumns = target.PrimaryKey.Select(x => x.QuotedColumName);
}
else
{
throw new InvalidOperationException("Table has no primary key that can be used for conflict detection.");
}

q.AppendLine($"MERGE INTO {target.QuotedTableName} AS TARGET");
q.AppendLine(
$"USING (SELECT {string.Join(", ", insertedColumns)} FROM {source}) AS SOURCE ({insertedColumnList})");
q.AppendLine($"ON {matchOn}");

if (updateSet != null)
q.Append("USING (SELECT ");
q.AppendColumns(insertedProperties);
q.Append($" FROM {source}) AS SOURCE (");
q.AppendColumns(insertedProperties);
q.AppendLine(")");

q.Append("ON ");
q.AppendJoin($" AND ", matchColumns, (b, col) => b.Append($"TARGET.{col} = SOURCE.{col}"));
q.AppendLine();

if (onConflictTyped.Update != null)
{
q.AppendLine($"WHEN MATCHED THEN UPDATE SET {updateSet}");
q.AppendLine($"WHEN MATCHED THEN UPDATE SET ");
q.AppendJoin(", ", GetUpdates(target, insertedProperties, onConflictTyped.Update));
q.AppendLine();
}

q.AppendLine(
$"WHEN NOT MATCHED THEN INSERT ({insertedColumnList}) VALUES ({string.Join(", ", insertedColumns.Select(c => $"SOURCE.{c}"))})");
q.Append($"WHEN NOT MATCHED THEN INSERT (");
q.AppendColumns(insertedProperties);
q.AppendLine(")");

if (columnList.Length != 0)
q.Append("VALUES (");
q.AppendJoin(", ", insertedProperties, (b, col) => b.Append($"SOURCE.{col.QuotedColumName}"));
q.AppendLine(")");

if (returnedProperties.Count != 0)
{
q.AppendLine($"OUTPUT {columnList}");
q.Append("OUTPUT ");
q.AppendJoin($", ", returnedProperties, (b, col) => b.Append($"INSERTED.{col.QuotedColumName} AS {col.QuotedColumName}"));
q.AppendLine();
}
}

// No conflict handling
else
{
q.AppendLine($"INSERT INTO {target.QuotedTableName} ({insertedColumnList})");
q.Append($"INSERT INTO {target.QuotedTableName} (");
q.AppendColumns(insertedProperties);
q.AppendLine(")");

if (columnList.Length != 0)
if (returnedProperties.Count != 0)
{
q.AppendLine($"OUTPUT {columnList}");
q.Append("OUTPUT ");
q.AppendJoin($", ", returnedProperties, (b, col) => b.Append($"INSERTED.{col.QuotedColumName} AS {col.QuotedColumName}"));
q.AppendLine();
}

q.AppendLine($"""
SELECT {insertedColumnList}
FROM {source}
""");
q.Append("SELECT ");
q.AppendColumns(insertedProperties);
q.AppendLine();
q.Append($"FROM {source}");
q.AppendLine();
}

q.AppendLine(";");
Expand All @@ -88,7 +119,8 @@ public override string BuildMoveDataSql<T>(
q.AppendLine($"SET IDENTITY_INSERT {target.QuotedTableName} OFF;");
}

return q.ToString();
var x = q.ToString();
Comment thread
SebastianStehle marked this conversation as resolved.
Outdated
return x;
}

protected override string GetExcludedColumnName(string columnName)
Expand Down
Loading