Skip to content

Commit

Permalink
Improved decimal scale conversion
Browse files Browse the repository at this point in the history
For SqlParameter and SqlBulckCopy
  • Loading branch information
DavoudEshtehari committed Mar 19, 2020
1 parent 4bdd1f0 commit d7d8e22
Show file tree
Hide file tree
Showing 19 changed files with 767 additions and 375 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -889,5 +889,29 @@ Unable to retrieve value for null key.
</remarks>
<exception cref="T:System.ArgumentNullException">To set the value to null, use <see cref="F:System.DBNull.Value" />.</exception>
</WorkstationID>
<TruncateScaledDecimal>
<summary>Gets or sets a Boolean value that indicates how the decimal scale conversion applies when using either the <see cref="P:Microsoft.Data.SqlClient.SqlParameter" /> type or <see cref="P:Microsoft.Data.SqlClient.SqlBulkCopy" /> type.</summary>
<value>The value of the <see cref="P:Microsoft.Data.SqlClient.SqlConnectionStringBuilder.TruncateScaledDecimal" /> property, or <see langword ="false" /> if none has been supplied.</value>
<remarks>
<format type="text/markdown"><![CDATA[
## Remarks
This property corresponds to the "Truncate Scaled Decimal" key within the connection string.
The value of this key must be `true`, `false`, `yes`, or `no`.
A value of `yes` is treated the same as a value of `true`.
A value of `no` is treated the same as a value of `false`.
If this property is set as **true**, the result value will be truncated by the number of <xref:Microsoft.Data.SqlClient.SqlParameter.Scale%2A>'s digits, or as defined in target table in <xref:Microsoft.Data.SqlClient.SqlBulkCopy.DestinationTableName%2A> property. Otherwise, the result will be rounded like Microsoft SQL Server does.
> [!NOTE]
> This feature is added to cover the backwards compatibility.
]]></format>
</remarks>
<altmember cref="P:Microsoft.Data.SqlClient.SqlConnectionStringBuilder.Keys" />
</TruncateScaledDecimal>
</members>
</docs>
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ internal static partial class DbConnectionStringDefaults
internal const SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled;
internal const string EnclaveAttestationUrl = "";
internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
internal const bool TruncateScaledDecimal = false;
}


Expand Down Expand Up @@ -704,6 +705,7 @@ internal static partial class DbConnectionStringKeywords
internal const string ColumnEncryptionSetting = "Column Encryption Setting";
internal const string EnclaveAttestationUrl = "Enclave Attestation Url";
internal const string AttestationProtocol = "Attestation Protocol";
internal const string TruncateScaledDecimal = "Truncate Scaled Decimal";

// common keywords (OleDb, OracleClient, SqlClient)
internal const string DataSource = "Data Source";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed)
private int _currentRowLength;
private DataRowState _rowStateToSkip;
private IEnumerator _rowEnumerator;
private bool _truncateScaledDecimal;

private int RowNumber
{
Expand All @@ -230,6 +231,17 @@ private int RowNumber
}
}

private SqlConnection Connection
{
get { return _connection; }
set
{
var cnnStringBuilder = new SqlConnectionStringBuilder(value.ConnectionString);
_truncateScaledDecimal = cnnStringBuilder.TruncateScaledDecimal;
_connection = value;
}
}

private TdsParser _parser;
private TdsParserStateObject _stateObj;
private List<_ColumnMapping> _sortedColumnMappings;
Expand Down Expand Up @@ -267,7 +279,7 @@ public SqlBulkCopy(SqlConnection connection)
{
throw ADP.ArgumentNull(nameof(connection));
}
_connection = connection;
Connection = connection;
_columnMappings = new SqlBulkCopyColumnMappingCollection();
}

Expand All @@ -294,7 +306,7 @@ public SqlBulkCopy(string connectionString)
{
throw ADP.ArgumentNull(nameof(connectionString));
}
_connection = new SqlConnection(connectionString);
Connection = new SqlConnection(connectionString);
_columnMappings = new SqlBulkCopyColumnMappingCollection();
_ownConnection = true;
}
Expand Down Expand Up @@ -1499,7 +1511,8 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re

