diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/InternalHelpers.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/InternalHelpers.cs index 6f9d8f0b4..6e2e5770e 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/InternalHelpers.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/InternalHelpers.cs @@ -4,219 +4,153 @@ // Created by: Alexey Kulakov // Created: 2020.04.10 -using System; -using System.Collections.Generic; using Microsoft.Data.SqlClient; using System.Data.SqlTypes; -using System.Diagnostics.Metrics; using Xtensive.Diagnostics; -namespace Xtensive.Sql.Drivers.SqlServer -{ - internal static class InternalHelpers - { - private static readonly uint[] PowersOf10 = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000 - }; +namespace Xtensive.Sql.Drivers.SqlServer; - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static bool ShouldRetryOn(Exception ex) - { - ArgumentNullException.ThrowIfNull(ex); - if (ex is SqlException sqlException) { - foreach (SqlError err in sqlException.Errors) { - Metrics.SqlErrorCounter.Add(1, KeyValuePair.Create("Code", (object)err.Number)); - } +internal static class InternalHelpers +{ + private static readonly UInt128 Max96bitValue = new(0xFFFFFFFFUL, ulong.MaxValue); - foreach (SqlError err in sqlException.Errors) { - switch (err.Number) { - // SQL Error Code: 49920 - // Cannot process request. Too many operations in progress for subscription "%ld". - // The service is busy processing multiple requests for this subscription. - // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for operation status. - // Wait until pending requests are complete or delete one of your pending requests and retry your request later. - case 49920: - // SQL Error Code: 49919 - // Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". - // The service is busy processing multiple create or update requests for your subscription or server. - // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for pending operations. - // Wait till pending create or update requests are complete or delete one of your pending requests and - // retry your request later. - case 49919: - // SQL Error Code: 49918 - // Cannot process request. Not enough resources to process request. - // The service is currently busy.Please retry the request later. - case 49918: - // SQL Error Code: 41839 - // Transaction exceeded the maximum number of commit dependencies. - case 41839: - // SQL Error Code: 41325 - // The current transaction failed to commit due to a serializable validation failure. - case 41325: - // SQL Error Code: 41305 - // The current transaction failed to commit due to a repeatable read validation failure. - case 41305: - // SQL Error Code: 41302 - // The current transaction attempted to update a record that has been updated since the transaction started. - case 41302: - // SQL Error Code: 41301 - // Dependency failure: a dependency was taken on another transaction that later failed to commit. - case 41301: - // SQL Error Code: 40613 - // Database XXXX on server YYYY is not currently available. Please retry the connection later. - // If the problem persists, contact customer support, and provide them the session tracing ID of ZZZZZ. - case 40613: - // SQL Error Code: 40501 - // The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded). - case 40501: - // SQL Error Code: 40197 - // The service has encountered an error processing your request. Please try again. - case 40197: - // SQL Error Code: 10929 - // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. - // However, the server is currently too busy to support requests greater than %d for this database. - // For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again. - case 10929: - // SQL Error Code: 10928 - // Resource ID: %d. The %s limit for the database is %d and has been reached. For more information, - // see http://go.microsoft.com/fwlink/?LinkId=267637. - case 10928: - // SQL Error Code: 10060 - // A network-related or instance-specific error occurred while establishing a connection to SQL Server. - // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server - // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed - // because the connected party did not properly respond after a period of time, or established connection failed - // because connected host has failed to respond.)"} - case 10060: - // SQL Error Code: 10054 - // A transport-level error has occurred when sending the request to the server. - // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) - case 10054: - // SQL Error Code: 10053 - // A transport-level error has occurred when receiving results from the server. - // An established connection was aborted by the software in your host machine. - case 10053: - // SQL Error Code: 1205 - // Deadlock - case 1205: - // SQL Error Code: 233 - // The client was unable to establish a connection because of an error during connection initialization process before login. - // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; - // the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum - // allowed connections) on the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by - // the remote host.) - case 233: - // SQL Error Code: 121 - // The semaphore timeout period has expired - case 121: - // SQL Error Code: 64 - // A connection was successfully established with the server, but then an error occurred during the login process. - // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) - case 64: - // DBNETLIB Error Code: 20 - // The instance of SQL Server you attempted to connect to does not support encryption. - case 20: - return true; - // This exception can be thrown even if the operation completed successfully, so it's safer to let the application fail. - // DBNETLIB Error Code: -2 - // Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated. - //case -2: - } - } + private static readonly UInt128 Ten = 10; - return false; + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static bool ShouldRetryOn(Exception ex) + { + ArgumentNullException.ThrowIfNull(ex); + if (ex is SqlException sqlException) { + foreach (SqlError err in sqlException.Errors) { + Metrics.SqlErrorCounter.Add(1, KeyValuePair.Create("Code", (object)err.Number)); } - Metrics.SqlErrorCounter.Add(1, KeyValuePair.Create("Code", (object)ex.GetType().Name)); - return ex is TimeoutException; - } - - internal static unsafe decimal TruncateToNetDecimal(SqlDecimal sqlDecimal) - { - var inputData = sqlDecimal.Data; - var scale = sqlDecimal.Scale; - - if (inputData[3] == 0) { - if (scale <= 28) { - return sqlDecimal.Value; + foreach (SqlError err in sqlException.Errors) { + switch (err.Number) { + // SQL Error Code: 49920 + // Cannot process request. Too many operations in progress for subscription "%ld". + // The service is busy processing multiple requests for this subscription. + // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for operation status. + // Wait until pending requests are complete or delete one of your pending requests and retry your request later. + case 49920: + // SQL Error Code: 49919 + // Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". + // The service is busy processing multiple create or update requests for your subscription or server. + // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for pending operations. + // Wait till pending create or update requests are complete or delete one of your pending requests and + // retry your request later. + case 49919: + // SQL Error Code: 49918 + // Cannot process request. Not enough resources to process request. + // The service is currently busy.Please retry the request later. + case 49918: + // SQL Error Code: 41839 + // Transaction exceeded the maximum number of commit dependencies. + case 41839: + // SQL Error Code: 41325 + // The current transaction failed to commit due to a serializable validation failure. + case 41325: + // SQL Error Code: 41305 + // The current transaction failed to commit due to a repeatable read validation failure. + case 41305: + // SQL Error Code: 41302 + // The current transaction attempted to update a record that has been updated since the transaction started. + case 41302: + // SQL Error Code: 41301 + // Dependency failure: a dependency was taken on another transaction that later failed to commit. + case 41301: + // SQL Error Code: 40613 + // Database XXXX on server YYYY is not currently available. Please retry the connection later. + // If the problem persists, contact customer support, and provide them the session tracing ID of ZZZZZ. + case 40613: + // SQL Error Code: 40501 + // The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded). + case 40501: + // SQL Error Code: 40197 + // The service has encountered an error processing your request. Please try again. + case 40197: + // SQL Error Code: 10929 + // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. + // However, the server is currently too busy to support requests greater than %d for this database. + // For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again. + case 10929: + // SQL Error Code: 10928 + // Resource ID: %d. The %s limit for the database is %d and has been reached. For more information, + // see http://go.microsoft.com/fwlink/?LinkId=267637. + case 10928: + // SQL Error Code: 10060 + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server + // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed + // because the connected party did not properly respond after a period of time, or established connection failed + // because connected host has failed to respond.)"} + case 10060: + // SQL Error Code: 10054 + // A transport-level error has occurred when sending the request to the server. + // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) + case 10054: + // SQL Error Code: 10053 + // A transport-level error has occurred when receiving results from the server. + // An established connection was aborted by the software in your host machine. + case 10053: + // SQL Error Code: 1205 + // Deadlock + case 1205: + // SQL Error Code: 233 + // The client was unable to establish a connection because of an error during connection initialization process before login. + // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; + // the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum + // allowed connections) on the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by + // the remote host.) + case 233: + // SQL Error Code: 121 + // The semaphore timeout period has expired + case 121: + // SQL Error Code: 64 + // A connection was successfully established with the server, but then an error occurred during the login process. + // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) + case 64: + // DBNETLIB Error Code: 20 + // The instance of SQL Server you attempted to connect to does not support encryption. + case 20: + return true; + // This exception can be thrown even if the operation completed successfully, so it's safer to let the application fail. + // DBNETLIB Error Code: -2 + // Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated. + //case -2: } } - else if (scale == 0) { - return sqlDecimal.Value; // throws OverflowException. - } - fixed (int* inputS = inputData) { - var input = (uint*) inputS; - byte maxZeroCount = 0; - while (maxZeroCount < scale && (input[maxZeroCount >> 6] & (1 << maxZeroCount)) == 0) { - maxZeroCount++; - } - - var realScale = scale; - var outputData = sqlDecimal.Data; - fixed (int* outputS = outputData) { - var output = (uint*) outputS; - var dividerPow = maxZeroCount > 9 ? (byte) 9 : maxZeroCount; - if (dividerPow > 5) { - var divider = PowersOf10[dividerPow]; - for (byte length = 4; realScale >= dividerPow; realScale -= dividerPow) { - if (DivHasRem(input, ref length, divider)) { - break; - } - - output[0] = input[0]; - output[1] = input[1]; - output[2] = input[2]; - output[3] = input[3]; - } - } + return false; + } - for (byte length = 4; realScale > 0 && output[3] != 0; realScale--) { - _ = DivHasRem(output, ref length, 10); - } + Metrics.SqlErrorCounter.Add(1, KeyValuePair.Create("Code", (object)ex.GetType().Name)); + return ex is TimeoutException; + } - if (output[3] != 0) { - return sqlDecimal.Value; // throws OverflowException. - } + private static UInt128 FromSqlDecimalData(int[] a) => + new((uint) a[2] | ((ulong) (uint) a[3] << 32), (uint) a[0] | ((ulong) (uint) a[1] << 32)); - return new decimal(outputS[0], outputS[1], outputS[2], !sqlDecimal.IsPositive, realScale); - } - } + internal static decimal TruncateToNetDecimal(SqlDecimal sqlDecimal) + { + try { + return sqlDecimal.Value; // throws an OverflowException if the value is out of the decimal range. } + catch (OverflowException) { + var scale = sqlDecimal.Scale; + var u128 = FromSqlDecimalData(sqlDecimal.Data); // sqlDecimal.Data allocates a new array - private static unsafe bool DivHasRem(uint* bits, ref byte length, uint divider) - { - unchecked { - ulong rem = 0; - var tempBits = bits + length; - for (byte i = 0; i < length; i++) { - var bit = *(--tempBits); - if (bit == 0 && i == 0) { - length--; - i--; - continue; - } - - var num = (rem << 32) + bit; - bit = (uint) (num / divider); - rem = (uint) (num - (ulong) bit * divider); - *tempBits = bit; - } + for (; scale > 0 && u128 > Max96bitValue; --scale) { + u128 /= Ten; + } - return rem > 0; + if (u128 > Max96bitValue) { + throw; } + return new((int) u128, (int) (u128 >> 32), (int) (u128 >> 64), !sqlDecimal.IsPositive, scale); } } -} \ No newline at end of file +}