Skip to content

Commit 72524cc

Browse files
SergeiPavlovCopilotsnaumenko-st
authored
Get rid of unsafe code from TruncateToNetDecimal() implemetation (#397)
* Make `TruncateToNetDecimal()` be `safe` * Ten value * Update Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/InternalHelpers.cs Co-authored-by: Copilot <[email protected]> * FromSqlDecimalData() * Update Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/InternalHelpers.cs Co-authored-by: Sergey Naumenko <[email protected]> * Update Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/InternalHelpers.cs Co-authored-by: Sergey Naumenko <[email protected]> * Simplify * Pattern * Fix casting * Fix order of UInt128 parts * Simplify * Simplify * u128 * Simplify * Simplify * Try Catch * Typo * add comment * Simplify * correct comment * fix build * Simplify * Simplify --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Sergey Naumenko <[email protected]>
1 parent 91f5332 commit 72524cc

File tree

1 file changed

+129
-195
lines changed

1 file changed

+129
-195
lines changed

Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/InternalHelpers.cs

Lines changed: 129 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -4,219 +4,153 @@
44
// Created by: Alexey Kulakov
55
// Created: 2020.04.10
66

7-
using System;
8-
using System.Collections.Generic;
97
using Microsoft.Data.SqlClient;
108
using System.Data.SqlTypes;
11-
using System.Diagnostics.Metrics;
129
using Xtensive.Diagnostics;
1310

14-
namespace Xtensive.Sql.Drivers.SqlServer
15-
{
16-
internal static class InternalHelpers
17-
{
18-
private static readonly uint[] PowersOf10 = {
19-
1,
20-
10,
21-
100,
22-
1000,
23-
10000,
24-
100000,
25-
1000000,
26-
10000000,
27-
100000000,
28-
1000000000
29-
};
11+
namespace Xtensive.Sql.Drivers.SqlServer;
3012

31-
/// <summary>
32-
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
33-
/// directly from your code. This API may change or be removed in future releases.
34-
/// </summary>
35-
public static bool ShouldRetryOn(Exception ex)
36-
{
37-
ArgumentNullException.ThrowIfNull(ex);
38-
if (ex is SqlException sqlException) {
39-
foreach (SqlError err in sqlException.Errors) {
40-
Metrics.SqlErrorCounter.Add(1, KeyValuePair.Create("Code", (object)err.Number));
41-
}
13+
internal static class InternalHelpers
14+
{
15+
private static readonly UInt128 Max96bitValue = new(0xFFFFFFFFUL, ulong.MaxValue);
4216

43-
foreach (SqlError err in sqlException.Errors) {
44-
switch (err.Number) {
45-
// SQL Error Code: 49920
46-
// Cannot process request. Too many operations in progress for subscription "%ld".
47-
// The service is busy processing multiple requests for this subscription.
48-
// Requests are currently blocked for resource optimization. Query sys.dm_operation_status for operation status.
49-
// Wait until pending requests are complete or delete one of your pending requests and retry your request later.
50-
case 49920:
51-
// SQL Error Code: 49919
52-
// Cannot process create or update request. Too many create or update operations in progress for subscription "%ld".
53-
// The service is busy processing multiple create or update requests for your subscription or server.
54-
// Requests are currently blocked for resource optimization. Query sys.dm_operation_status for pending operations.
55-
// Wait till pending create or update requests are complete or delete one of your pending requests and
56-
// retry your request later.
57-
case 49919:
58-
// SQL Error Code: 49918
59-
// Cannot process request. Not enough resources to process request.
60-
// The service is currently busy.Please retry the request later.
61-
case 49918:
62-
// SQL Error Code: 41839
63-
// Transaction exceeded the maximum number of commit dependencies.
64-
case 41839:
65-
// SQL Error Code: 41325
66-
// The current transaction failed to commit due to a serializable validation failure.
67-
case 41325:
68-
// SQL Error Code: 41305
69-
// The current transaction failed to commit due to a repeatable read validation failure.
70-
case 41305:
71-
// SQL Error Code: 41302
72-
// The current transaction attempted to update a record that has been updated since the transaction started.
73-
case 41302:
74-
// SQL Error Code: 41301
75-
// Dependency failure: a dependency was taken on another transaction that later failed to commit.
76-
case 41301:
77-
// SQL Error Code: 40613
78-
// Database XXXX on server YYYY is not currently available. Please retry the connection later.
79-
// If the problem persists, contact customer support, and provide them the session tracing ID of ZZZZZ.
80-
case 40613:
81-
// SQL Error Code: 40501
82-
// The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded).
83-
case 40501:
84-
// SQL Error Code: 40197
85-
// The service has encountered an error processing your request. Please try again.
86-
case 40197:
87-
// SQL Error Code: 10929
88-
// Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d.
89-
// However, the server is currently too busy to support requests greater than %d for this database.
90-
// For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again.
91-
case 10929:
92-
// SQL Error Code: 10928
93-
// Resource ID: %d. The %s limit for the database is %d and has been reached. For more information,
94-
// see http://go.microsoft.com/fwlink/?LinkId=267637.
95-
case 10928:
96-
// SQL Error Code: 10060
97-
// A network-related or instance-specific error occurred while establishing a connection to SQL Server.
98-
// The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server
99-
// is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed
100-
// because the connected party did not properly respond after a period of time, or established connection failed
101-
// because connected host has failed to respond.)"}
102-
case 10060:
103-
// SQL Error Code: 10054
104-
// A transport-level error has occurred when sending the request to the server.
105-
// (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.)
106-
case 10054:
107-
// SQL Error Code: 10053
108-
// A transport-level error has occurred when receiving results from the server.
109-
// An established connection was aborted by the software in your host machine.
110-
case 10053:
111-
// SQL Error Code: 1205
112-
// Deadlock
113-
case 1205:
114-
// SQL Error Code: 233
115-
// The client was unable to establish a connection because of an error during connection initialization process before login.
116-
// Possible causes include the following: the client tried to connect to an unsupported version of SQL Server;
117-
// the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum
118-
// allowed connections) on the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by
119-
// the remote host.)
120-
case 233:
121-
// SQL Error Code: 121
122-
// The semaphore timeout period has expired
123-
case 121:
124-
// SQL Error Code: 64
125-
// A connection was successfully established with the server, but then an error occurred during the login process.
126-
// (provider: TCP Provider, error: 0 - The specified network name is no longer available.)
127-
case 64:
128-
// DBNETLIB Error Code: 20
129-
// The instance of SQL Server you attempted to connect to does not support encryption.
130-
case 20:
131-
return true;
132-
// This exception can be thrown even if the operation completed successfully, so it's safer to let the application fail.
133-
// DBNETLIB Error Code: -2
134-
// Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated.
135-
//case -2:
136-
}
137-
}
17+
private static readonly UInt128 Ten = 10;
13818

139-
return false;
19+
/// <summary>
20+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
21+
/// directly from your code. This API may change or be removed in future releases.
22+
/// </summary>
23+
public static bool ShouldRetryOn(Exception ex)
24+
{
25+
ArgumentNullException.ThrowIfNull(ex);
26+
if (ex is SqlException sqlException) {
27+
foreach (SqlError err in sqlException.Errors) {
28+
Metrics.SqlErrorCounter.Add(1, KeyValuePair.Create("Code", (object)err.Number));
14029
}
14130

142-
Metrics.SqlErrorCounter.Add(1, KeyValuePair.Create("Code", (object)ex.GetType().Name));
143-
return ex is TimeoutException;
144-
}
145-
146-
internal static unsafe decimal TruncateToNetDecimal(SqlDecimal sqlDecimal)
147-
{
148-
var inputData = sqlDecimal.Data;
149-
var scale = sqlDecimal.Scale;
150-
151-
if (inputData[3] == 0) {
152-
if (scale <= 28) {
153-
return sqlDecimal.Value;
31+
foreach (SqlError err in sqlException.Errors) {
32+
switch (err.Number) {
33+
// SQL Error Code: 49920
34+
// Cannot process request. Too many operations in progress for subscription "%ld".
35+
// The service is busy processing multiple requests for this subscription.
36+
// Requests are currently blocked for resource optimization. Query sys.dm_operation_status for operation status.
37+
// Wait until pending requests are complete or delete one of your pending requests and retry your request later.
38+
case 49920:
39+
// SQL Error Code: 49919
40+
// Cannot process create or update request. Too many create or update operations in progress for subscription "%ld".
41+
// The service is busy processing multiple create or update requests for your subscription or server.
42+
// Requests are currently blocked for resource optimization. Query sys.dm_operation_status for pending operations.
43+
// Wait till pending create or update requests are complete or delete one of your pending requests and
44+
// retry your request later.
45+
case 49919:
46+
// SQL Error Code: 49918
47+
// Cannot process request. Not enough resources to process request.
48+
// The service is currently busy.Please retry the request later.
49+
case 49918:
50+
// SQL Error Code: 41839
51+
// Transaction exceeded the maximum number of commit dependencies.
52+
case 41839:
53+
// SQL Error Code: 41325
54+
// The current transaction failed to commit due to a serializable validation failure.
55+
case 41325:
56+
// SQL Error Code: 41305
57+
// The current transaction failed to commit due to a repeatable read validation failure.
58+
case 41305:
59+
// SQL Error Code: 41302
60+
// The current transaction attempted to update a record that has been updated since the transaction started.
61+
case 41302:
62+
// SQL Error Code: 41301
63+
// Dependency failure: a dependency was taken on another transaction that later failed to commit.
64+
case 41301:
65+
// SQL Error Code: 40613
66+
// Database XXXX on server YYYY is not currently available. Please retry the connection later.
67+
// If the problem persists, contact customer support, and provide them the session tracing ID of ZZZZZ.
68+
case 40613:
69+
// SQL Error Code: 40501
70+
// The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded).
71+
case 40501:
72+
// SQL Error Code: 40197
73+
// The service has encountered an error processing your request. Please try again.
74+
case 40197:
75+
// SQL Error Code: 10929
76+
// Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d.
77+
// However, the server is currently too busy to support requests greater than %d for this database.
78+
// For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again.
79+
case 10929:
80+
// SQL Error Code: 10928
81+
// Resource ID: %d. The %s limit for the database is %d and has been reached. For more information,
82+
// see http://go.microsoft.com/fwlink/?LinkId=267637.
83+
case 10928:
84+
// SQL Error Code: 10060
85+
// A network-related or instance-specific error occurred while establishing a connection to SQL Server.
86+
// The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server
87+
// is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed
88+
// because the connected party did not properly respond after a period of time, or established connection failed
89+
// because connected host has failed to respond.)"}
90+
case 10060:
91+
// SQL Error Code: 10054
92+
// A transport-level error has occurred when sending the request to the server.
93+
// (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.)
94+
case 10054:
95+
// SQL Error Code: 10053
96+
// A transport-level error has occurred when receiving results from the server.
97+
// An established connection was aborted by the software in your host machine.
98+
case 10053:
99+
// SQL Error Code: 1205
100+
// Deadlock
101+
case 1205:
102+
// SQL Error Code: 233
103+
// The client was unable to establish a connection because of an error during connection initialization process before login.
104+
// Possible causes include the following: the client tried to connect to an unsupported version of SQL Server;
105+
// the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum
106+
// allowed connections) on the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by
107+
// the remote host.)
108+
case 233:
109+
// SQL Error Code: 121
110+
// The semaphore timeout period has expired
111+
case 121:
112+
// SQL Error Code: 64
113+
// A connection was successfully established with the server, but then an error occurred during the login process.
114+
// (provider: TCP Provider, error: 0 - The specified network name is no longer available.)
115+
case 64:
116+
// DBNETLIB Error Code: 20
117+
// The instance of SQL Server you attempted to connect to does not support encryption.
118+
case 20:
119+
return true;
120+
// This exception can be thrown even if the operation completed successfully, so it's safer to let the application fail.
121+
// DBNETLIB Error Code: -2
122+
// Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated.
123+
//case -2:
154124
}
155125
}
156-
else if (scale == 0) {
157-
return sqlDecimal.Value; // throws OverflowException.
158-
}
159126

160-
fixed (int* inputS = inputData) {
161-
var input = (uint*) inputS;
162-
byte maxZeroCount = 0;
163-
while (maxZeroCount < scale && (input[maxZeroCount >> 6] & (1 << maxZeroCount)) == 0) {
164-
maxZeroCount++;
165-
}
166-
167-
var realScale = scale;
168-
var outputData = sqlDecimal.Data;
169-
fixed (int* outputS = outputData) {
170-
var output = (uint*) outputS;
171-
var dividerPow = maxZeroCount > 9 ? (byte) 9 : maxZeroCount;
172-
if (dividerPow > 5) {
173-
var divider = PowersOf10[dividerPow];
174-
for (byte length = 4; realScale >= dividerPow; realScale -= dividerPow) {
175-
if (DivHasRem(input, ref length, divider)) {
176-
break;
177-
}
178-
179-
output[0] = input[0];
180-
output[1] = input[1];
181-
output[2] = input[2];
182-
output[3] = input[3];
183-
}
184-
}
127+
return false;
128+
}
185129

186-
for (byte length = 4; realScale > 0 && output[3] != 0; realScale--) {
187-
_ = DivHasRem(output, ref length, 10);
188-
}
130+
Metrics.SqlErrorCounter.Add(1, KeyValuePair.Create("Code", (object)ex.GetType().Name));
131+
return ex is TimeoutException;
132+
}
189133

190-
if (output[3] != 0) {
191-
return sqlDecimal.Value; // throws OverflowException.
192-
}
134+
private static UInt128 FromSqlDecimalData(int[] a) =>
135+
new((uint) a[2] | ((ulong) (uint) a[3] << 32), (uint) a[0] | ((ulong) (uint) a[1] << 32));
193136

194-
return new decimal(outputS[0], outputS[1], outputS[2], !sqlDecimal.IsPositive, realScale);
195-
}
196-
}
137+
internal static decimal TruncateToNetDecimal(SqlDecimal sqlDecimal)
138+
{
139+
try {
140+
return sqlDecimal.Value; // throws an OverflowException if the value is out of the decimal range.
197141
}
142+
catch (OverflowException) {
143+
var scale = sqlDecimal.Scale;
144+
var u128 = FromSqlDecimalData(sqlDecimal.Data); // sqlDecimal.Data allocates a new array
198145

199-
private static unsafe bool DivHasRem(uint* bits, ref byte length, uint divider)
200-
{
201-
unchecked {
202-
ulong rem = 0;
203-
var tempBits = bits + length;
204-
for (byte i = 0; i < length; i++) {
205-
var bit = *(--tempBits);
206-
if (bit == 0 && i == 0) {
207-
length--;
208-
i--;
209-
continue;
210-
}
211-
212-
var num = (rem << 32) + bit;
213-
bit = (uint) (num / divider);
214-
rem = (uint) (num - (ulong) bit * divider);
215-
*tempBits = bit;
216-
}
146+
for (; scale > 0 && u128 > Max96bitValue; --scale) {
147+
u128 /= Ten;
148+
}
217149

218-
return rem > 0;
150+
if (u128 > Max96bitValue) {
151+
throw;
219152
}
153+
return new((int) u128, (int) (u128 >> 32), (int) (u128 >> 64), !sqlDecimal.IsPositive, scale);
220154
}
221155
}
222-
}
156+
}

0 commit comments

Comments
 (0)