if (sqlValue.Scale != scale)
{
sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, scale);
bool roundDecimal = !_truncateScaledDecimal;
sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, scale, roundDecimal);
}

if (sqlValue.Precision > precision)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ internal static partial class DEFAULT
internal const SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled;
internal const string EnclaveAttestationUrl = "";
internal static readonly SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
internal const bool Truncate_Scaled_Decimal = false;
}

// SqlConnection ConnectionString Options
Expand Down Expand Up @@ -97,6 +98,7 @@ internal static class KEY
internal const string Connect_Retry_Count = "connectretrycount";
internal const string Connect_Retry_Interval = "connectretryinterval";
internal const string Authentication = "authentication";
internal const string Truncate_Scaled_Decimal = "truncate scaled decimal";
}

// Constant for the number of duplicate options in the connection string
Expand Down Expand Up @@ -222,6 +224,8 @@ internal static class TRANSACTIONBINDING

private readonly string _expandedAttachDBFilename; // expanded during construction so that CreatePermissionSet & Expand are consistent

private readonly bool _truncateScaledDecimal;

internal SqlConnectionString(string connectionString) : base(connectionString, GetParseSynonyms())
{
ThrowUnsupportedIfKeywordSet(KEY.AsynchronousProcessing);
Expand Down Expand Up @@ -277,6 +281,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G
_userID = ConvertValueToString(KEY.User_ID, DEFAULT.User_ID);
_workstationId = ConvertValueToString(KEY.Workstation_Id, null);

_truncateScaledDecimal = ConvertValueToBoolean(KEY.Truncate_Scaled_Decimal, DEFAULT.Truncate_Scaled_Decimal);


if (_loadBalanceTimeout < 0)
Expand Down Expand Up @@ -501,6 +506,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS
_columnEncryptionSetting = connectionOptions._columnEncryptionSetting;
_enclaveAttestationUrl = connectionOptions._enclaveAttestationUrl;
_attestationProtocol = connectionOptions._attestationProtocol;
_truncateScaledDecimal = connectionOptions._truncateScaledDecimal;

ValidateValueLength(_dataSource, TdsEnums.MAXLEN_SERVERNAME, KEY.Data_Source);
}
Expand All @@ -526,6 +532,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS
internal bool Pooling { get { return _pooling; } }
internal bool Replication { get { return _replication; } }
internal bool UserInstance { get { return _userInstance; } }
internal bool TruncateScaledDecimal { get { return _truncateScaledDecimal; } }

internal int ConnectTimeout { get { return _connectTimeout; } }
internal int LoadBalanceTimeout { get { return _loadBalanceTimeout; } }
Expand Down Expand Up @@ -648,6 +655,7 @@ internal static Dictionary<string, string> GetParseSynonyms()
{ KEY.Connect_Retry_Count, KEY.Connect_Retry_Count },
{ KEY.Connect_Retry_Interval, KEY.Connect_Retry_Interval },
{ KEY.Authentication, KEY.Authentication },
{ KEY.Truncate_Scaled_Decimal, KEY.Truncate_Scaled_Decimal },

{ SYNONYM.APP, KEY.Application_Name },
{ SYNONYM.Async, KEY.AsynchronousProcessing },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ private enum Keywords
EnclaveAttestationUrl,
AttestationProtocol,

TruncateScaledDecimal,

// keep the count value last
KeywordsCount
}
Expand All @@ -92,6 +94,7 @@ private enum Keywords
private string _typeSystemVersion = DbConnectionStringDefaults.TypeSystemVersion;
private string _userID = DbConnectionStringDefaults.UserID;
private string _workstationID = DbConnectionStringDefaults.WorkstationID;
private bool _truncateScaledDecimal = DbConnectionStringDefaults.TruncateScaledDecimal;

