Skip to content

Commit

Permalink
#45 WIP Commands which are suppressing errors are now wrapped in TRY …
Browse files Browse the repository at this point in the history
…... CATCH in the SQL. Still need to implement error handling in the C#.

- Moved the code for wrapping SQL in TRY ... CATCH from the SqlBatch to extension methods on IBatchItem so that it can be used by SqlBatchCommand as well.
- The SQL now has a SELECT statement before each command to pause execution till the command is ready.
- Added supressErrors and exceptionHandler parameters to the generated AddExecute* generic overloads.
- Added missing generated AddExecuteXmlReader generic overloads.
- Added sproc to test LocalData.mdf database for raising an error, for testing error handling.
  • Loading branch information
billings7 committed May 17, 2017
1 parent c069631 commit 2b53e29
Show file tree
Hide file tree
Showing 16 changed files with 769 additions and 436 deletions.
10 changes: 9 additions & 1 deletion Database/BatchProcessArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ internal class BatchProcessArgs
{
[NotNull]
public readonly Version ServerVersion;

[NotNull]
public readonly string InfoMessagePrefix;

[NotNull]
public readonly string ConnectionString;

[NotNull]
public readonly SqlStringBuilder SqlBuilder = new SqlStringBuilder();
Expand Down Expand Up @@ -49,9 +55,11 @@ internal class BatchProcessArgs

public ushort CommandIndex;

public BatchProcessArgs([NotNull] Version serverVersion)
public BatchProcessArgs([NotNull] Version serverVersion, [NotNull] string infoMessagePrefix, string connectionString)
{
ServerVersion = serverVersion;
InfoMessagePrefix = infoMessagePrefix;
ConnectionString = connectionString;
}

[NotNull]
Expand Down
5 changes: 5 additions & 0 deletions Database/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ internal static class BatchState

internal static class ExecuteState
{
/// <summary>
/// Indicates the start of a command. The next record set will be a "pause" set to allow the command to start the actual processing.
/// </summary>
internal const string Start = "Start";

/// <summary>
/// Indicates the next record set will be for the output parameters for the command.
/// </summary>
Expand Down
44 changes: 36 additions & 8 deletions Database/DbBatchDataReader.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
#region © Copyright Web Applications (UK) Ltd, 2017. All rights reserved.
// Copyright (c) 2017, Web Applications UK Ltd
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of Web Applications UK Ltd nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL WEB APPLICATIONS UK LTD BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -125,7 +150,7 @@ internal BatchReaderState State
/// <returns>
/// <see langword="true" /> if the behavior applies; otherwise, <see langword="false" />.
/// </returns>
protected bool IsCommandBehavior(CommandBehavior condition)
protected bool IsCommandBehavior(CommandBehavior condition)
=> condition == (condition & CommandBehavior);

/// <summary>
Expand Down Expand Up @@ -356,7 +381,7 @@ protected override void Dispose(bool disposing)
/// <param name="bufferOffset">The index with the buffer to which the data will be copied.</param>
/// <param name="length">The maximum number of characters to read.</param>
/// <exception cref="T:System.InvalidCastException">The specified cast is not valid. </exception>
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
=> BaseReaderOpen().GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);

/// <summary>Gets the value of the specified column as a single character.</summary>
Expand All @@ -372,7 +397,7 @@ public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int b
/// <param name="buffer">The buffer into which to copy the data.</param>
/// <param name="bufferOffset">The index with the buffer to which the data will be copied.</param>
/// <param name="length">The maximum number of characters to read.</param>
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
=> BaseReaderOpen().GetChars(ordinal, dataOffset, buffer, bufferOffset, length);

/// <summary>Gets the value of the specified column as a globally-unique identifier (GUID).</summary>
Expand Down Expand Up @@ -476,17 +501,20 @@ public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken can
/// <summary>Returns the provider-specific field type of the specified column.</summary>
/// <returns>The <see cref="T:System.Type" /> object that describes the data type of the specified column.</returns>
/// <param name="ordinal">The zero-based column ordinal.</param>
public override Type GetProviderSpecificFieldType(int ordinal) => BaseReaderOpen().GetProviderSpecificFieldType(ordinal);
public override Type GetProviderSpecificFieldType(int ordinal) => BaseReaderOpen()
.GetProviderSpecificFieldType(ordinal);

/// <summary>Gets the value of the specified column as an instance of <see cref="T:System.Object" />.</summary>
/// <returns>The value of the specified column.</returns>
/// <param name="ordinal">The zero-based column ordinal.</param>
public override object GetProviderSpecificValue(int ordinal) => BaseReaderOpen().GetProviderSpecificValue(ordinal);
public override object GetProviderSpecificValue(int ordinal) => BaseReaderOpen()
.GetProviderSpecificValue(ordinal);

/// <summary>Gets all provider-specific attribute columns in the collection for the current row.</summary>
/// <returns>The number of instances of <see cref="T:System.Object" /> in the array.</returns>
/// <param name="values">An array of <see cref="T:System.Object" /> into which to copy the attribute columns.</param>
public override int GetProviderSpecificValues(object[] values) => BaseReaderOpen().GetProviderSpecificValues(values);
public override int GetProviderSpecificValues(object[] values) => BaseReaderOpen()
.GetProviderSpecificValues(values);

/// <summary>Retrieves data as a <see cref="T:System.IO.Stream" />.</summary>
/// <returns>The returned object.</returns>
Expand Down
182 changes: 182 additions & 0 deletions Database/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.SqlTypes;
Expand Down Expand Up @@ -102,5 +103,186 @@ public static int IndexOf(
}
return -1;
}

