Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delimit savepoint names as identifiers #23036

Merged
1 commit merged into from
Oct 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions All.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ Licensed under the Apache License, Version 2.0. See License.txt in the project r
<s:Boolean x:Key="/Default/UserDictionary/Words/=remapper/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=requiredness/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=retriable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=savepoints/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=shaper/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=spatialite/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sproc/@EntryIndexedValue">True</s:Boolean>
Expand Down
21 changes: 21 additions & 0 deletions src/EFCore.Relational/Storage/ISqlGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,26 @@ public interface ISqlGenerationHelper
/// <param name="text"> The comment text. </param>
/// <returns> The generated SQL. </returns>
string GenerateComment([NotNull] string text);

/// <summary>
/// Generates an SQL statement which creates a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be created. </param>
/// <returns> An SQL string to create the savepoint. </returns>
string GenerateCreateSavepointStatement([NotNull] string name);

/// <summary>
/// Generates an SQL statement which which rolls back to a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be rolled back to. </param>
/// <returns> An SQL string to roll back the savepoint. </returns>
string GenerateRollbackToSavepointStatement([NotNull] string name);

/// <summary>
/// Generates an SQL statement which which releases a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be released. </param>
/// <returns> An SQL string to release the savepoint. </returns>
string GenerateReleaseSavepointStatement([NotNull] string name);
}
}
24 changes: 24 additions & 0 deletions src/EFCore.Relational/Storage/RelationalSqlGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,29 @@ public virtual string GenerateComment(string text)

return builder.ToString();
}

/// <summary>
/// Generates an SQL statement which creates a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be created. </param>
/// <returns> An SQL string to create the savepoint. </returns>
public virtual string GenerateCreateSavepointStatement(string name)
=> "SAVEPOINT " + DelimitIdentifier(name) + StatementTerminator;

/// <summary>
/// Generates an SQL statement which which rolls back to a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be rolled back to. </param>
/// <returns> An SQL string to roll back the savepoint. </returns>
public virtual string GenerateRollbackToSavepointStatement(string name)
=> "ROLLBACK TO " + DelimitIdentifier(name) + StatementTerminator;

/// <summary>
/// Generates an SQL statement which which releases a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be released. </param>
/// <returns> An SQL string to release the savepoint. </returns>
public virtual string GenerateReleaseSavepointStatement(string name)
=> "RELEASE SAVEPOINT " + DelimitIdentifier(name) + StatementTerminator;
}
}
52 changes: 12 additions & 40 deletions src/EFCore.Relational/Storage/RelationalTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class RelationalTransaction : IDbContextTransaction, IInfrastructure<DbTr
{
private readonly DbTransaction _dbTransaction;
private readonly bool _transactionOwned;
private readonly ISqlGenerationHelper _sqlGenerationHelper;

private bool _connectionClosed;
private bool _disposed;
Expand All @@ -40,16 +41,19 @@ public class RelationalTransaction : IDbContextTransaction, IInfrastructure<DbTr
/// <param name="transactionOwned">
/// A value indicating whether the transaction is owned by this class (i.e. if it can be disposed when this class is disposed).
/// </param>
/// <param name="sqlGenerationHelper"> The SQL generation helper to use. </param>
public RelationalTransaction(
[NotNull] IRelationalConnection connection,
[NotNull] DbTransaction transaction,
Guid transactionId,
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Database.Transaction> logger,
bool transactionOwned)
bool transactionOwned,
[NotNull] ISqlGenerationHelper sqlGenerationHelper)
{
Check.NotNull(connection, nameof(connection));
Check.NotNull(transaction, nameof(transaction));
Check.NotNull(logger, nameof(logger));
Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper));