private int _connectTimeout = DbConnectionStringDefaults.ConnectTimeout;
private int _loadBalanceTimeout = DbConnectionStringDefaults.LoadBalanceTimeout;
Expand Down Expand Up @@ -156,6 +159,7 @@ private static string[] CreateValidKeywords()
validKeywords[(int)Keywords.ColumnEncryptionSetting] = DbConnectionStringKeywords.ColumnEncryptionSetting;
validKeywords[(int)Keywords.EnclaveAttestationUrl] = DbConnectionStringKeywords.EnclaveAttestationUrl;
validKeywords[(int)Keywords.AttestationProtocol] = DbConnectionStringKeywords.AttestationProtocol;
validKeywords[(int)Keywords.TruncateScaledDecimal] = DbConnectionStringKeywords.TruncateScaledDecimal;
return validKeywords;
}

Expand Down Expand Up @@ -199,6 +203,7 @@ private static Dictionary<string, Keywords> CreateKeywordsDictionary()
hash.Add(DbConnectionStringKeywords.ColumnEncryptionSetting, Keywords.ColumnEncryptionSetting);
hash.Add(DbConnectionStringKeywords.EnclaveAttestationUrl, Keywords.EnclaveAttestationUrl);
hash.Add(DbConnectionStringKeywords.AttestationProtocol, Keywords.AttestationProtocol);
hash.Add(DbConnectionStringKeywords.TruncateScaledDecimal, Keywords.TruncateScaledDecimal);

hash.Add(DbConnectionStringSynonyms.APP, Keywords.ApplicationName);
hash.Add(DbConnectionStringSynonyms.EXTENDEDPROPERTIES, Keywords.AttachDBFilename);
Expand Down Expand Up @@ -356,6 +361,9 @@ public override object this[string keyword]
case Keywords.ConnectRetryInterval:
ConnectRetryInterval = ConvertToInt32(value);
break;
case Keywords.TruncateScaledDecimal:
TruncateScaledDecimal = ConvertToBoolean(value);
break;

default:
Debug.Fail("unexpected keyword");
Expand Down Expand Up @@ -821,6 +829,17 @@ public override ICollection Values
}
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/TruncateScaledDecimal/*' />
public bool TruncateScaledDecimal
{
get { return _truncateScaledDecimal; }
set
{
SetValue(DbConnectionStringKeywords.TruncateScaledDecimal, value);
_truncateScaledDecimal = value;
}
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/Clear/*' />
public override void Clear()
{
Expand Down Expand Up @@ -957,6 +976,8 @@ private object GetAt(Keywords index)
return EnclaveAttestationUrl;
case Keywords.AttestationProtocol:
return AttestationProtocol;
case Keywords.TruncateScaledDecimal:
return TruncateScaledDecimal;

default:
Debug.Fail("unexpected keyword");
Expand Down Expand Up @@ -1102,6 +1123,9 @@ private void Reset(Keywords index)
case Keywords.AttestationProtocol:
_attestationProtocol = DbConnectionStringDefaults.AttestationProtocol;
break;
case Keywords.TruncateScaledDecimal:
_truncateScaledDecimal = DbConnectionStringDefaults.TruncateScaledDecimal;
break;
default:
Debug.Fail("unexpected keyword");
throw UnsupportedKeyword(s_validKeywords[(int)index]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ internal SqlInternalConnectionTds Connection
}
}

internal bool TruncateScaledDecimal
{
get
{
return _connHandler.ConnectionOptions.TruncateScaledDecimal;
}
}

internal SqlInternalTransaction CurrentTransaction
{
get
Expand Down Expand Up @@ -6902,25 +6910,25 @@ private bool TryReadDecimalBits(int length, TdsParserStateObject stateObj, out i
return true;
}

internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale)
internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale, bool round)
{
if (d.Scale != newScale)
{
return SqlDecimal.AdjustScale(d, newScale - d.Scale, false /* Don't round, truncate. */);
return SqlDecimal.AdjustScale(d, newScale - d.Scale, round);
}