/// <summary>
/// Begins a TRY block in the <paramref name="args"/> <see cref="BatchProcessArgs.SqlBuilder"/>.
/// </summary>
/// <param name="item">The item the block is for.</param>
/// <param name="args">The arguments.</param>
/// <param name="startIndex">The start index.</param>
/// <exception cref="System.ArgumentOutOfRangeException">IsolationLevel</exception>
internal static void BeginTry([NotNull] this IBatchItem item, [NotNull] BatchProcessArgs args, out int startIndex)
{
bool hasTransaction = item.Transaction != TransactionType.None;
bool hasTryCatch = item.SuppressErrors || hasTransaction;

if (hasTryCatch)
{
if (hasTransaction)
{
string isoLevel = GetIsolationLevelStr(item.IsolationLevel);
if (isoLevel == null)
throw new ArgumentOutOfRangeException(
nameof(IsolationLevel),
item.IsolationLevel,
string.Format(Resources.IsolationLevelNotSupported, item.IsolationLevel));

string transactionName = item.TransactionName;
Debug.Assert(transactionName != null);

// Set the isolation level and begin or save a transaction for the batch
args.SqlBuilder
.AppendLine()
.Append("SET TRANSACTION ISOLATION LEVEL ")
.Append(isoLevel)
.AppendLine(";")

.Append(args.InTransaction ? "SAVE" : "BEGIN")
.Append(" TRANSACTION ")
.AppendIdentifier(transactionName)
.AppendLine(";");
args.TransactionStack.Push(transactionName, isoLevel);
}

// Wrap the contents of the batch in a TRY ... CATCH block
args.SqlBuilder
.AppendLine()
.AppendLine("BEGIN TRY")
.AppendLine()
.GetLength(out startIndex);
}
else
startIndex = -1;
}

/// <summary>
/// Ends a TRY block started with <see cref="BeginTry"/>.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The arguments.</param>
/// <param name="startIndex">The start index.</param>
internal static void EndTry([NotNull] this IBatchItem item, [NotNull] BatchProcessArgs args, int startIndex)
{
TransactionType transaction = item.Transaction;
bool hasTransaction = transaction != TransactionType.None;
bool hasTryCatch = item.SuppressErrors || hasTransaction;

if (hasTryCatch)
{
string tranName = item.TransactionName;
if (hasTransaction)
{
args.TransactionStack.Pop(out string name, out _);
Debug.Assert(name == tranName);
Debug.Assert(tranName != null);
}

// If the transaction type is Commit and this is a root transaction, commit it
if (transaction == TransactionType.Commit && !args.InTransaction)
{
args.SqlBuilder
.AppendLine()
.Append("COMMIT TRANSACTION ")
.AppendIdentifier(tranName)
.AppendLine(";");
}
// If the transaction is Rollback, always roll it back
else if (transaction == TransactionType.Rollback)
args.SqlBuilder
.Append("ROLLBACK TRANSACTION ")
.AppendIdentifier(tranName)
.AppendLine(";");

// End the TRY block and start the CATCH block
args.SqlBuilder
.IndentRegion(startIndex)
.AppendLine()
.AppendLine("END TRY")
.AppendLine("BEGIN CATCH")
.GetLength(out startIndex);

// If there is a transaction, roll it back if possible
if (hasTransaction)
{
if (args.InTransaction)
args.SqlBuilder
.AppendLine("IF XACT_STATE() <> -1 ")
.Append("\t");

args.SqlBuilder
.Append("ROLLBACK TRANSACTION ")
.AppendIdentifier(tranName)
.AppendLine(";");
}

// Output an Error info message then select the error information
SqlBatch.AppendInfo(args, Constants.ExecuteState.Error, "%d", "@CmdIndex")
.AppendLine(
"SELECT\tERROR_NUMBER(),\r\n\tERROR_SEVERITY(),\r\n\tERROR_STATE(),\r\n\tERROR_LINE(),\r\n\tISNULL(QUOTENAME(ERROR_PROCEDURE()),'NULL'),\r\n\tERROR_MESSAGE();");

// If the error isnt being suppressed, rethrow it for any outer catches to handle it
if (!item.SuppressErrors)
{
if (args.ServerVersion.Major < 11)
{
// Cant rethrow the actual error, so raise a special error message
SqlBatch.AppendInfo(args, Constants.ExecuteState.ReThrow, "%d", "@CmdIndex", true);
}
else
{
args.SqlBuilder.AppendLine("THROW;");
}
}

// End the CATCH block
args.SqlBuilder
.IndentRegion(startIndex)
.AppendLine()
.AppendLine("END CATCH")
.AppendLine();

// Reset the isolation level
if (hasTransaction)
{
if (!args.TransactionStack.TryPeek(out _, out string isoLevel))
isoLevel = GetIsolationLevelStr(IsolationLevel.Unspecified);

if (isoLevel != null)
args.SqlBuilder
.AppendLine()
.Append("SET TRANSACTION ISOLATION LEVEL ")
.Append(isoLevel)
.AppendLine(";")
.AppendLine();
}
}
}

/// <summary>
/// Gets the isolation level string.
/// </summary>
/// <param name="isoLevel">The isolation level.</param>
/// <returns></returns>
private static string GetIsolationLevelStr(IsolationLevel isoLevel)
{
switch (isoLevel)
{
case IsolationLevel.ReadUncommitted:
return "READ UNCOMMITTED";
case IsolationLevel.ReadCommitted:
case IsolationLevel.Unspecified:
return "READ COMMITTED";
case IsolationLevel.RepeatableRead:
return "REPEATABLE READ";
case IsolationLevel.Serializable:
return "SERIALIZABLE";
case IsolationLevel.Snapshot:
return "SNAPSHOT";
case IsolationLevel.Chaos:
default:
return null;
}
}

}
}
Loading

0 comments on commit 2b53e29

Please sign in to comment.