if (connection.DbConnection != transaction.Connection)
{
Expand All @@ -62,6 +66,7 @@ public RelationalTransaction(
_dbTransaction = transaction;
Logger = logger;
_transactionOwned = transactionOwned;
_sqlGenerationHelper = sqlGenerationHelper;
}

/// <summary>
Expand Down Expand Up @@ -279,7 +284,7 @@ public virtual void CreateSavepoint(string name)
{
using var command = Connection.DbConnection.CreateCommand();
command.Transaction = _dbTransaction;
command.CommandText = GetCreateSavepointSql(name);
command.CommandText = _sqlGenerationHelper.GenerateCreateSavepointStatement(name);
command.ExecuteNonQuery();
}

Expand Down Expand Up @@ -323,7 +328,7 @@ public virtual async Task CreateSavepointAsync(string name, CancellationToken ca
{
using var command = Connection.DbConnection.CreateCommand();
command.Transaction = _dbTransaction;
command.CommandText = GetCreateSavepointSql(name);
command.CommandText = _sqlGenerationHelper.GenerateCreateSavepointStatement(name);
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}

Expand All @@ -350,15 +355,6 @@ await Logger.TransactionErrorAsync(
}
}

/// <summary>
/// When implemented in a provider supporting transaction savepoints, this method should return an
/// SQL statement which creates a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be created. </param>
/// <returns> An SQL string to create the savepoint. </returns>
protected virtual string GetCreateSavepointSql([NotNull] string name)
=> "SAVEPOINT " + name;

/// <inheritdoc />
public virtual void RollbackToSavepoint(string name)
{
Expand All @@ -377,7 +373,7 @@ public virtual void RollbackToSavepoint(string name)
{
using var command = Connection.DbConnection.CreateCommand();
command.Transaction = _dbTransaction;
command.CommandText = GetRollbackToSavepointSql(name);
command.CommandText = _sqlGenerationHelper.GenerateRollbackToSavepointStatement(name);
command.ExecuteNonQuery();
}

Expand Down Expand Up @@ -421,7 +417,7 @@ public virtual async Task RollbackToSavepointAsync(string name, CancellationToke
{
using var command = Connection.DbConnection.CreateCommand();
command.Transaction = _dbTransaction;
command.CommandText = GetRollbackToSavepointSql(name);
command.CommandText = _sqlGenerationHelper.GenerateRollbackToSavepointStatement(name);
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}

Expand All @@ -448,15 +444,6 @@ await Logger.TransactionErrorAsync(
}
}

/// <summary>
/// When implemented in a provider supporting transaction savepoints, this method should return an
/// SQL statement which rolls back a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be created. </param>
/// <returns> An SQL string to create the savepoint. </returns>
protected virtual string GetRollbackToSavepointSql([NotNull] string name)
=> "ROLLBACK TO " + name;

/// <inheritdoc />
public virtual void ReleaseSavepoint(string name)
{
Expand All @@ -475,7 +462,7 @@ public virtual void ReleaseSavepoint(string name)
{
using var command = Connection.DbConnection.CreateCommand();
command.Transaction = _dbTransaction;
command.CommandText = GetReleaseSavepointSql(name);
command.CommandText = _sqlGenerationHelper.GenerateReleaseSavepointStatement(name);
command.ExecuteNonQuery();
}

Expand Down Expand Up @@ -519,7 +506,7 @@ public virtual async Task ReleaseSavepointAsync(string name, CancellationToken c
{
using var command = Connection.DbConnection.CreateCommand();
command.Transaction = _dbTransaction;
command.CommandText = GetReleaseSavepointSql(name);
command.CommandText = _sqlGenerationHelper.GenerateReleaseSavepointStatement(name);
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}

Expand All @@ -546,21 +533,6 @@ await Logger.TransactionErrorAsync(
}
}

/// <summary>
/// <para>
/// When implemented in a provider supporting transaction savepoints, this method should return an
/// SQL statement which releases a savepoint with the given name.
/// </para>
/// <para>
/// If savepoint release isn't supported, <see cref="ReleaseSavepoint " /> and <see cref="ReleaseSavepointAsync " /> should
/// be overridden to do nothing.
/// </para>
/// </summary>
/// <param name="name"> The name of the savepoint to be created. </param>
/// <returns> An SQL string to create the savepoint. </returns>
protected virtual string GetReleaseSavepointSql([NotNull] string name)
=> "RELEASE SAVEPOINT " + name;

/// <inheritdoc />
public virtual bool SupportsSavepoints
=> true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public virtual RelationalTransaction Create(
Guid transactionId,
IDiagnosticsLogger<DbLoggerCategory.Database.Transaction> logger,
bool transactionOwned)
=> new RelationalTransaction(connection, transaction, transactionId, logger, transactionOwned);
=> new RelationalTransaction(
connection, transaction, transactionId, logger, transactionOwned, Dependencies.SqlGenerationHelper);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.EntityFrameworkCore.Storage
Expand Down Expand Up @@ -51,8 +53,24 @@ public sealed class RelationalTransactionFactoryDependencies
/// </para>
/// </summary>
[EntityFrameworkInternal]
public RelationalTransactionFactoryDependencies()
public RelationalTransactionFactoryDependencies([NotNull] ISqlGenerationHelper sqlGenerationHelper)
{
Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper));

SqlGenerationHelper = sqlGenerationHelper;
}

/// <summary>
/// Helpers for SQL generation.
/// </summary>
public ISqlGenerationHelper SqlGenerationHelper { get; }

/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="sqlGenerationHelper"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public RelationalTransactionFactoryDependencies With([NotNull] ISqlGenerationHelper sqlGenerationHelper)
=> new RelationalTransactionFactoryDependencies(sqlGenerationHelper);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,29 @@ public override void DelimitIdentifier(StringBuilder builder, string identifier)
EscapeIdentifier(builder, identifier);
builder.Append(']');
}