return d;
}

internal static decimal AdjustDecimalScale(decimal value, int newScale)
internal static decimal AdjustDecimalScale(decimal value, int newScale, bool round)
{
int oldScale = (decimal.GetBits(value)[3] & 0x00ff0000) >> 0x10;

if (newScale != oldScale)
{
SqlDecimal num = new SqlDecimal(value);

num = SqlDecimal.AdjustScale(num, newScale - oldScale, false /* Don't round, truncate. */);
num = SqlDecimal.AdjustScale(num, newScale - oldScale, round);
return num.Value;
}

Expand Down Expand Up @@ -8966,9 +8974,10 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet
// bug 49512, make sure the value matches the scale the user enters
if (!isNull)
{
bool roundDecimal = !TruncateScaledDecimal;
if (isSqlVal)
{
value = AdjustSqlDecimalScale((SqlDecimal)value, scale);
value = AdjustSqlDecimalScale((SqlDecimal)value, scale, roundDecimal);

// If Precision is specified, verify value precision vs param precision
if (precision != 0)
Expand All @@ -8981,7 +8990,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet
}
else
{
value = AdjustDecimalScale((Decimal)value, scale);
value = AdjustDecimalScale((Decimal)value, scale, roundDecimal);

SqlDecimal sqlValue = new SqlDecimal((Decimal)value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,7 @@ internal static class DbConnectionStringDefaults
internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
internal const string Certificate = "";
internal const PoolBlockingPeriod PoolBlockingPeriod = SqlClient.PoolBlockingPeriod.Auto;
internal const bool TruncateScaledDecimal = false;
}

internal static class DbConnectionOptionKeywords
Expand Down Expand Up @@ -1099,6 +1100,7 @@ internal static class DbConnectionStringKeywords
internal const string EnclaveAttestationUrl = "Enclave Attestation Url";
internal const string AttestationProtocol = "Attestation Protocol";
internal const string PoolBlockingPeriod = "PoolBlockingPeriod";
internal const string TruncateScaledDecimal = "Truncate Scaled Decimal";

// common keywords (OleDb, OracleClient, SqlClient)
internal const string DataSource = "Data Source";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed)
private int _currentRowLength;
private DataRowState _rowStateToSkip;
private IEnumerator _rowEnumerator;
private bool _truncateScaledDecimal;

private int RowNumber
{
Expand All @@ -290,6 +291,17 @@ private int RowNumber
}
}

private SqlConnection Connection
{
get { return _connection; }
set
{
var cnnStringBuilder = new SqlConnectionStringBuilder(value.ConnectionString);
_truncateScaledDecimal = cnnStringBuilder.TruncateScaledDecimal;
_connection = value;
}
}

private TdsParser _parser;
private TdsParserStateObject _stateObj;
private List<_ColumnMapping> _sortedColumnMappings;
Expand Down Expand Up @@ -333,7 +345,7 @@ public SqlBulkCopy(SqlConnection connection)
{
throw ADP.ArgumentNull("connection");
}
_connection = connection;
Connection = connection;
_columnMappings = new SqlBulkCopyColumnMappingCollection();
}

Expand Down Expand Up @@ -361,7 +373,7 @@ public SqlBulkCopy(string connectionString) : this(new SqlConnection(connectionS
{
throw ADP.ArgumentNull("connectionString");
}
_connection = new SqlConnection(connectionString);
Connection = new SqlConnection(connectionString);
_columnMappings = new SqlBulkCopyColumnMappingCollection();
_ownConnection = true;
}
Expand Down Expand Up @@ -1647,8 +1659,9 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
}

if (sqlValue.Scale != scale)
{
sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, scale);
{
bool roundDecimal = !_truncateScaledDecimal;
sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, scale, roundDecimal);
}

if (sqlValue.Precision > precision)
Expand Down
Loading

0 comments on commit d7d8e22

Please sign in to comment.