/// <summary>
/// Generates an SQL statement which creates a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be created. </param>
/// <returns> An SQL string to create the savepoint. </returns>
public override string GenerateCreateSavepointStatement(string name)
=> "SAVE TRANSACTION " + DelimitIdentifier(name) + StatementTerminator;

/// <summary>
/// Generates an SQL statement which which rolls back to a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be rolled back to. </param>
/// <returns> An SQL string to roll back the savepoint. </returns>
public override string GenerateRollbackToSavepointStatement(string name)
=> "ROLLBACK TRANSACTION " + DelimitIdentifier(name) + StatementTerminator;

/// <summary>
/// Generates an SQL statement which which releases a savepoint with the given name.
/// </summary>
/// <param name="name"> The name of the savepoint to be released. </param>
/// <returns> An SQL string to release the savepoint. </returns>
public override string GenerateReleaseSavepointStatement(string name)
=> throw new NotSupportedException("SQL Server does not support releasing a savepoint");
}
}
13 changes: 4 additions & 9 deletions src/EFCore.SqlServer/Storage/Internal/SqlServerTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,13 @@ public SqlServerTransaction(
[NotNull] DbTransaction transaction,
Guid transactionId,
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Database.Transaction> logger,
bool transactionOwned)
: base(connection, transaction, transactionId, logger, transactionOwned)
bool transactionOwned,
[NotNull] ISqlGenerationHelper sqlGenerationHelper)
: base(connection, transaction, transactionId, logger, transactionOwned, sqlGenerationHelper)
{
}

/// <inheritdoc />
protected override string GetCreateSavepointSql(string name)
=> "SAVE TRANSACTION " + name;

/// <inheritdoc />
protected override string GetRollbackToSavepointSql(string name)
=> "ROLLBACK TRANSACTION " + name;
// SQL Server doesn't support releasing savepoints. Override to do nothing.
bricelam marked this conversation as resolved.
Show resolved Hide resolved

/// <inheritdoc />
public override void ReleaseSavepoint(string name) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

using System;
using System.Data.Common;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal
{
Expand All @@ -16,6 +18,22 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal
/// </summary>
public class SqlServerTransactionFactory : IRelationalTransactionFactory
{
/// <summary>
/// Initializes a new instance of the <see cref="RelationalTransactionFactory" /> class.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this service. </param>
public SqlServerTransactionFactory([NotNull] RelationalTransactionFactoryDependencies dependencies)
{
Check.NotNull(dependencies, nameof(dependencies));

Dependencies = dependencies;
}

/// <summary>
/// Parameter object containing dependencies for this service.
/// </summary>
protected virtual RelationalTransactionFactoryDependencies Dependencies { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -28,6 +46,6 @@ public virtual RelationalTransaction Create(
Guid transactionId,
IDiagnosticsLogger<DbLoggerCategory.Database.Transaction> logger,
bool transactionOwned)
=> new SqlServerTransaction(connection, transaction, transactionId, logger, transactionOwned);
=> new SqlServerTransaction(connection, transaction, transactionId, logger, transactionOwned, Dependencies.SqlGenerationHelper);
}
}
20 changes: 16 additions & 4 deletions src/EFCore/Storage/IDbContextTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,27 @@ Task RollbackToSavepointAsync([NotNull] string name, CancellationToken cancellat
=> throw new NotSupportedException();

/// <summary>
/// Destroys a savepoint previously defined in the current transaction. This allows the system to
/// reclaim some resources before the transaction ends.
/// <para>
/// Destroys a savepoint previously defined in the current transaction. This allows the system to
/// reclaim some resources before the transaction ends.
/// </para>
/// <para>
/// If savepoint release isn't supported, <see cref="ReleaseSavepoint " /> and <see cref="ReleaseSavepointAsync " /> should
/// do nothing rather than throw. This is the default behavior.
/// </para>
/// </summary>
/// <param name="name"> The name of the savepoint to release. </param>
void ReleaseSavepoint([NotNull] string name) { }

/// <summary>
/// Destroys a savepoint previously defined in the current transaction. This allows the system to
/// reclaim some resources before the transaction ends.
/// <para>
/// Destroys a savepoint previously defined in the current transaction. This allows the system to
/// reclaim some resources before the transaction ends.
/// </para>
/// <para>
/// If savepoint release isn't supported, <see cref="ReleaseSavepoint " /> and <see cref="ReleaseSavepointAsync " /> should
/// do nothing rather than throw. This is the default behavior.
/// </para>
/// </summary>
/// <param name="name"> The name of the savepoint to release. </param>
/// <param name="cancellationToken"> The cancellation token. </param>
Expand Down
Loading