From 82682c76b9b8133cf200db0d6cd1313d26a662ac Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 17 Dec 2019 13:38:16 -0600 Subject: [PATCH 01/31] SqlBulkCopy - generics to avoid boxing --- .../src/Microsoft.Data.SqlClient.csproj | 1 + .../Data/Common/AdapterUtil.SqlClient.cs | 2 +- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 319 ++++++++++------ .../Microsoft/Data/SqlClient/SqlParameter.cs | 172 +++++---- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 353 ++++++++++-------- .../Data/SqlClient/ValueTypeConverter.cs | 44 +++ .../Microsoft.Data.SqlClient.Tests.csproj | 1 + src/ValueTypeConverterTests.cs | 43 +++ 8 files changed, 582 insertions(+), 353 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs create mode 100644 src/ValueTypeConverterTests.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index b0a0b70040..8d9d377143 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -579,6 +579,7 @@ + True True diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/AdapterUtil.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/AdapterUtil.SqlClient.cs index eb32c6f5b8..35d6f1bd6a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/AdapterUtil.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/AdapterUtil.SqlClient.cs @@ -703,7 +703,7 @@ private static void IsDirectionValid(ParameterDirection value) internal static void IsNullOrSqlType(object value, out bool isNull, out bool isSqlType) { - if ((value == null) || (value == DBNull.Value)) + if ((value == null) || (value is DBNull)) { isNull = true; isSqlType = false; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 22297b4dc7..f0f390c0c6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -857,11 +857,15 @@ private void Dispose(bool disposing) } // Unified method to read a value from the current row - private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out bool isDataFeed, out bool isNull) + private Task ReadWriteValueGenericAsync(int destRowIndex) { _SqlMetaData metadata = _sortedColumnMappings[destRowIndex]._metadata; int sourceOrdinal = _sortedColumnMappings[destRowIndex]._sourceColumnOrdinal; + bool isSqlType = false; + bool isDataFeed = false; + bool isNull = false; + switch (_rowSourceType) { case ValueSourceType.IDataReader: @@ -871,33 +875,33 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b { if (_DbDataReaderRowSource.IsDBNull(sourceOrdinal)) { - isSqlType = false; - isDataFeed = false; isNull = true; - return DBNull.Value; + var dbnull = DBNull.Value; + return WriteValueAsync(ref dbnull, destRowIndex, isSqlType, isDataFeed, isNull); } else { - isSqlType = false; - isDataFeed = true; - isNull = false; switch (_currentRowMetadata[destRowIndex].Method) { case ValueMethod.DataFeedStream: - return new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)); + var stream = new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)); + return WriteValueAsync(ref stream, destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.DataFeedText: - return new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)); + var text = new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)); + return WriteValueAsync(ref text, destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.DataFeedXml: // Only SqlDataReader supports an XmlReader // There is no GetXmlReader on DbDataReader, however if GetValue returns XmlReader we will read it as stream if it is assigned to XML field Debug.Assert(_SqlDataReaderRowSource != null, "Should not be reading row as an XmlReader if bulk copy source is not a SqlDataReader"); - return new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)); + var xml = new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)); + return WriteValueAsync(ref xml, destRowIndex, isSqlType, isDataFeed, isNull); default: Debug.Fail($"Current column is marked as being a DataFeed, but no DataFeed compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); isDataFeed = false; object columnValue = _DbDataReaderRowSource.GetValue(sourceOrdinal); ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return columnValue; + + return WriteValueAsync(ref columnValue, destRowIndex, isSqlType, isDataFeed, isNull); } } } @@ -906,36 +910,28 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b { if (_currentRowMetadata[destRowIndex].IsSqlType) { - INullable value; isSqlType = true; - isDataFeed = false; switch (_currentRowMetadata[destRowIndex].Method) { case ValueMethod.SqlTypeSqlDecimal: - value = _SqlDataReaderRowSource.GetSqlDecimal(sourceOrdinal); - break; + var value = _SqlDataReaderRowSource.GetSqlDecimal(sourceOrdinal); + return WriteValueAsync(ref value, destRowIndex, isSqlType, isDataFeed, value.IsNull); case ValueMethod.SqlTypeSqlDouble: // use cast to handle IsNull correctly because no public constructor allows it - value = (SqlDecimal)_SqlDataReaderRowSource.GetSqlDouble(sourceOrdinal); - break; + var dblValue = (SqlDecimal)_SqlDataReaderRowSource.GetSqlDouble(sourceOrdinal); + return WriteValueAsync(ref dblValue, destRowIndex, isSqlType, isDataFeed, dblValue.IsNull); case ValueMethod.SqlTypeSqlSingle: - // use cast to handle IsNull correctly because no public constructor allows it - value = (SqlDecimal)_SqlDataReaderRowSource.GetSqlSingle(sourceOrdinal); - break; + // use cast to handle value.IsNull correctly because no public constructor allows it + var singleValue = (SqlDecimal)_SqlDataReaderRowSource.GetSqlSingle(sourceOrdinal); + return WriteValueAsync(ref singleValue, destRowIndex, isSqlType, isDataFeed, singleValue.IsNull); default: Debug.Fail($"Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); - value = (INullable)_SqlDataReaderRowSource.GetSqlValue(sourceOrdinal); - break; + var sqlValue = (INullable)_SqlDataReaderRowSource.GetSqlValue(sourceOrdinal); + return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, sqlValue.IsNull); } - - isNull = value.IsNull; - return value; } else { - isSqlType = false; - isDataFeed = false; - object value = _SqlDataReaderRowSource.GetValue(sourceOrdinal); isNull = ((value == null) || (value == DBNull.Value)); if ((!isNull) && (metadata.type == SqlDbType.Udt)) @@ -949,27 +945,71 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b Debug.Assert(!(value is INullable) || !((INullable)value).IsNull, "IsDBNull returned false, but GetValue returned a null INullable"); } #endif - return value; + return WriteValueAsync(ref value, destRowIndex, isSqlType, isDataFeed, isNull); } } else { - isDataFeed = false; - - IDataReader rowSourceAsIDataReader = (IDataReader)_rowSource; + IDataReader r = (IDataReader)_rowSource; // Only use IsDbNull when streaming is enabled and only for non-SqlDataReader - if ((_enableStreaming) && (_SqlDataReaderRowSource == null) && (rowSourceAsIDataReader.IsDBNull(sourceOrdinal))) + if ((_enableStreaming) && (_SqlDataReaderRowSource == null) && (r.IsDBNull(sourceOrdinal))) { - isSqlType = false; isNull = true; - return DBNull.Value; + var dbnull = DBNull.Value; + return WriteValueAsync(ref dbnull, destRowIndex, isSqlType, isDataFeed, isNull); } else { - object columnValue = rowSourceAsIDataReader.GetValue(sourceOrdinal); - ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return columnValue; + var fieldType = r.GetFieldType(sourceOrdinal); + + var typeCode = fieldType != null && !r.IsDBNull(sourceOrdinal) + ? Type.GetTypeCode(fieldType) //TODO can be optimized out + : TypeCode.Empty; + + switch (typeCode) + { + case TypeCode.Int32: + var i = r.GetInt32(sourceOrdinal); + return WriteValueAsync(ref i, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.String: + var s = r.GetString(sourceOrdinal); + return WriteValueAsync(ref s, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Double: + var d = r.GetDouble(sourceOrdinal); + return WriteValueAsync(ref d, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Decimal: + var dc = r.GetDecimal(sourceOrdinal); + return WriteValueAsync(ref dc, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Int16: + var sh = r.GetInt16(sourceOrdinal); + return WriteValueAsync(ref sh, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Int64: + var l = r.GetInt64(sourceOrdinal); + return WriteValueAsync(ref l, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Char: + var c = r.GetChar(sourceOrdinal); + return WriteValueAsync(ref c, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Byte: + var by = r.GetByte(sourceOrdinal); + return WriteValueAsync(ref by, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Boolean: + var b = r.GetBoolean(sourceOrdinal); + return WriteValueAsync(ref b, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.DateTime: + var dt = r.GetDateTime(sourceOrdinal); + return WriteValueAsync(ref dt, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Object when fieldType == typeof(Guid): + var g = r.GetGuid(sourceOrdinal); + return WriteValueAsync(ref g, destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Object when fieldType == typeof(float): + var f = r.GetFloat(sourceOrdinal); + return WriteValueAsync(ref f, destRowIndex, isSqlType, isDataFeed, isNull); + default: + object columnValue = r.GetValue(sourceOrdinal); + ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); + return WriteValueAsync(ref columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + } } } case ValueSourceType.DataTable: @@ -979,6 +1019,7 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b Debug.Assert(sourceOrdinal < _currentRowLength, "inconsistency of length of rows from rowsource!"); isDataFeed = false; + // unfortunately this has to be boxed due to DataRow's API. object currentRowValue = _currentRow[sourceOrdinal]; ADP.IsNullOrSqlType(currentRowValue, out isNull, out isSqlType); @@ -991,7 +1032,8 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b { if (isSqlType) { - return new SqlDecimal(((SqlSingle)currentRowValue).Value); + var sqlDec = new SqlDecimal(((SqlSingle)currentRowValue).Value); + return WriteValueAsync(ref sqlDec, destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -999,16 +1041,21 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b if (!float.IsNaN(f)) { isSqlType = true; - return new SqlDecimal(f); + var sqlDec = new SqlDecimal(f); + return WriteValueAsync(ref sqlDec, destRowIndex, isSqlType, isDataFeed, isNull); + } + else + { + return WriteValueAsync(ref currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } - break; } } case ValueMethod.SqlTypeSqlDouble: { if (isSqlType) { - return new SqlDecimal(((SqlDouble)currentRowValue).Value); + var sqlValue = new SqlDecimal(((SqlDouble)currentRowValue).Value); + return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -1016,33 +1063,41 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b if (!double.IsNaN(d)) { isSqlType = true; - return new SqlDecimal(d); + var sqlValue = new SqlDecimal(d); + return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); + } + else + { + return WriteValueAsync(ref currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } - break; } } case ValueMethod.SqlTypeSqlDecimal: { if (isSqlType) { - return (SqlDecimal)currentRowValue; + var sqlValue = (SqlDecimal)currentRowValue; + return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } else { isSqlType = true; - return new SqlDecimal((decimal)currentRowValue); + var sqlValue = new SqlDecimal((decimal)currentRowValue); + return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } } default: { Debug.Fail($"Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); - break; + // If we are here then either the value is null, there was no special storage type for this column or the special storage type wasn't handled (e.g. if the currentRowValue is NaN) + return WriteValueAsync(ref currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } } } - - // If we are here then either the value is null, there was no special storage type for this column or the special storage type wasn't handled (e.g. if the currentRowValue is NaN) - return currentRowValue; + else + { + return WriteValueAsync(ref currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); + } } default: { @@ -1367,8 +1422,10 @@ private string UnquotedName(string name) return name; } - private object ValidateBulkCopyVariant(object value) + private bool ValidateBulkCopyVariantIfNeeded(ref T value, out object variantValue) { + variantValue = null; + // From the spec: // "The only acceptable types are ..." // GUID, BIGVARBINARY, BIGBINARY, BIGVARCHAR, BIGCHAR, NVARCHAR, NCHAR, BIT, INT1, INT2, INT4, INT8, @@ -1396,20 +1453,21 @@ private object ValidateBulkCopyVariant(object value) case TdsEnums.SQLDATETIMEOFFSET: if (value is INullable) { // Current limitation in the SqlBulkCopy Variant code limits BulkCopy to CLR/COM Types. - return MetaType.GetComValueFromSqlVariant(value); + variantValue = MetaType.GetComValueFromSqlVariant(value); + return true; } else { - return value; + return false; } default: throw SQL.BulkLoadInvalidVariantValue(); } } - private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, ref bool isSqlType, out bool coercedToDataFeed) + private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metadata, bool isNull, bool isSqlType) { - coercedToDataFeed = false; + bool coercedToDataFeed = false; if (isNull) { @@ -1417,11 +1475,14 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re { throw SQL.BulkLoadBulkLoadNotAllowDBNull(metadata.column); } - return value; + + return DoWriteValueAsync(ref value, col, isSqlType, coercedToDataFeed, isNull, metadata); } MetaType type = metadata.metaType; bool typeChanged = false; + object objValue = null; + SqlDecimal? decValue = null; // If the column is encrypted then we are going to transparently encrypt this column // (based on connection string setting)- Use the metaType for the underlying @@ -1446,47 +1507,49 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re { case TdsEnums.SQLNUMERICN: case TdsEnums.SQLDECIMALN: - mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); - - // Convert Source Decimal Precision and Scale to Destination Precision and Scale - // Sql decimal data could get corrupted on insert if the scale of - // the source and destination weren't the same. The BCP protocol, specifies the - // scale of the incoming data in the insert statement, we just tell the server we - // are inserting the same scale back. - SqlDecimal sqlValue; - if ((isSqlType) && (!typeChanged)) + if (typeof(T) == typeof(decimal)) { - sqlValue = (SqlDecimal)value; + decValue = new SqlDecimal(ValueTypeConverter.Convert(ref value)); + } + else if (typeof(T) == typeof(SqlDecimal)) + { + decValue = ValueTypeConverter.Convert(ref value); } else { - sqlValue = new SqlDecimal((decimal)value); + mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); + decValue = new SqlDecimal((decimal)SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false)); } - if (sqlValue.Scale != scale) + // Convert Source Decimal Precision and Scale to Destination Precision and Scale + // Sql decimal data could get corrupted on insert if the scale of + // the source and destination weren't the same. The BCP protocol, specifies the + // scale of the incoming data in the insert statement, we just tell the server we + // are inserting the same scale back. + if (decValue.Value.Scale != scale) { - sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, scale); + decValue = TdsParser.AdjustSqlDecimalScale(decValue.Value, scale); } - if (sqlValue.Precision > precision) + if (decValue.Value.Precision > precision) { try { - sqlValue = SqlDecimal.ConvertToPrecScale(sqlValue, precision, sqlValue.Scale); + decValue = SqlDecimal.ConvertToPrecScale(decValue.Value, precision, decValue.Value.Scale); } catch (SqlTruncateException) { - throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, ADP.ParameterValueOutOfRange(sqlValue)); + mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); + throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, ADP.ParameterValueOutOfRange(decValue.Value)); } } // Perf: It is more efficient to write a SqlDecimal than a decimal since we need to break it into its 'bits' when writing - value = sqlValue; isSqlType = true; typeChanged = false; // Setting this to false as SqlParameter.CoerceValue will only set it to true when converting to a CLR type - break; + break; + case TdsEnums.SQLINTN: case TdsEnums.SQLFLTN: case TdsEnums.SQLFLT4: @@ -1509,16 +1572,22 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re case TdsEnums.SQLDATETIME2: case TdsEnums.SQLDATETIMEOFFSET: mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); + typeChanged = SqlParameter.CoerceValueIfNeeded(ref value, mt, out objValue, out coercedToDataFeed); break; case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); + typeChanged = SqlParameter.CoerceValueIfNeeded(ref value, mt, out objValue, out coercedToDataFeed, false); if (!coercedToDataFeed) { // We do not need to test for TextDataFeed as it is only assigned to (N)VARCHAR(MAX) - string str = ((isSqlType) && (!typeChanged)) ? ((SqlString)value).Value : ((string)value); + string str = typeChanged + ? (string)objValue + : isSqlType + ? ValueTypeConverter.Convert(ref value).Value + : ValueTypeConverter.Convert(ref value) + ; + int maxStringLength = length / 2; if (str.Length > maxStringLength) { @@ -1535,10 +1604,10 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re throw SQL.BulkLoadStringTooLong(_destinationTableName, metadata.column, str); } } + break; case TdsEnums.SQLVARIANT: - value = ValidateBulkCopyVariant(value); - typeChanged = true; + typeChanged = ValidateBulkCopyVariantIfNeeded(ref value, out objValue); break; case TdsEnums.SQLUDT: // UDTs are sent as varbinary so we need to get the raw bytes @@ -1549,16 +1618,16 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re // in byte[] form. if (!(value is byte[])) { - value = _connection.GetBytes(value); + objValue = _connection.GetBytes(value); typeChanged = true; } break; case TdsEnums.SQLXMLTYPE: // Could be either string, SqlCachedBuffer, XmlReader or XmlDataFeed Debug.Assert((value is XmlReader) || (value is SqlCachedBuffer) || (value is string) || (value is SqlString) || (value is XmlDataFeed), "Invalid value type of Xml datatype"); - if (value is XmlReader) + if (value is XmlReader xmlReader) { - value = new XmlDataFeed((XmlReader)value); + objValue = new XmlDataFeed(xmlReader); typeChanged = true; coercedToDataFeed = true; } @@ -1568,14 +1637,6 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re Debug.Fail("Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null)); throw SQL.BulkLoadCannotConvertValue(value.GetType(), metadata.metaType, null); } - - if (typeChanged) - { - // All type changes change to CLR types - isSqlType = false; - } - - return value; } catch (Exception e) { @@ -1585,6 +1646,22 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re } throw SQL.BulkLoadCannotConvertValue(value.GetType(), metadata.metaType, e); } + + if (decValue.HasValue) + { + var dv = decValue.Value; + return WriteConvertedValue(ref dv, col, isSqlType, isNull, coercedToDataFeed, metadata); + } + else if (typeChanged) + { + // All type changes change to CLR types + isSqlType = false; + return WriteConvertedValue(ref objValue, col, isSqlType, isNull, coercedToDataFeed, metadata); + } + else + { + return WriteConvertedValue(ref value, col, isSqlType, isNull, coercedToDataFeed, metadata); + } } /// @@ -2124,32 +2201,50 @@ private bool FireRowsCopiedEvent(long rowsCopied) // When _isAsyncBulkCopy == false: Writes are purely sync. This method return null at the end. private Task ReadWriteColumnValueAsync(int col) { - bool isSqlType; - bool isDataFeed; - bool isNull; - object value = GetValueFromSourceRow(col, out isSqlType, out isDataFeed, out isNull); //this will return Task/null in future: as rTask + var writeTask = ReadWriteValueGenericAsync(col); //this will return Task/null in future: as rTask + return writeTask; + } + + private Task WriteValueAsync(ref T value, int col, bool isSqlType, bool isDataFeed, bool isNull) + { _SqlMetaData metadata = _sortedColumnMappings[col]._metadata; - if (!isDataFeed) + if (isDataFeed) { - value = ConvertValue(value, metadata, isNull, ref isSqlType, out isDataFeed); + //nothing to convert, skip straight to write + return DoWriteValueAsync(ref value, col, isSqlType, isDataFeed, isNull, metadata); + } + else + { + return ConvertWriteValueAsync(ref value, col, metadata, isNull, isSqlType); + } + } - // If column encryption is requested via connection string option, perform encryption here - if (!isNull && // if value is not NULL - metadata.isEncrypted) - { // If we are transparently encrypting - Debug.Assert(_parser.ShouldEncryptValuesForBulkCopy()); - value = _parser.EncryptColumnValue(value, metadata, metadata.column, _stateObj, isDataFeed, isSqlType); - isSqlType = false; // Its not a sql type anymore - } + private Task WriteConvertedValue(ref T value, int col, bool isSqlType, bool isNull, bool isDatafeed, _SqlMetaData metadata) + { + // If column encryption is requested via connection string option, perform encryption here + if (!isNull && // if value is not NULL + metadata.isEncrypted) + { // If we are transparently encrypting + Debug.Assert(_parser.ShouldEncryptValuesForBulkCopy()); + var bytesValue = _parser.EncryptColumnValue(ref value, metadata, metadata.column, _stateObj, isDatafeed, isSqlType); + isSqlType = false; // Its not a sql type anymore + + return DoWriteValueAsync(ref bytesValue, col, isSqlType, isDatafeed, isNull, metadata); } + else + { + return DoWriteValueAsync(ref value, col, isSqlType, isDatafeed, isNull, metadata); + } + } - //write part + private Task DoWriteValueAsync(ref T value, int col, bool isSqlType, bool isDataFeed, bool isNull, _SqlMetaData metadata) + { Task writeTask = null; if (metadata.type != SqlDbType.Variant) { //this is the most common path - writeTask = _parser.WriteBulkCopyValue(value, metadata, _stateObj, isSqlType, isDataFeed, isNull); //returns Task/Null + writeTask = _parser.WriteBulkCopyValue(ref value, metadata, _stateObj, isSqlType, isDataFeed, isNull); //returns Task/Null } else { @@ -2161,17 +2256,17 @@ private Task ReadWriteColumnValueAsync(int col) variantInternalType = _SqlDataReaderRowSource.GetVariantInternalStorageType(_sortedColumnMappings[col]._sourceColumnOrdinal); } - if (variantInternalType == SqlBuffer.StorageType.DateTime2) + if (variantInternalType == SqlBuffer.StorageType.DateTime2 && value is DateTime) { - _parser.WriteSqlVariantDateTime2(((DateTime)value), _stateObj); + _parser.WriteSqlVariantDateTime2(ValueTypeConverter.Convert(ref value), _stateObj); } - else if (variantInternalType == SqlBuffer.StorageType.Date) + else if (variantInternalType == SqlBuffer.StorageType.Date && value is DateTime d) { - _parser.WriteSqlVariantDate(((DateTime)value), _stateObj); + _parser.WriteSqlVariantDate(ValueTypeConverter.Convert(ref value), _stateObj); } else { - writeTask = _parser.WriteSqlVariantDataRowValue(value, _stateObj); //returns Task/Null + writeTask = _parser.WriteSqlVariantDataRowValue(ref value, _stateObj); //returns Task/Null } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index 77a15904ae..4483e232d5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -995,117 +995,133 @@ object ICloneable.Clone() } // Coerced Value is also used in SqlBulkCopy.ConvertValue(object value, _SqlMetaData metadata) + internal static object CoerceValue(object value, MetaType destinationType, out bool coercedToDataFeed, out bool typeChanged, bool allowStreaming = true) + { + typeChanged = CoerceValueIfNeeded(ref value, destinationType, out var objValue, out coercedToDataFeed, allowStreaming); + + return typeChanged ? objValue : value; + } + + internal static bool CoerceValueIfNeeded(ref T value, MetaType destinationType, out object objValue, out bool coercedToDataFeed, bool allowStreaming = true) { Debug.Assert(!(value is DataFeed), "Value provided should not already be a data feed"); Debug.Assert(!ADP.IsNull(value), "Value provided should not be null"); Debug.Assert(null != destinationType, "null destinationType"); coercedToDataFeed = false; - typeChanged = false; - Type currentType = value.GetType(); + objValue = null; + Type currentType = typeof(T) == typeof(object) + ? value.GetType() // only call GetType if we know boxing has already occurred. + : typeof(T); - if ((typeof(object) != destinationType.ClassType) && - (currentType != destinationType.ClassType) && - ((currentType != destinationType.SqlType) || (SqlDbType.Xml == destinationType.SqlDbType))) - { // Special case for Xml types (since we need to convert SqlXml into a string) - try + if (typeof(object) == destinationType.ClassType || + currentType == destinationType.ClassType || + currentType == destinationType.SqlType && SqlDbType.Xml != destinationType.SqlDbType) + // Special case for Xml types (since we need to convert SqlXml into a string) + { + return false; + } + + try + { + // Assume that the type changed + if ((typeof(string) == destinationType.ClassType)) { - // Assume that the type changed - typeChanged = true; - if ((typeof(string) == destinationType.ClassType)) + // For Xml data, destination Type is always string + if (typeof(SqlXml) == currentType) { - // For Xml data, destination Type is always string - if (typeof(SqlXml) == currentType) - { - value = MetaType.GetStringFromXml((XmlReader)(((SqlXml)value).CreateReader())); - } - else if (typeof(SqlString) == currentType) - { - typeChanged = false; // Do nothing - } - else if (typeof(XmlReader).IsAssignableFrom(currentType)) - { - if (allowStreaming) - { - coercedToDataFeed = true; - value = new XmlDataFeed((XmlReader)value); - } - else - { - value = MetaType.GetStringFromXml((XmlReader)value); - } - } - else if (typeof(char[]) == currentType) - { - value = new string((char[])value); - } - else if (typeof(SqlChars) == currentType) - { - value = new string(((SqlChars)value).Value); - } - else if (value is TextReader && allowStreaming) + var xmlValue = ValueTypeConverter.Convert(ref value); + objValue = MetaType.GetStringFromXml(xmlValue.CreateReader()); + } + else if (typeof(SqlString) == currentType) + { + return false; + } + else if (value is XmlReader xmlReader) + { + if (allowStreaming) { coercedToDataFeed = true; - value = new TextDataFeed((TextReader)value); + objValue = new XmlDataFeed(xmlReader); } else { - value = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); + objValue = MetaType.GetStringFromXml(xmlReader); } } - else if ((DbType.Currency == destinationType.DbType) && (typeof(string) == currentType)) - { - value = decimal.Parse((string)value, NumberStyles.Currency, (IFormatProvider)null); - } - else if ((typeof(SqlBytes) == currentType) && (typeof(byte[]) == destinationType.ClassType)) + else if (typeof(char[]) == currentType) { - typeChanged = false; // Do nothing + var charArrayValue = ValueTypeConverter.Convert(ref value); + objValue = new string(charArrayValue); } - else if ((typeof(string) == currentType) && (SqlDbType.Time == destinationType.SqlDbType)) + else if (typeof(SqlChars) == currentType) { - value = TimeSpan.Parse((string)value); + var sqlCharsValue = ValueTypeConverter.Convert(ref value); + objValue = new string(sqlCharsValue.Value); } - else if ((typeof(string) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) - { - value = DateTimeOffset.Parse((string)value, (IFormatProvider)null); - } - else if ((typeof(DateTime) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) - { - value = new DateTimeOffset((DateTime)value); - } - else if (TdsEnums.SQLTABLE == destinationType.TDSType && ( - value is DataTable || - value is DbDataReader || - value is System.Collections.Generic.IEnumerable)) - { - // no conversion for TVPs. - typeChanged = false; - } - else if (destinationType.ClassType == typeof(byte[]) && value is Stream && allowStreaming) + else if (value is TextReader tr && allowStreaming) { coercedToDataFeed = true; - value = new StreamDataFeed((Stream)value); + objValue = new TextDataFeed(tr); } else { - value = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); + objValue = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); } } - catch (Exception e) + else if ((DbType.Currency == destinationType.DbType) && (typeof(string) == currentType)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - - throw ADP.ParameterConversionFailed(value, destinationType.ClassType, e); + objValue = decimal.Parse(ValueTypeConverter.Convert(ref value), NumberStyles.Currency, (IFormatProvider)null); + } + else if ((typeof(SqlBytes) == currentType) && (typeof(byte[]) == destinationType.ClassType)) + { + return false;// Do nothing + } + else if ((typeof(string) == currentType) && (SqlDbType.Time == destinationType.SqlDbType)) + { + objValue = TimeSpan.Parse(ValueTypeConverter.Convert(ref value)); + } + else if ((typeof(string) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) + { + objValue = DateTimeOffset.Parse(ValueTypeConverter.Convert(ref value), (IFormatProvider)null); + } + else if ((typeof(DateTime) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) + { + objValue = new DateTimeOffset(ValueTypeConverter.Convert(ref value)); + } + else if (TdsEnums.SQLTABLE == destinationType.TDSType && ( + value is DataTable || + value is DbDataReader || + value is System.Collections.Generic.IEnumerable)) + { + // no conversion for TVPs. + return false; + } + else if (destinationType.ClassType == typeof(byte[]) && allowStreaming && value is Stream stream) + { + coercedToDataFeed = true; + objValue = new StreamDataFeed(stream); + } + else + { + objValue = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); } } + catch (Exception e) + { + if (!ADP.IsCatchableExceptionType(e)) + { + throw; + } + + throw ADP.ParameterConversionFailed(value, destinationType.ClassType, e); + } Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); - Debug.Assert(value.GetType() == currentType ^ typeChanged, "Incorrect value for typeChanged"); - return value; + Debug.Assert(value.GetType() != currentType, "Incorrect value for typeChanged"); + + return true; } internal void FixStreamDataForNonPLP() diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index ccb91ccef5..9c937120eb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. + // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -288,7 +288,7 @@ internal SqlStatistics Statistics internal int IncrementNonTransactedOpenResultCount() { // IMPORTANT - this increments the connection wide open result count for all - // operations not under a transaction! Do not call if you intend to modify the + // operations not under a transaction! Do not call if you intend to modify the // count for a transaction! Debug.Assert(_nonTransactedOpenResultCount >= 0, "Unexpected result count state"); int result = Interlocked.Increment(ref _nonTransactedOpenResultCount); @@ -298,7 +298,7 @@ internal int IncrementNonTransactedOpenResultCount() internal void DecrementNonTransactedOpenResultCount() { // IMPORTANT - this decrements the connection wide open result count for all - // operations not under a transaction! Do not call if you intend to modify the + // operations not under a transaction! Do not call if you intend to modify the // count for a transaction! Interlocked.Decrement(ref _nonTransactedOpenResultCount); Debug.Assert(_nonTransactedOpenResultCount >= 0, "Unexpected result count state"); @@ -381,7 +381,7 @@ internal void Connect( // If we are pooling, check to see if we were processing an // alias which has changed, which means we need to clean out // the pool. See Webdata 104293. - // This should not apply to routing, as it is not an alias change, routed connection + // This should not apply to routing, as it is not an alias change, routed connection // should still use VNN of AlwaysOn cluster as server for pooling purposes. connHandler.PoolGroupProviderInfo.AliasCheck(serverInfo.PreRoutingServerName == null ? serverInfo.ResolvedServerName : serverInfo.PreRoutingServerName); @@ -735,7 +735,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus if (payload[0] == 0xaa) { // If the first byte is 0xAA, we are connecting to a 6.5 or earlier server, which - // is not supported. + // is not supported. throw SQL.InvalidSQLServerVersionUnknown(); } @@ -832,9 +832,9 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus if (encrypt && !integratedSecurity) { - // optimization: in case of SQL Authentication and encryption, set SNI_SSL_IGNORE_CHANNEL_BINDINGS to let SNI + // optimization: in case of SQL Authentication and encryption, set SNI_SSL_IGNORE_CHANNEL_BINDINGS to let SNI // know that it does not need to allocate/retrieve the Channel Bindings from the SSL context. - // This applies to Native SNI + // This applies to Native SNI info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; } @@ -1179,7 +1179,7 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, bool calle } try { - // the following handler will throw an exception or generate a warning event + // the following handler will throw an exception or generate a warning event _connHandler.OnError(exception, breakConnection); } finally @@ -1268,8 +1268,8 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) len -= iColon; /* The error message should come back in the following format: "TCP Provider: MESSAGE TEXT" - If the message is received on a Win9x OS, the error message will not contain MESSAGE TEXT - If we get an error message with no message text, just return the entire message otherwise + If the message is received on a Win9x OS, the error message will not contain MESSAGE TEXT + If we get an error message with no message text, just return the entire message otherwise return just the message text. */ if (len > 0) @@ -1759,7 +1759,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead if (_connHandler != null) connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref // We are omitting checks for error.Class in the code below (see processing of INFO) since we know (and assert) that error class - // error.Class < TdsEnums.MIN_ERROR_CLASS for info message. + // error.Class < TdsEnums.MIN_ERROR_CLASS for info message. // Also we know that TdsEnums.MIN_ERROR_CLASS SpinWait.SpinUntil(() => !stateObj._attentionSending); private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange sqlEnvChange) @@ -3968,7 +3968,7 @@ internal bool TryProcessTceCryptoMetadata(TdsParserStateObject stateObj, } } - // Read Encryption Type. + // Read Encryption Type. byte encryptionType; if (!stateObj.TryReadByte(out encryptionType)) { @@ -4070,9 +4070,9 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) // If we failed, it is quite possible this is because certain culture id's // were removed in Win2k and beyond, however Sql Server still supports them. - // In this case we will mask off the sort id (the leading 1). If that fails, - // or we have a culture id other than the cases below, we throw an error and - // throw away the rest of the results. + // In this case we will mask off the sort id (the leading 1). If that fails, + // or we have a culture id other than the cases below, we throw an error and + // throw away the rest of the results. // Sometimes GetCultureInfo will return CodePage 0 instead of throwing. // This should be treated as an error and functionality switches into the following logic. @@ -4324,7 +4324,7 @@ internal bool TryReadCipherInfoEntry(TdsParserStateObject stateObj, out SqlTceCi byte byteValue; int length; - // Read the length of encrypted CEK + // Read the length of encrypted CEK if (!stateObj.TryReadUInt16(out shortValue)) { return false; @@ -4434,7 +4434,7 @@ internal bool TryProcessMetaData(int cColumns, TdsParserStateObject stateObj, ou { Debug.Assert(cColumns > 0, "should have at least 1 column in metadata!"); - // Read the cipher info table first + // Read the cipher info table first SqlTceCipherInfoTable? cipherTable = null; if (IsColumnEncryptionSupported) { @@ -4702,7 +4702,7 @@ private bool TryCommonProcessMetaData(TdsParserStateObject stateObj, _SqlMetaDat } } - // Read the column name + // Read the column name if (!stateObj.TryReadByte(out byteLen)) { return false; @@ -5411,7 +5411,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt byte denormalizedScale = md.baseTI.scale; Debug.Assert(false == md.baseTI.isEncrypted, "Double encryption detected"); - //DEVNOTE: When modifying the following routines (for deserialization) please pay attention to + //DEVNOTE: When modifying the following routines (for deserialization) please pay attention to // deserialization code in DecryptWithKey () method and modify it accordingly. switch (tdsType) { @@ -5685,7 +5685,7 @@ internal bool TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, int length, T length = int.MaxValue; } - //DEVNOTE: When modifying the following routines (for deserialization) please pay attention to + //DEVNOTE: When modifying the following routines (for deserialization) please pay attention to // deserialization code in DecryptWithKey () method and modify it accordingly. switch (tdsType) { @@ -6275,10 +6275,10 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars // Special case data type correction for SqlMoney inside a SqlVariant. if ((TdsEnums.SQLNUMERICN == mt.TDSType) && (8 == length)) { - // The caller will coerce all SqlTypes to native CLR types, which means SqlMoney will - // coerce to decimal/SQLNUMERICN (via SqlMoney.Value call). In the case where the original - // value was SqlMoney the caller will also pass in the metadata length for the SqlMoney type - // which is 8 bytes. To honor the intent of the caller here we coerce this special case + // The caller will coerce all SqlTypes to native CLR types, which means SqlMoney will + // coerce to decimal/SQLNUMERICN (via SqlMoney.Value call). In the case where the original + // value was SqlMoney the caller will also pass in the metadata length for the SqlMoney type + // which is 8 bytes. To honor the intent of the caller here we coerce this special case // input back to SqlMoney from decimal/SQLNUMERICN. mt = MetaType.GetMetaTypeFromValue(new SqlMoney((decimal)value)); } @@ -6393,7 +6393,8 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars { stateObj.WriteByte(mt.Precision); //propbytes: precision stateObj.WriteByte((byte)((decimal.GetBits((decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale - WriteDecimal((decimal)value, stateObj); + var d = (decimal)value; + WriteDecimal(ref d, stateObj); break; } @@ -6428,10 +6429,10 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars // Therefore the sql_variant value must not include the MaxLength. This is the major difference // between this method and WriteSqlVariantValue above. // - internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObj, bool canAccumulate = true) + internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject stateObj, bool canAccumulate = true) { // handle null values - if ((null == value) || (DBNull.Value == value)) + if ((null == value) || typeof(T) == typeof(DBNull)) { WriteInt(TdsEnums.FIXEDNULL, stateObj); return null; @@ -6440,46 +6441,46 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta MetaType metatype = MetaType.GetMetaTypeFromValue(value); int length = 0; - if (metatype.IsAnsiType) + if (metatype.IsAnsiType && value is string) { - length = GetEncodingCharLength((string)value, length, 0, _defaultEncoding); + length = GetEncodingCharLength(ValueTypeConverter.Convert(ref value), length, 0, _defaultEncoding); } switch (metatype.TDSType) { case TdsEnums.SQLFLT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteFloat((float)value, stateObj); + WriteFloat(ValueTypeConverter.Convert(ref value), stateObj); break; case TdsEnums.SQLFLT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteDouble((double)value, stateObj); + WriteDouble(ValueTypeConverter.Convert(ref value), stateObj); break; case TdsEnums.SQLINT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteLong((long)value, stateObj); + WriteLong(ValueTypeConverter.Convert(ref value), stateObj); break; case TdsEnums.SQLINT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteInt((int)value, stateObj); + WriteInt(ValueTypeConverter.Convert(ref value), stateObj); break; case TdsEnums.SQLINT2: WriteSqlVariantHeader(4, metatype.TDSType, metatype.PropBytes, stateObj); - WriteShort((short)value, stateObj); + WriteShort(ValueTypeConverter.Convert(ref value), stateObj); break; case TdsEnums.SQLINT1: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); - stateObj.WriteByte((byte)value); + stateObj.WriteByte(ValueTypeConverter.Convert(ref value)); break; case TdsEnums.SQLBIT: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); - if ((bool)value == true) + if (ValueTypeConverter.Convert(ref value)) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -6488,7 +6489,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLBIGVARBINARY: { - byte[] b = (byte[])value; + byte[] b = ValueTypeConverter.Convert(ref value); length = b.Length; WriteSqlVariantHeader(4 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6498,7 +6499,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLBIGVARCHAR: { - string s = (string)value; + string s = ValueTypeConverter.Convert(ref value); length = s.Length; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6510,7 +6511,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLUNIQUEID: { - System.Guid guid = (System.Guid)value; + Guid guid = ValueTypeConverter.Convert(ref value); Span b = stackalloc byte[16]; FillGuidBytes(guid, b); @@ -6523,7 +6524,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLNVARCHAR: { - string s = (string)value; + string s = ValueTypeConverter.Convert(ref value); length = s.Length * 2; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6538,7 +6539,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLDATETIME: { - TdsDateTime dt = MetaType.FromDateTime((DateTime)value, 8); + TdsDateTime dt = MetaType.FromDateTime(ValueTypeConverter.Convert(ref value), 8); WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); WriteInt(dt.days, stateObj); @@ -6549,7 +6550,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLMONEY: { WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteCurrency((decimal)value, 8, stateObj); + WriteCurrency(ValueTypeConverter.Convert(ref value), 8, stateObj); break; } @@ -6557,21 +6558,22 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta { WriteSqlVariantHeader(21, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Precision); //propbytes: precision - stateObj.WriteByte((byte)((decimal.GetBits((decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale - WriteDecimal((decimal)value, stateObj); + var decValue = ValueTypeConverter.Convert(ref value); + stateObj.WriteByte((byte)((decimal.GetBits(decValue)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale + WriteDecimal(ref decValue, stateObj); break; } case TdsEnums.SQLTIME: WriteSqlVariantHeader(8, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale - WriteTime((TimeSpan)value, metatype.Scale, 5, stateObj); + WriteTime(ValueTypeConverter.Convert(ref value), metatype.Scale, 5, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: WriteSqlVariantHeader(13, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale - WriteDateTimeOffset((DateTimeOffset)value, metatype.Scale, 10, stateObj); + WriteDateTimeOffset(ValueTypeConverter.Convert(ref value), metatype.Scale, 10, stateObj); break; default: @@ -6992,7 +6994,7 @@ struct { return bytes; } - private void WriteDecimal(decimal value, TdsParserStateObject stateObj) + private void WriteDecimal(ref decimal value, TdsParserStateObject stateObj) { stateObj._decimalBits = decimal.GetBits(value); Debug.Assert(null != stateObj._decimalBits, "decimalBits should be filled in at TdsExecuteRPC time"); @@ -7410,7 +7412,7 @@ private static int StateValueLength(int dataLen) initialLength += 1 /* StateId*/ + StateValueLength(reconnectData._initialState[i].Length); } } - int currentLength = 0; // sizeof(DWORD) - length itself + int currentLength = 0; // sizeof(DWORD) - length itself currentLength += 1 + 2 * (reconnectData._initialDatabase == reconnectData._database ? 0 : TdsParserStaticMethods.NullAwareStringLength(reconnectData._database)); currentLength += 1 + 2 * (reconnectData._initialLanguage == reconnectData._language ? 0 : TdsParserStaticMethods.NullAwareStringLength(reconnectData._language)); currentLength += (reconnectData._collation != null && !SqlCollation.AreSame(reconnectData._collation, reconnectData._initialCollation)) ? 6 : 1; @@ -7839,7 +7841,7 @@ internal void TdsLogin(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures 25) fNoNBCAndSparse:1, // set if client does not support NBC and Sparse column 26) fUserInstance:1, // This connection wants to connect to a SQL "user instance" 27) fUnknownCollationHandling:1, // This connection can handle unknown collation correctly. - 28) fExtension:1 // Extensions are used + 28) fExtension:1 // Extensions are used 32 - total */ @@ -8264,11 +8266,11 @@ bool isDelegateControlRequest // Promote, Commit and Rollback requests for // delegated transactions often happen while there is an open result - // set, so we need to handle them by using a different MARS session, + // set, so we need to handle them by using a different MARS session, // otherwise we'll write on the physical state objects while someone - // else is using it. When we don't have MARS enabled, we need to + // else is using it. When we don't have MARS enabled, we need to // lock the physical state object to synchronize its use at least - // until we increment the open results count. Once it's been + // until we increment the open results count. Once it's been // incremented the delegated transaction requests will fail, so they // won't stomp on anything. @@ -8287,7 +8289,7 @@ bool isDelegateControlRequest // Temporarily disable async writes _asyncWrite = false; - // This validation step MUST be done after locking the connection to guarantee we don't + // This validation step MUST be done after locking the connection to guarantee we don't // accidentally execute after the transaction has completed on a different thread. if (!isDelegateControlRequest) { @@ -8335,8 +8337,8 @@ bool isDelegateControlRequest // Only assign the passed in transaction if it is not equal to the current transaction. // And, if it is not equal, the current actually should be null. Anything else - // is a unexpected state. The concern here is mainly for the mixed use of - // T-SQL and API transactions. + // is a unexpected state. The concern here is mainly for the mixed use of + // T-SQL and API transactions. // Expected states: // 1) _pendingTransaction = null, _currentTransaction = null, non null transaction @@ -8506,11 +8508,11 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques // Promote, Commit and Rollback requests for // delegated transactions often happen while there is an open result - // set, so we need to handle them by using a different MARS session, + // set, so we need to handle them by using a different MARS session, // otherwise we'll write on the physical state objects while someone - // else is using it. When we don't have MARS enabled, we need to - // lock the physical state object to synchronize it's use at least - // until we increment the open results count. Once it's been + // else is using it. When we don't have MARS enabled, we need to + // lock the physical state object to synchronize it's use at least + // until we increment the open results count. Once it's been // incremented the delegated transaction requests will fail, so they // won't stomp on anything. @@ -8538,7 +8540,7 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques throw ADP.ClosedConnectionError(); } - // This validation step MUST be done after locking the connection to guarantee we don't + // This validation step MUST be done after locking the connection to guarantee we don't // accidentally execute after the transaction has completed on a different thread. _connHandler.CheckEnlistedTransactionBinding(); @@ -8562,7 +8564,7 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques { Debug.Assert(!sync, "Should not have gotten a Task when writing in sync mode"); - // Need to wait for flush - continuation will unlock the connection + // Need to wait for flush - continuation will unlock the connection bool taskReleaseConnectionLock = releaseConnectionLock; releaseConnectionLock = false; return executeTask.ContinueWith( @@ -8636,11 +8638,11 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo // Promote, Commit and Rollback requests for // delegated transactions often happen while there is an open result - // set, so we need to handle them by using a different MARS session, + // set, so we need to handle them by using a different MARS session, // otherwise we'll write on the physical state objects while someone - // else is using it. When we don't have MARS enabled, we need to + // else is using it. When we don't have MARS enabled, we need to // lock the physical state object to synchronize its use at least - // until we increment the open results count. Once it's been + // until we increment the open results count. Once it's been // incremented the delegated transaction requests will fail, so they // won't stomp on anything. @@ -8658,7 +8660,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo throw ADP.ClosedConnectionError(); } - // This validation step MUST be done after locking the connection to guarantee we don't + // This validation step MUST be done after locking the connection to guarantee we don't // accidentally execute after the transaction has completed on a different thread. if (firstCall) { @@ -8986,7 +8988,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet { if (isSqlVal) { - serializedValue = SerializeUnencryptedSqlValue(value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj); + serializedValue = SerializeUnencryptedSqlValue(ref value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj); } else { @@ -9306,13 +9308,13 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet { if (isSqlVal) { - writeParamTask = WriteSqlValue(value, mt, actualSize, codePageByteSize, param.Offset, stateObj); + writeParamTask = WriteSqlValue(ref value, mt, actualSize, codePageByteSize, param.Offset, stateObj); } else { // for codePageEncoded types, WriteValue simply expects the number of characters // For plp types, we also need the encoded byte size - writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); + writeParamTask = WriteValue(ref value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); } } @@ -9348,7 +9350,7 @@ private void TDSExecuteRPCParameterSetupWriteCompletion(SqlCommand cmd, _SqlRPC[ ); } - // This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter + // This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter private void TDSExecuteRPCParameterSetupFlushCompletion(TdsParserStateObject stateObj, TaskCompletionSource completion, Task execFlushTask, bool taskReleaseConnectionLock) { execFlushTask.ContinueWith(tsk => ExecuteFlushTaskCallback(tsk, stateObj, completion, taskReleaseConnectionLock), TaskScheduler.Default); @@ -9854,7 +9856,7 @@ internal Task WriteBulkCopyDone(TdsParserStateObject stateObj) } /// - /// Loads the column encryptions keys into cache. This will read the master key info, + /// Loads the column encryptions keys into cache. This will read the master key info, /// decrypt the CEK and keep it ready for encryption. /// /// @@ -10031,7 +10033,7 @@ internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int coun // read user type - 4 bytes Yukon, 2 backwards WriteInt(0x0, stateObj); - // Write the flags + // Write the flags ushort flags; flags = (ushort)(md.Updatability << 2); flags |= md.IsNullable ? TdsEnums.Nullable : (ushort)0; @@ -10115,10 +10117,10 @@ internal bool ShouldEncryptValuesForBulkCopy() } /// - /// Encrypts a column value (for SqlBulkCopy) + /// Encrypts a column value (for SqlBulkCopy) /// /// - internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) + internal byte[] EncryptColumnValue(ref T value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) { Debug.Assert(IsColumnEncryptionSupported, "Server doesn't support encryption, yet we received encryption metadata"); Debug.Assert(ShouldEncryptValuesForBulkCopy(), "Encryption attempted when not requested"); @@ -10137,13 +10139,16 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin // For some datatypes, engine does truncation before storing the value. (For example, when // trying to insert a varbinary(7000) into a varbinary(3000) column). Since we encrypt the // column values, engine has no way to tell the size of the plaintext datatype. Therefore, - // we truncate the values based on target column sizes here before encrypting them. This + // we truncate the values based on target column sizes here before encrypting them. This // truncation is only needed if we exceed the max column length or if the target column is // not a blob type (eg. varbinary(max)). The actual work of truncating the column happens - // when we normalize and serialize the data buffers. The serialization routine expects us + // when we normalize and serialize the data buffers. The serialization routine expects us // to report the size of data to be copied out (for serialization). If we underreport the // size, truncation will happen for us! - actualLengthInBytes = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; + actualLengthInBytes = (isSqlType) + ? ValueTypeConverter.Convert(ref value).Length + : ValueTypeConverter.Convert(ref value).Length; + if (metadata.baseTI.length > 0 && actualLengthInBytes > metadata.baseTI.length) { @@ -10163,7 +10168,10 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin ThrowUnsupportedCollationEncountered(null); // stateObject only when reading } - string stringValue = (isSqlType) ? ((SqlString)value).Value : (string)value; + string stringValue = (isSqlType) + ? ValueTypeConverter.Convert(ref value).Value + : ValueTypeConverter.Convert(ref value); + actualLengthInBytes = _defaultEncoding.GetByteCount(stringValue); // If the string length is > max length, then use the max length (see comments above) @@ -10177,7 +10185,10 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: - actualLengthInBytes = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; + actualLengthInBytes = (isSqlType + ? ValueTypeConverter.Convert(ref value).Value.Length + : ValueTypeConverter.Convert(ref value).Length) + * 2; if (metadata.baseTI.length > 0 && actualLengthInBytes > metadata.baseTI.length) @@ -10196,7 +10207,7 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin if (isSqlType) { // SqlType - serializedValue = SerializeUnencryptedSqlValue(value, + serializedValue = SerializeUnencryptedSqlValue(ref value, metadata.baseTI.metaType, actualLengthInBytes, offset: 0, @@ -10222,7 +10233,7 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin _connHandler.ConnectionOptions.DataSource); } - internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) + internal Task WriteBulkCopyValue(ref T value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) { Debug.Assert(!isSqlType || value is INullable, "isSqlType is true, but value can not be type cast to an INullable"); Debug.Assert(!isDataFeed ^ value is DataFeed, "Incorrect value for isDataFeed"); @@ -10260,6 +10271,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars MetaType metatype = metadata.metaType; int ccb = 0; int ccbStringBytes = 0; + object objValue = null; if (isNull) { @@ -10287,7 +10299,9 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: - ccb = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; + ccb = (isSqlType) + ? ValueTypeConverter.Convert(ref value).Length + : ValueTypeConverter.Convert(ref value).Length; break; case TdsEnums.SQLUNIQUEID: ccb = GUID_SIZE; @@ -10303,11 +10317,11 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars string stringValue = null; if (isSqlType) { - stringValue = ((SqlString)value).Value; + stringValue = ValueTypeConverter.Convert(ref value).Value; } else { - stringValue = (string)value; + stringValue = ValueTypeConverter.Convert(ref value); } ccb = stringValue.Length; @@ -10316,15 +10330,21 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: - ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; + ccb = (isSqlType + ? ValueTypeConverter.Convert(ref value).Value.Length + : ValueTypeConverter.Convert(ref value).Length + ) * 2; break; case TdsEnums.SQLXMLTYPE: // Value here could be string or XmlReader - if (value is XmlReader) + if (value is XmlReader xr) { - value = MetaType.GetStringFromXml((XmlReader)value); + objValue = MetaType.GetStringFromXml(xr); } - ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; + ccb = (isSqlType + ? ValueTypeConverter.Convert(ref value).Value.Length + : ValueTypeConverter.Convert(ref value).Length + ) * 2; break; default: @@ -10372,11 +10392,16 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars if (isSqlType) { - internalWriteTask = WriteSqlValue(value, metatype, ccb, ccbStringBytes, 0, stateObj); + internalWriteTask = WriteSqlValue(ref value, metatype, ccb, ccbStringBytes, 0, stateObj); } else if (metatype.SqlDbType != SqlDbType.Udt || metatype.IsLong) { - internalWriteTask = WriteValue(value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed); + // we only have to consider a conversion from above in this case. + + internalWriteTask = objValue != null + ? WriteValue(ref objValue, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed) + : WriteValue(ref value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed) + ; if ((internalWriteTask == null) && (_asyncWrite)) { internalWriteTask = stateObj.WaitForAccumulatedWrites(); @@ -10386,7 +10411,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars else { WriteShort(ccb, stateObj); - internalWriteTask = stateObj.WriteByteArray((byte[])value, ccb, 0); + internalWriteTask = stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value), ccb, 0); } #if DEBUG @@ -10678,7 +10703,7 @@ private bool IsBOMNeeded(MetaType type, object value) return false; } - private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaType type, int actualLength, TdsParserStateObject stateObj, bool isDataFeed) + private Task GetTerminationTask(Task unterminatedWriteTask, MetaType type, int actualLength, TdsParserStateObject stateObj, bool isDataFeed) { if (type.IsPlp && ((actualLength > 0) || isDataFeed)) { @@ -10699,16 +10724,16 @@ private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaTy } - private Task WriteSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) + private Task WriteSqlValue(ref T value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) { return GetTerminationTask( - WriteUnterminatedSqlValue(value, type, actualLength, codePageByteSize, offset, stateObj), - value, type, actualLength, stateObj, false); + WriteUnterminatedSqlValue(ref value, type, actualLength, codePageByteSize, offset, stateObj), + type, actualLength, stateObj, false); } // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) + private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) { Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) || (value is INullable && !((INullable)value).IsNull)), @@ -10719,11 +10744,11 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat(((SqlSingle)value).Value, stateObj); + WriteFloat(ValueTypeConverter.Convert(ref value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble(((SqlDouble)value).Value, stateObj); + WriteDouble(ValueTypeConverter.Convert(ref value).Value, stateObj); } break; @@ -10739,12 +10764,12 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe if (value is SqlBinary) { - return stateObj.WriteByteArray(((SqlBinary)value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, canAccumulate: false); } else { Debug.Assert(value is SqlBytes); - return stateObj.WriteByteArray(((SqlBytes)value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, canAccumulate: false); } } @@ -10752,11 +10777,11 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe { Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); Span b = stackalloc byte[16]; - SqlGuid sqlGuid = (SqlGuid)value; + SqlGuid sqlGuid = ValueTypeConverter.Convert(ref value); if (sqlGuid.IsNull) { - b.Clear(); // this is needed because initlocals may be supressed in framework assemblies meaning the memory is not automaticaly zeroed + b.Clear(); // this is needed because initlocals may be supressed in framework assemblies meaning the memory is not automaticaly zeroed } else { @@ -10770,7 +10795,7 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); - if (((SqlBoolean)value).Value == true) + if (ValueTypeConverter.Convert(ref value).Value == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -10780,17 +10805,17 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLINTN: if (type.FixedLength == 1) - stateObj.WriteByte(((SqlByte)value).Value); + stateObj.WriteByte(ValueTypeConverter.Convert(ref value).Value); else if (type.FixedLength == 2) - WriteShort(((SqlInt16)value).Value, stateObj); + WriteShort(ValueTypeConverter.Convert(ref value).Value, stateObj); else if (type.FixedLength == 4) - WriteInt(((SqlInt32)value).Value, stateObj); + WriteInt(ValueTypeConverter.Convert(ref value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong(((SqlInt64)value).Value, stateObj); + WriteLong(ValueTypeConverter.Convert(ref value).Value, stateObj); } break; @@ -10804,14 +10829,14 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe } if (value is SqlChars) { - string sch = new string(((SqlChars)value).Value); + string sch = new string(ValueTypeConverter.Convert(ref value).Value); return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); - return WriteEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); + return WriteEncodingChar(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } @@ -10840,21 +10865,21 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe if (value is SqlChars) { - return WriteCharArray(((SqlChars)value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteCharArray(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); - return WriteString(((SqlString)value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteString(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, stateObj, canAccumulate: false); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - WriteSqlDecimal((SqlDecimal)value, stateObj); + WriteSqlDecimal(ValueTypeConverter.Convert(ref value), stateObj); break; case TdsEnums.SQLDATETIMN: - SqlDateTime dt = (SqlDateTime)value; + SqlDateTime dt = ValueTypeConverter.Convert(ref value); if (type.FixedLength == 4) { @@ -10874,7 +10899,7 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLMONEYN: { - WriteSqlMoney((SqlMoney)value, type.FixedLength, stateObj); + WriteSqlMoney(ValueTypeConverter.Convert(ref value), type.FixedLength, stateObj); break; } @@ -11344,28 +11369,28 @@ private Task NullIfCompletedWriteTask(Task task) } } - private Task WriteValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) + private Task WriteValue(ref T value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) { - return GetTerminationTask(WriteUnterminatedValue(value, type, scale, actualLength, encodingByteSize, offset, stateObj, paramSize, isDataFeed), - value, type, actualLength, stateObj, isDataFeed); + return GetTerminationTask(WriteUnterminatedValue(ref value, type, scale, actualLength, encodingByteSize, offset, stateObj, paramSize, isDataFeed), + type, actualLength, stateObj, isDataFeed); } // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) + private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) { - Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object"); + Debug.Assert((null != value) && !(value is DBNull), "unexpected missing or empty object"); // parameters are always sent over as BIG or N types switch (type.NullableType) { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat((float)value, stateObj); + WriteFloat(ValueTypeConverter.Convert(ref value), stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble((double)value, stateObj); + WriteDouble(ValueTypeConverter.Convert(ref value), stateObj); } break; @@ -11382,15 +11407,15 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int if (isDataFeed) { Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!"); - return NullIfCompletedWriteTask(WriteStreamFeed((StreamDataFeed)value, stateObj, paramSize)); + return NullIfCompletedWriteTask(WriteStreamFeed(ValueTypeConverter.Convert(ref value), stateObj, paramSize)); } else { if (type.IsPlp) { - WriteInt(actualLength, stateObj); // chunk length + WriteInt(actualLength, stateObj); // chunk length } - return stateObj.WriteByteArray((byte[])value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value), actualLength, offset, canAccumulate: false); } } @@ -11398,7 +11423,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); Span b = stackalloc byte[16]; - FillGuidBytes((System.Guid)value, b); + FillGuidBytes(ValueTypeConverter.Convert(ref value), b); stateObj.WriteByteSpan(b); break; } @@ -11406,7 +11431,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); - if ((bool)value == true) + if (ValueTypeConverter.Convert(ref value) == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -11416,15 +11441,15 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLINTN: if (type.FixedLength == 1) - stateObj.WriteByte((byte)value); + stateObj.WriteByte(ValueTypeConverter.Convert(ref value)); else if (type.FixedLength == 2) - WriteShort((short)value, stateObj); + WriteShort(ValueTypeConverter.Convert(ref value), stateObj); else if (type.FixedLength == 4) - WriteInt((int)value, stateObj); + WriteInt(ValueTypeConverter.Convert(ref value), stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong((long)value, stateObj); + WriteLong(ValueTypeConverter.Convert(ref value), stateObj); } break; @@ -11442,7 +11467,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { - return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); + return NullIfCompletedWriteTask(WriteXmlFeed(ValueTypeConverter.Convert(ref value), stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); } else { @@ -11457,11 +11482,11 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value - return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value), actualLength, 0, canAccumulate: false); } else { - return WriteEncodingChar((string)value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); + return WriteEncodingChar(ValueTypeConverter.Convert(ref value), actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } } } @@ -11479,7 +11504,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { - return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); + return NullIfCompletedWriteTask(WriteXmlFeed(ValueTypeConverter.Convert(ref value), stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); } else { @@ -11502,25 +11527,26 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value - return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value), actualLength, 0, canAccumulate: false); } else { // convert to cchars instead of cbytes actualLength >>= 1; - return WriteString((string)value, actualLength, offset, stateObj, canAccumulate: false); + return WriteString(ValueTypeConverter.Convert(ref value), actualLength, offset, stateObj, canAccumulate: false); } } } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - WriteDecimal((decimal)value, stateObj); + var d = ValueTypeConverter.Convert(ref value); + WriteDecimal(ref d, stateObj); break; case TdsEnums.SQLDATETIMN: Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length"); - TdsDateTime dt = MetaType.FromDateTime((DateTime)value, (byte)type.FixedLength); + TdsDateTime dt = MetaType.FromDateTime(ValueTypeConverter.Convert(ref value), (byte)type.FixedLength); if (type.FixedLength == 4) { @@ -11540,13 +11566,13 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLMONEYN: { - WriteCurrency((decimal)value, type.FixedLength, stateObj); + WriteCurrency(ValueTypeConverter.Convert(ref value), type.FixedLength, stateObj); break; } case TdsEnums.SQLDATE: { - WriteDate((DateTime)value, stateObj); + WriteDate(ValueTypeConverter.Convert(ref value), stateObj); break; } @@ -11555,7 +11581,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { throw SQL.TimeScaleValueOutOfRange(scale); } - WriteTime((TimeSpan)value, scale, actualLength, stateObj); + WriteTime(ValueTypeConverter.Convert(ref value), scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIME2: @@ -11563,11 +11589,11 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { throw SQL.TimeScaleValueOutOfRange(scale); } - WriteDateTime2((DateTime)value, scale, actualLength, stateObj); + WriteDateTime2(ValueTypeConverter.Convert(ref value), scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: - WriteDateTimeOffset((DateTimeOffset)value, scale, actualLength, stateObj); + WriteDateTimeOffset(ValueTypeConverter.Convert(ref value), scale, actualLength, stateObj); break; default: @@ -11811,7 +11837,7 @@ private byte[] SerializeUnencryptedValue(object value, MetaType type, byte scale // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) + private byte[] SerializeUnencryptedSqlValue(ref T value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) { Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) || (value is INullable && !((INullable)value).IsNull)), @@ -11827,11 +11853,13 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - return SerializeFloat(((SqlSingle)value).Value); + { + return SerializeFloat(ValueTypeConverter.Convert(ref value).Value); + } else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - return SerializeDouble(((SqlDouble)value).Value); + return SerializeDouble(ValueTypeConverter.Convert(ref value).Value); } case TdsEnums.SQLBIGBINARY: @@ -11840,21 +11868,22 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act { byte[] b = new byte[actualLength]; - if (value is SqlBinary) + if (typeof(T) == typeof(SqlBinary)) { - Buffer.BlockCopy(((SqlBinary)value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(ValueTypeConverter.Convert(ref value).Value, offset, b, 0, actualLength); } else { Debug.Assert(value is SqlBytes); - Buffer.BlockCopy(((SqlBytes)value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(ValueTypeConverter.Convert(ref value).Value, offset, b, 0, actualLength); } + return b; } case TdsEnums.SQLUNIQUEID: { - byte[] b = ((SqlGuid)value).ToByteArray(); + byte[] b = ValueTypeConverter.Convert(ref value).ToByteArray(); Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); return b; @@ -11865,23 +11894,23 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); // We normalize to allow conversion across data types. BIT is serialized into a BIGINT. - return SerializeLong(((SqlBoolean)value).Value == true ? 1 : 0, stateObj); + return SerializeLong(ValueTypeConverter.Convert(ref value).Value == true ? 1 : 0, stateObj); } case TdsEnums.SQLINTN: // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT. if (type.FixedLength == 1) - return SerializeLong(((SqlByte)value).Value, stateObj); + return SerializeLong(ValueTypeConverter.Convert(ref value).Value, stateObj); if (type.FixedLength == 2) - return SerializeLong(((SqlInt16)value).Value, stateObj); + return SerializeLong(ValueTypeConverter.Convert(ref value).Value, stateObj); if (type.FixedLength == 4) - return SerializeLong(((SqlInt32)value).Value, stateObj); + return SerializeLong(ValueTypeConverter.Convert(ref value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - return SerializeLong(((SqlInt64)value).Value, stateObj); + return SerializeLong(ValueTypeConverter.Convert(ref value).Value, stateObj); } case TdsEnums.SQLBIGCHAR: @@ -11889,13 +11918,13 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act case TdsEnums.SQLTEXT: if (value is SqlChars) { - String sch = new String(((SqlChars)value).Value); + String sch = new String(ValueTypeConverter.Convert(ref value).Value); return SerializeEncodingChar(sch, actualLength, offset, _defaultEncoding); } else { Debug.Assert(value is SqlString); - return SerializeEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding); + return SerializeEncodingChar(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, _defaultEncoding); } @@ -11910,20 +11939,20 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act if (value is SqlChars) { - return SerializeCharArray(((SqlChars)value).Value, actualLength, offset); + return SerializeCharArray(ValueTypeConverter.Convert(ref value).Value, actualLength, offset); } else { Debug.Assert(value is SqlString); - return SerializeString(((SqlString)value).Value, actualLength, offset); + return SerializeString(ValueTypeConverter.Convert(ref value).Value, actualLength, offset); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - return SerializeSqlDecimal((SqlDecimal)value, stateObj); + return SerializeSqlDecimal(ValueTypeConverter.Convert(ref value), stateObj); case TdsEnums.SQLDATETIMN: - SqlDateTime dt = (SqlDateTime)value; + SqlDateTime dt = ValueTypeConverter.Convert(ref value); if (type.FixedLength == 4) { @@ -11969,7 +11998,7 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act case TdsEnums.SQLMONEYN: { - return SerializeSqlMoney((SqlMoney)value, type.FixedLength, stateObj); + return SerializeSqlMoney(ValueTypeConverter.Convert(ref value), type.FixedLength, stateObj); } default: diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs new file mode 100644 index 0000000000..aceb98fd5f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient +{ + /// + /// Delegate representing a converter to pass in by reference. + /// + /// + /// + /// + /// + public delegate TOut RefConverter(ref TIn input); + + /// + /// Converts value types from a generic to the desired, underlying type without boxing. + /// + /// + /// + public static class ValueTypeConverter + { + /// + /// Converts the value to the TOut type + /// + public static readonly RefConverter Convert; + + static ValueTypeConverter() + { + var paramExpr = Expression.Parameter(typeof(TIn).MakeByRefType(), "input"); + + var lambda = typeof(TIn) != typeof(TOut) + ? Expression.Lambda>(Expression.Convert(paramExpr, typeof(TOut)), paramExpr) + : Expression.Lambda>(paramExpr, paramExpr) + ; + + Convert = lambda.Compile(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index 143f09edc2..fc67a1a428 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -9,6 +9,7 @@ $(BinFolder)$(Configuration).FunctionalTests + diff --git a/src/ValueTypeConverterTests.cs b/src/ValueTypeConverterTests.cs new file mode 100644 index 0000000000..9eb30d3043 --- /dev/null +++ b/src/ValueTypeConverterTests.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Data.SqlClient.Tests +{ + public class ValueTypeConverterTests + { + [Fact] + public void ObjectConversionSuccessful() + { + object test = 0m; + + decimal converted = ValueTypeConverter.Convert(ref test); + + Assert.Equal(test, converted); + } + + [Fact] + public void SelfConversionSuccessful() + { + decimal test = 0m; + + decimal converted = ValueTypeConverter.Convert(ref test); + + Assert.Equal(test, converted); + } + + [Fact] + public void AssignableConversionSuccessful() + { + SqlDecimal test = 0m; + + decimal converted = ValueTypeConverter.Convert(ref test); + + Assert.Equal(test, converted); + } + } +} From b601cdaccdc01d61442a113b33363629c6c6d41d Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Thu, 19 Dec 2019 10:41:57 -0600 Subject: [PATCH 02/31] fixing build --- .../netfx/src/Microsoft.Data.SqlClient.csproj | 1 + .../Data/SqlClient/ValueTypeConverter.cs | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index f6571605f5..baee06db89 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -117,6 +117,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs new file mode 100644 index 0000000000..aceb98fd5f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient +{ + /// + /// Delegate representing a converter to pass in by reference. + /// + /// + /// + /// + /// + public delegate TOut RefConverter(ref TIn input); + + /// + /// Converts value types from a generic to the desired, underlying type without boxing. + /// + /// + /// + public static class ValueTypeConverter + { + /// + /// Converts the value to the TOut type + /// + public static readonly RefConverter Convert; + + static ValueTypeConverter() + { + var paramExpr = Expression.Parameter(typeof(TIn).MakeByRefType(), "input"); + + var lambda = typeof(TIn) != typeof(TOut) + ? Expression.Lambda>(Expression.Convert(paramExpr, typeof(TOut)), paramExpr) + : Expression.Lambda>(paramExpr, paramExpr) + ; + + Convert = lambda.Compile(); + } + } +} From bbc1e1daf99b0a6bd57334ba38b5f7f4b728f5be Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Thu, 19 Dec 2019 10:53:50 -0600 Subject: [PATCH 03/31] pr feedback --- .../tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj | 2 +- .../tests/FunctionalTests}/ValueTypeConverterTests.cs | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{ => Microsoft.Data.SqlClient/tests/FunctionalTests}/ValueTypeConverterTests.cs (100%) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index fc67a1a428..c413700973 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -9,7 +9,6 @@ $(BinFolder)$(Configuration).FunctionalTests - @@ -48,6 +47,7 @@ + diff --git a/src/ValueTypeConverterTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs similarity index 100% rename from src/ValueTypeConverterTests.cs rename to src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs From cb586a5295c7e05610bf262f4a03644a25ff94ab Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Mon, 30 Dec 2019 14:40:24 -0600 Subject: [PATCH 04/31] simplifying netcore ValueTypeConverter --- .../src/Microsoft.Data.SqlClient.csproj | 1 + .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 140 ++++++------ .../Microsoft/Data/SqlClient/SqlParameter.cs | 22 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 214 +++++++++--------- .../Data/SqlClient/ValueTypeConverter.cs | 34 +-- .../SqlClient/ValueTypeConverterBackup.cs | 44 ++++ .../Microsoft.Data.SqlClient.Tests.csproj | 1 + .../ValueTypeConverterTests.cs | 6 +- 8 files changed, 233 insertions(+), 229 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverterBackup.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 8d9d377143..043e46bd5b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -579,6 +579,7 @@ + True diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index f0f390c0c6..20f1b1c118 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -877,7 +877,7 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) { isNull = true; var dbnull = DBNull.Value; - return WriteValueAsync(ref dbnull, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(dbnull, destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -885,23 +885,23 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) { case ValueMethod.DataFeedStream: var stream = new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)); - return WriteValueAsync(ref stream, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(stream, destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.DataFeedText: var text = new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)); - return WriteValueAsync(ref text, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(text, destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.DataFeedXml: // Only SqlDataReader supports an XmlReader // There is no GetXmlReader on DbDataReader, however if GetValue returns XmlReader we will read it as stream if it is assigned to XML field Debug.Assert(_SqlDataReaderRowSource != null, "Should not be reading row as an XmlReader if bulk copy source is not a SqlDataReader"); var xml = new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)); - return WriteValueAsync(ref xml, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(xml, destRowIndex, isSqlType, isDataFeed, isNull); default: Debug.Fail($"Current column is marked as being a DataFeed, but no DataFeed compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); isDataFeed = false; object columnValue = _DbDataReaderRowSource.GetValue(sourceOrdinal); ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return WriteValueAsync(ref columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); } } } @@ -915,19 +915,19 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) { case ValueMethod.SqlTypeSqlDecimal: var value = _SqlDataReaderRowSource.GetSqlDecimal(sourceOrdinal); - return WriteValueAsync(ref value, destRowIndex, isSqlType, isDataFeed, value.IsNull); + return WriteValueAsync(value, destRowIndex, isSqlType, isDataFeed, value.IsNull); case ValueMethod.SqlTypeSqlDouble: // use cast to handle IsNull correctly because no public constructor allows it var dblValue = (SqlDecimal)_SqlDataReaderRowSource.GetSqlDouble(sourceOrdinal); - return WriteValueAsync(ref dblValue, destRowIndex, isSqlType, isDataFeed, dblValue.IsNull); + return WriteValueAsync(dblValue, destRowIndex, isSqlType, isDataFeed, dblValue.IsNull); case ValueMethod.SqlTypeSqlSingle: // use cast to handle value.IsNull correctly because no public constructor allows it var singleValue = (SqlDecimal)_SqlDataReaderRowSource.GetSqlSingle(sourceOrdinal); - return WriteValueAsync(ref singleValue, destRowIndex, isSqlType, isDataFeed, singleValue.IsNull); + return WriteValueAsync(singleValue, destRowIndex, isSqlType, isDataFeed, singleValue.IsNull); default: Debug.Fail($"Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); var sqlValue = (INullable)_SqlDataReaderRowSource.GetSqlValue(sourceOrdinal); - return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, sqlValue.IsNull); + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, sqlValue.IsNull); } } else @@ -945,7 +945,7 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) Debug.Assert(!(value is INullable) || !((INullable)value).IsNull, "IsDBNull returned false, but GetValue returned a null INullable"); } #endif - return WriteValueAsync(ref value, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(value, destRowIndex, isSqlType, isDataFeed, isNull); } } else @@ -956,8 +956,7 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) if ((_enableStreaming) && (_SqlDataReaderRowSource == null) && (r.IsDBNull(sourceOrdinal))) { isNull = true; - var dbnull = DBNull.Value; - return WriteValueAsync(ref dbnull, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(DBNull.Value, destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -970,45 +969,33 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) switch (typeCode) { case TypeCode.Int32: - var i = r.GetInt32(sourceOrdinal); - return WriteValueAsync(ref i, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetInt32(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.String: - var s = r.GetString(sourceOrdinal); - return WriteValueAsync(ref s, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetString(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.Double: - var d = r.GetDouble(sourceOrdinal); - return WriteValueAsync(ref d, destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Decimal: - var dc = r.GetDecimal(sourceOrdinal); - return WriteValueAsync(ref dc, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetDouble(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case TypeCode.Decimal: + return WriteValueAsync(r.GetDecimal(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.Int16: - var sh = r.GetInt16(sourceOrdinal); - return WriteValueAsync(ref sh, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetInt16(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.Int64: - var l = r.GetInt64(sourceOrdinal); - return WriteValueAsync(ref l, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetInt64(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.Char: - var c = r.GetChar(sourceOrdinal); - return WriteValueAsync(ref c, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetChar(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.Byte: - var by = r.GetByte(sourceOrdinal); - return WriteValueAsync(ref by, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetByte(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.Boolean: - var b = r.GetBoolean(sourceOrdinal); - return WriteValueAsync(ref b, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetBoolean(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.DateTime: - var dt = r.GetDateTime(sourceOrdinal); - return WriteValueAsync(ref dt, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetDateTime(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.Object when fieldType == typeof(Guid): - var g = r.GetGuid(sourceOrdinal); - return WriteValueAsync(ref g, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetGuid(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case TypeCode.Object when fieldType == typeof(float): - var f = r.GetFloat(sourceOrdinal); - return WriteValueAsync(ref f, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(r.GetFloat(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); default: object columnValue = r.GetValue(sourceOrdinal); ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return WriteValueAsync(ref columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); } } } @@ -1033,7 +1020,7 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) if (isSqlType) { var sqlDec = new SqlDecimal(((SqlSingle)currentRowValue).Value); - return WriteValueAsync(ref sqlDec, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(sqlDec, destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -1042,11 +1029,11 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) { isSqlType = true; var sqlDec = new SqlDecimal(f); - return WriteValueAsync(ref sqlDec, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(sqlDec, destRowIndex, isSqlType, isDataFeed, isNull); } else { - return WriteValueAsync(ref currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } } } @@ -1055,7 +1042,7 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) if (isSqlType) { var sqlValue = new SqlDecimal(((SqlDouble)currentRowValue).Value); - return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -1064,11 +1051,11 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) { isSqlType = true; var sqlValue = new SqlDecimal(d); - return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } else { - return WriteValueAsync(ref currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } } } @@ -1077,26 +1064,26 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) if (isSqlType) { var sqlValue = (SqlDecimal)currentRowValue; - return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } else { isSqlType = true; var sqlValue = new SqlDecimal((decimal)currentRowValue); - return WriteValueAsync(ref sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } } default: { Debug.Fail($"Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); // If we are here then either the value is null, there was no special storage type for this column or the special storage type wasn't handled (e.g. if the currentRowValue is NaN) - return WriteValueAsync(ref currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } } } else { - return WriteValueAsync(ref currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } } default: @@ -1422,7 +1409,7 @@ private string UnquotedName(string name) return name; } - private bool ValidateBulkCopyVariantIfNeeded(ref T value, out object variantValue) + private bool ValidateBulkCopyVariantIfNeeded(T value, out object variantValue) { variantValue = null; @@ -1465,7 +1452,7 @@ private bool ValidateBulkCopyVariantIfNeeded(ref T value, out object variantV } } - private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metadata, bool isNull, bool isSqlType) + private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, bool isNull, bool isSqlType) { bool coercedToDataFeed = false; @@ -1476,7 +1463,7 @@ private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metada throw SQL.BulkLoadBulkLoadNotAllowDBNull(metadata.column); } - return DoWriteValueAsync(ref value, col, isSqlType, coercedToDataFeed, isNull, metadata); + return DoWriteValueAsync(value, col, isSqlType, coercedToDataFeed, isNull, metadata); } MetaType type = metadata.metaType; @@ -1509,11 +1496,11 @@ private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metada case TdsEnums.SQLDECIMALN: if (typeof(T) == typeof(decimal)) { - decValue = new SqlDecimal(ValueTypeConverter.Convert(ref value)); + decValue = new SqlDecimal(ValueTypeConverter.Convert(value)); } else if (typeof(T) == typeof(SqlDecimal)) { - decValue = ValueTypeConverter.Convert(ref value); + decValue = ValueTypeConverter.Convert(value); } else { @@ -1549,7 +1536,7 @@ private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metada typeChanged = false; // Setting this to false as SqlParameter.CoerceValue will only set it to true when converting to a CLR type break; - + case TdsEnums.SQLINTN: case TdsEnums.SQLFLTN: case TdsEnums.SQLFLT4: @@ -1572,22 +1559,22 @@ private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metada case TdsEnums.SQLDATETIME2: case TdsEnums.SQLDATETIMEOFFSET: mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - typeChanged = SqlParameter.CoerceValueIfNeeded(ref value, mt, out objValue, out coercedToDataFeed); + typeChanged = SqlParameter.CoerceValueIfNeeded(value, mt, out objValue, out coercedToDataFeed); break; case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - typeChanged = SqlParameter.CoerceValueIfNeeded(ref value, mt, out objValue, out coercedToDataFeed, false); + typeChanged = SqlParameter.CoerceValueIfNeeded(value, mt, out objValue, out coercedToDataFeed, false); if (!coercedToDataFeed) { // We do not need to test for TextDataFeed as it is only assigned to (N)VARCHAR(MAX) string str = typeChanged ? (string)objValue : isSqlType - ? ValueTypeConverter.Convert(ref value).Value - : ValueTypeConverter.Convert(ref value) + ? ValueTypeConverter.Convert(value).Value + : ValueTypeConverter.Convert(value) ; - + int maxStringLength = length / 2; if (str.Length > maxStringLength) { @@ -1607,7 +1594,7 @@ private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metada break; case TdsEnums.SQLVARIANT: - typeChanged = ValidateBulkCopyVariantIfNeeded(ref value, out objValue); + typeChanged = ValidateBulkCopyVariantIfNeeded(value, out objValue); break; case TdsEnums.SQLUDT: // UDTs are sent as varbinary so we need to get the raw bytes @@ -1618,7 +1605,7 @@ private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metada // in byte[] form. if (!(value is byte[])) { - objValue = _connection.GetBytes(value); + objValue = _connection.GetBytes(value); typeChanged = true; } break; @@ -1649,18 +1636,17 @@ private Task ConvertWriteValueAsync(ref T value, int col, _SqlMetaData metada if (decValue.HasValue) { - var dv = decValue.Value; - return WriteConvertedValue(ref dv, col, isSqlType, isNull, coercedToDataFeed, metadata); + return WriteConvertedValue(decValue.Value, col, isSqlType, isNull, coercedToDataFeed, metadata); } else if (typeChanged) { // All type changes change to CLR types isSqlType = false; - return WriteConvertedValue(ref objValue, col, isSqlType, isNull, coercedToDataFeed, metadata); + return WriteConvertedValue(objValue, col, isSqlType, isNull, coercedToDataFeed, metadata); } else { - return WriteConvertedValue(ref value, col, isSqlType, isNull, coercedToDataFeed, metadata); + return WriteConvertedValue(value, col, isSqlType, isNull, coercedToDataFeed, metadata); } } @@ -2206,45 +2192,45 @@ private Task ReadWriteColumnValueAsync(int col) return writeTask; } - private Task WriteValueAsync(ref T value, int col, bool isSqlType, bool isDataFeed, bool isNull) + private Task WriteValueAsync(T value, int col, bool isSqlType, bool isDataFeed, bool isNull) { _SqlMetaData metadata = _sortedColumnMappings[col]._metadata; if (isDataFeed) { //nothing to convert, skip straight to write - return DoWriteValueAsync(ref value, col, isSqlType, isDataFeed, isNull, metadata); + return DoWriteValueAsync(value, col, isSqlType, isDataFeed, isNull, metadata); } else { - return ConvertWriteValueAsync(ref value, col, metadata, isNull, isSqlType); + return ConvertWriteValueAsync(value, col, metadata, isNull, isSqlType); } } - private Task WriteConvertedValue(ref T value, int col, bool isSqlType, bool isNull, bool isDatafeed, _SqlMetaData metadata) + private Task WriteConvertedValue(T value, int col, bool isSqlType, bool isNull, bool isDatafeed, _SqlMetaData metadata) { // If column encryption is requested via connection string option, perform encryption here if (!isNull && // if value is not NULL metadata.isEncrypted) { // If we are transparently encrypting Debug.Assert(_parser.ShouldEncryptValuesForBulkCopy()); - var bytesValue = _parser.EncryptColumnValue(ref value, metadata, metadata.column, _stateObj, isDatafeed, isSqlType); + var bytesValue = _parser.EncryptColumnValue(value, metadata, metadata.column, _stateObj, isDatafeed, isSqlType); isSqlType = false; // Its not a sql type anymore - return DoWriteValueAsync(ref bytesValue, col, isSqlType, isDatafeed, isNull, metadata); + return DoWriteValueAsync(bytesValue, col, isSqlType, isDatafeed, isNull, metadata); } else { - return DoWriteValueAsync(ref value, col, isSqlType, isDatafeed, isNull, metadata); + return DoWriteValueAsync(value, col, isSqlType, isDatafeed, isNull, metadata); } } - private Task DoWriteValueAsync(ref T value, int col, bool isSqlType, bool isDataFeed, bool isNull, _SqlMetaData metadata) + private Task DoWriteValueAsync(T value, int col, bool isSqlType, bool isDataFeed, bool isNull, _SqlMetaData metadata) { Task writeTask = null; if (metadata.type != SqlDbType.Variant) { //this is the most common path - writeTask = _parser.WriteBulkCopyValue(ref value, metadata, _stateObj, isSqlType, isDataFeed, isNull); //returns Task/Null + writeTask = _parser.WriteBulkCopyValue(value, metadata, _stateObj, isSqlType, isDataFeed, isNull); //returns Task/Null } else { @@ -2258,15 +2244,15 @@ private Task DoWriteValueAsync(ref T value, int col, bool isSqlType, bool isD if (variantInternalType == SqlBuffer.StorageType.DateTime2 && value is DateTime) { - _parser.WriteSqlVariantDateTime2(ValueTypeConverter.Convert(ref value), _stateObj); + _parser.WriteSqlVariantDateTime2(ValueTypeConverter.Convert(value), _stateObj); } else if (variantInternalType == SqlBuffer.StorageType.Date && value is DateTime d) { - _parser.WriteSqlVariantDate(ValueTypeConverter.Convert(ref value), _stateObj); + _parser.WriteSqlVariantDate(ValueTypeConverter.Convert(value), _stateObj); } else { - writeTask = _parser.WriteSqlVariantDataRowValue(ref value, _stateObj); //returns Task/Null + writeTask = _parser.WriteSqlVariantDataRowValue(value, _stateObj); //returns Task/Null } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index 4483e232d5..c0fdfb8b8e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -107,7 +107,7 @@ public sealed partial class SqlParameter : DbParameter, IDbDataParameter, IClone /// /// Indicates if the parameter encryption metadata received by sp_describe_parameter_encryption. - /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate + /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate /// that no encryption is needed). /// internal bool HasReceivedMetadata { get; set; } @@ -407,7 +407,7 @@ internal SmiParameterMetaData MetaDataForSmi(out ParameterPeekAheadValue peekAhe long actualLen = GetActualSize(); long maxLen = this.Size; - // GetActualSize returns bytes length, but smi expects char length for + // GetActualSize returns bytes length, but smi expects char length for // character types, so adjust if (!mt.IsLong) { @@ -998,12 +998,12 @@ object ICloneable.Clone() internal static object CoerceValue(object value, MetaType destinationType, out bool coercedToDataFeed, out bool typeChanged, bool allowStreaming = true) { - typeChanged = CoerceValueIfNeeded(ref value, destinationType, out var objValue, out coercedToDataFeed, allowStreaming); + typeChanged = CoerceValueIfNeeded(value, destinationType, out var objValue, out coercedToDataFeed, allowStreaming); return typeChanged ? objValue : value; } - internal static bool CoerceValueIfNeeded(ref T value, MetaType destinationType, out object objValue, out bool coercedToDataFeed, bool allowStreaming = true) + internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, out object objValue, out bool coercedToDataFeed, bool allowStreaming = true) { Debug.Assert(!(value is DataFeed), "Value provided should not already be a data feed"); Debug.Assert(!ADP.IsNull(value), "Value provided should not be null"); @@ -1031,7 +1031,7 @@ internal static bool CoerceValueIfNeeded(ref T value, MetaType destinationTyp // For Xml data, destination Type is always string if (typeof(SqlXml) == currentType) { - var xmlValue = ValueTypeConverter.Convert(ref value); + var xmlValue = ValueTypeConverter.Convert(value); objValue = MetaType.GetStringFromXml(xmlValue.CreateReader()); } else if (typeof(SqlString) == currentType) @@ -1052,12 +1052,12 @@ internal static bool CoerceValueIfNeeded(ref T value, MetaType destinationTyp } else if (typeof(char[]) == currentType) { - var charArrayValue = ValueTypeConverter.Convert(ref value); + var charArrayValue = ValueTypeConverter.Convert(value); objValue = new string(charArrayValue); } else if (typeof(SqlChars) == currentType) { - var sqlCharsValue = ValueTypeConverter.Convert(ref value); + var sqlCharsValue = ValueTypeConverter.Convert(value); objValue = new string(sqlCharsValue.Value); } else if (value is TextReader tr && allowStreaming) @@ -1072,7 +1072,7 @@ internal static bool CoerceValueIfNeeded(ref T value, MetaType destinationTyp } else if ((DbType.Currency == destinationType.DbType) && (typeof(string) == currentType)) { - objValue = decimal.Parse(ValueTypeConverter.Convert(ref value), NumberStyles.Currency, (IFormatProvider)null); + objValue = decimal.Parse(ValueTypeConverter.Convert(value), NumberStyles.Currency, (IFormatProvider)null); } else if ((typeof(SqlBytes) == currentType) && (typeof(byte[]) == destinationType.ClassType)) { @@ -1080,15 +1080,15 @@ internal static bool CoerceValueIfNeeded(ref T value, MetaType destinationTyp } else if ((typeof(string) == currentType) && (SqlDbType.Time == destinationType.SqlDbType)) { - objValue = TimeSpan.Parse(ValueTypeConverter.Convert(ref value)); + objValue = TimeSpan.Parse(ValueTypeConverter.Convert(value)); } else if ((typeof(string) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) { - objValue = DateTimeOffset.Parse(ValueTypeConverter.Convert(ref value), (IFormatProvider)null); + objValue = DateTimeOffset.Parse(ValueTypeConverter.Convert(value), (IFormatProvider)null); } else if ((typeof(DateTime) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) { - objValue = new DateTimeOffset(ValueTypeConverter.Convert(ref value)); + objValue = new DateTimeOffset(ValueTypeConverter.Convert(value)); } else if (TdsEnums.SQLTABLE == destinationType.TDSType && ( value is DataTable || diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 9c937120eb..a6eff9f265 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -6429,7 +6429,7 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars // Therefore the sql_variant value must not include the MaxLength. This is the major difference // between this method and WriteSqlVariantValue above. // - internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject stateObj, bool canAccumulate = true) + internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject stateObj, bool canAccumulate = true) { // handle null values if ((null == value) || typeof(T) == typeof(DBNull)) @@ -6443,44 +6443,44 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s if (metatype.IsAnsiType && value is string) { - length = GetEncodingCharLength(ValueTypeConverter.Convert(ref value), length, 0, _defaultEncoding); + length = GetEncodingCharLength(ValueTypeConverter.Convert(value), length, 0, _defaultEncoding); } switch (metatype.TDSType) { case TdsEnums.SQLFLT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteFloat(ValueTypeConverter.Convert(ref value), stateObj); + WriteFloat(ValueTypeConverter.Convert(value), stateObj); break; case TdsEnums.SQLFLT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteDouble(ValueTypeConverter.Convert(ref value), stateObj); + WriteDouble(ValueTypeConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteLong(ValueTypeConverter.Convert(ref value), stateObj); + WriteLong(ValueTypeConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteInt(ValueTypeConverter.Convert(ref value), stateObj); + WriteInt(ValueTypeConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT2: WriteSqlVariantHeader(4, metatype.TDSType, metatype.PropBytes, stateObj); - WriteShort(ValueTypeConverter.Convert(ref value), stateObj); + WriteShort(ValueTypeConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT1: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); - stateObj.WriteByte(ValueTypeConverter.Convert(ref value)); + stateObj.WriteByte(ValueTypeConverter.Convert(value)); break; case TdsEnums.SQLBIT: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); - if (ValueTypeConverter.Convert(ref value)) + if (ValueTypeConverter.Convert(value)) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -6489,7 +6489,7 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s case TdsEnums.SQLBIGVARBINARY: { - byte[] b = ValueTypeConverter.Convert(ref value); + byte[] b = ValueTypeConverter.Convert(value); length = b.Length; WriteSqlVariantHeader(4 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6499,7 +6499,7 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s case TdsEnums.SQLBIGVARCHAR: { - string s = ValueTypeConverter.Convert(ref value); + string s = ValueTypeConverter.Convert(value); length = s.Length; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6511,7 +6511,7 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s case TdsEnums.SQLUNIQUEID: { - Guid guid = ValueTypeConverter.Convert(ref value); + Guid guid = ValueTypeConverter.Convert(value); Span b = stackalloc byte[16]; FillGuidBytes(guid, b); @@ -6524,7 +6524,7 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s case TdsEnums.SQLNVARCHAR: { - string s = ValueTypeConverter.Convert(ref value); + string s = ValueTypeConverter.Convert(value); length = s.Length * 2; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6539,7 +6539,7 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s case TdsEnums.SQLDATETIME: { - TdsDateTime dt = MetaType.FromDateTime(ValueTypeConverter.Convert(ref value), 8); + TdsDateTime dt = MetaType.FromDateTime(ValueTypeConverter.Convert(value), 8); WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); WriteInt(dt.days, stateObj); @@ -6550,7 +6550,7 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s case TdsEnums.SQLMONEY: { WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteCurrency(ValueTypeConverter.Convert(ref value), 8, stateObj); + WriteCurrency(ValueTypeConverter.Convert(value), 8, stateObj); break; } @@ -6558,7 +6558,7 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s { WriteSqlVariantHeader(21, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Precision); //propbytes: precision - var decValue = ValueTypeConverter.Convert(ref value); + var decValue = ValueTypeConverter.Convert(value); stateObj.WriteByte((byte)((decimal.GetBits(decValue)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale WriteDecimal(ref decValue, stateObj); break; @@ -6567,13 +6567,13 @@ internal Task WriteSqlVariantDataRowValue(ref T value, TdsParserStateObject s case TdsEnums.SQLTIME: WriteSqlVariantHeader(8, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale - WriteTime(ValueTypeConverter.Convert(ref value), metatype.Scale, 5, stateObj); + WriteTime(ValueTypeConverter.Convert(value), metatype.Scale, 5, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: WriteSqlVariantHeader(13, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale - WriteDateTimeOffset(ValueTypeConverter.Convert(ref value), metatype.Scale, 10, stateObj); + WriteDateTimeOffset(ValueTypeConverter.Convert(value), metatype.Scale, 10, stateObj); break; default: @@ -8988,7 +8988,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet { if (isSqlVal) { - serializedValue = SerializeUnencryptedSqlValue(ref value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj); + serializedValue = SerializeUnencryptedSqlValue(value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj); } else { @@ -9308,13 +9308,13 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet { if (isSqlVal) { - writeParamTask = WriteSqlValue(ref value, mt, actualSize, codePageByteSize, param.Offset, stateObj); + writeParamTask = WriteSqlValue(value, mt, actualSize, codePageByteSize, param.Offset, stateObj); } else { // for codePageEncoded types, WriteValue simply expects the number of characters // For plp types, we also need the encoded byte size - writeParamTask = WriteValue(ref value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); + writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); } } @@ -10120,7 +10120,7 @@ internal bool ShouldEncryptValuesForBulkCopy() /// Encrypts a column value (for SqlBulkCopy) /// /// - internal byte[] EncryptColumnValue(ref T value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) + internal byte[] EncryptColumnValue(T value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) { Debug.Assert(IsColumnEncryptionSupported, "Server doesn't support encryption, yet we received encryption metadata"); Debug.Assert(ShouldEncryptValuesForBulkCopy(), "Encryption attempted when not requested"); @@ -10146,8 +10146,8 @@ internal byte[] EncryptColumnValue(ref T value, SqlMetaDataPriv metadata, str // to report the size of data to be copied out (for serialization). If we underreport the // size, truncation will happen for us! actualLengthInBytes = (isSqlType) - ? ValueTypeConverter.Convert(ref value).Length - : ValueTypeConverter.Convert(ref value).Length; + ? ValueTypeConverter.Convert(value).Length + : ValueTypeConverter.Convert(value).Length; if (metadata.baseTI.length > 0 && actualLengthInBytes > metadata.baseTI.length) @@ -10169,8 +10169,8 @@ internal byte[] EncryptColumnValue(ref T value, SqlMetaDataPriv metadata, str } string stringValue = (isSqlType) - ? ValueTypeConverter.Convert(ref value).Value - : ValueTypeConverter.Convert(ref value); + ? ValueTypeConverter.Convert(value).Value + : ValueTypeConverter.Convert(value); actualLengthInBytes = _defaultEncoding.GetByteCount(stringValue); @@ -10186,8 +10186,8 @@ internal byte[] EncryptColumnValue(ref T value, SqlMetaDataPriv metadata, str case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: actualLengthInBytes = (isSqlType - ? ValueTypeConverter.Convert(ref value).Value.Length - : ValueTypeConverter.Convert(ref value).Length) + ? ValueTypeConverter.Convert(value).Value.Length + : ValueTypeConverter.Convert(value).Length) * 2; if (metadata.baseTI.length > 0 && @@ -10207,7 +10207,7 @@ internal byte[] EncryptColumnValue(ref T value, SqlMetaDataPriv metadata, str if (isSqlType) { // SqlType - serializedValue = SerializeUnencryptedSqlValue(ref value, + serializedValue = SerializeUnencryptedSqlValue(value, metadata.baseTI.metaType, actualLengthInBytes, offset: 0, @@ -10233,7 +10233,7 @@ internal byte[] EncryptColumnValue(ref T value, SqlMetaDataPriv metadata, str _connHandler.ConnectionOptions.DataSource); } - internal Task WriteBulkCopyValue(ref T value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) + internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) { Debug.Assert(!isSqlType || value is INullable, "isSqlType is true, but value can not be type cast to an INullable"); Debug.Assert(!isDataFeed ^ value is DataFeed, "Incorrect value for isDataFeed"); @@ -10300,8 +10300,8 @@ internal Task WriteBulkCopyValue(ref T value, SqlMetaDataPriv metadata, TdsPa case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: ccb = (isSqlType) - ? ValueTypeConverter.Convert(ref value).Length - : ValueTypeConverter.Convert(ref value).Length; + ? ValueTypeConverter.Convert(value).Length + : ValueTypeConverter.Convert(value).Length; break; case TdsEnums.SQLUNIQUEID: ccb = GUID_SIZE; @@ -10317,11 +10317,11 @@ internal Task WriteBulkCopyValue(ref T value, SqlMetaDataPriv metadata, TdsPa string stringValue = null; if (isSqlType) { - stringValue = ValueTypeConverter.Convert(ref value).Value; + stringValue = ValueTypeConverter.Convert(value).Value; } else { - stringValue = ValueTypeConverter.Convert(ref value); + stringValue = ValueTypeConverter.Convert(value); } ccb = stringValue.Length; @@ -10331,8 +10331,8 @@ internal Task WriteBulkCopyValue(ref T value, SqlMetaDataPriv metadata, TdsPa case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: ccb = (isSqlType - ? ValueTypeConverter.Convert(ref value).Value.Length - : ValueTypeConverter.Convert(ref value).Length + ? ValueTypeConverter.Convert(value).Value.Length + : ValueTypeConverter.Convert(value).Length ) * 2; break; case TdsEnums.SQLXMLTYPE: @@ -10342,8 +10342,8 @@ internal Task WriteBulkCopyValue(ref T value, SqlMetaDataPriv metadata, TdsPa objValue = MetaType.GetStringFromXml(xr); } ccb = (isSqlType - ? ValueTypeConverter.Convert(ref value).Value.Length - : ValueTypeConverter.Convert(ref value).Length + ? ValueTypeConverter.Convert(value).Value.Length + : ValueTypeConverter.Convert(value).Length ) * 2; break; @@ -10392,15 +10392,15 @@ internal Task WriteBulkCopyValue(ref T value, SqlMetaDataPriv metadata, TdsPa if (isSqlType) { - internalWriteTask = WriteSqlValue(ref value, metatype, ccb, ccbStringBytes, 0, stateObj); + internalWriteTask = WriteSqlValue(value, metatype, ccb, ccbStringBytes, 0, stateObj); } else if (metatype.SqlDbType != SqlDbType.Udt || metatype.IsLong) { // we only have to consider a conversion from above in this case. internalWriteTask = objValue != null - ? WriteValue(ref objValue, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed) - : WriteValue(ref value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed) + ? WriteValue(objValue, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed) + : WriteValue(value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed) ; if ((internalWriteTask == null) && (_asyncWrite)) { @@ -10411,7 +10411,7 @@ internal Task WriteBulkCopyValue(ref T value, SqlMetaDataPriv metadata, TdsPa else { WriteShort(ccb, stateObj); - internalWriteTask = stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value), ccb, 0); + internalWriteTask = stateObj.WriteByteArray(ValueTypeConverter.Convert(value), ccb, 0); } #if DEBUG @@ -10724,16 +10724,16 @@ private Task GetTerminationTask(Task unterminatedWriteTask, MetaType type, int a } - private Task WriteSqlValue(ref T value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) + private Task WriteSqlValue(T value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) { return GetTerminationTask( - WriteUnterminatedSqlValue(ref value, type, actualLength, codePageByteSize, offset, stateObj), + WriteUnterminatedSqlValue(value, type, actualLength, codePageByteSize, offset, stateObj), type, actualLength, stateObj, false); } // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) + private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) { Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) || (value is INullable && !((INullable)value).IsNull)), @@ -10744,11 +10744,11 @@ private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actual { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat(ValueTypeConverter.Convert(ref value).Value, stateObj); + WriteFloat(ValueTypeConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble(ValueTypeConverter.Convert(ref value).Value, stateObj); + WriteDouble(ValueTypeConverter.Convert(value).Value, stateObj); } break; @@ -10764,12 +10764,12 @@ private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actual if (value is SqlBinary) { - return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(value).Value, actualLength, offset, canAccumulate: false); } else { Debug.Assert(value is SqlBytes); - return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(value).Value, actualLength, offset, canAccumulate: false); } } @@ -10777,7 +10777,7 @@ private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actual { Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); Span b = stackalloc byte[16]; - SqlGuid sqlGuid = ValueTypeConverter.Convert(ref value); + SqlGuid sqlGuid = ValueTypeConverter.Convert(value); if (sqlGuid.IsNull) { @@ -10795,7 +10795,7 @@ private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actual case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); - if (ValueTypeConverter.Convert(ref value).Value == true) + if (ValueTypeConverter.Convert(value).Value == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -10805,17 +10805,17 @@ private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actual case TdsEnums.SQLINTN: if (type.FixedLength == 1) - stateObj.WriteByte(ValueTypeConverter.Convert(ref value).Value); + stateObj.WriteByte(ValueTypeConverter.Convert(value).Value); else if (type.FixedLength == 2) - WriteShort(ValueTypeConverter.Convert(ref value).Value, stateObj); + WriteShort(ValueTypeConverter.Convert(value).Value, stateObj); else if (type.FixedLength == 4) - WriteInt(ValueTypeConverter.Convert(ref value).Value, stateObj); + WriteInt(ValueTypeConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong(ValueTypeConverter.Convert(ref value).Value, stateObj); + WriteLong(ValueTypeConverter.Convert(value).Value, stateObj); } break; @@ -10829,14 +10829,14 @@ private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actual } if (value is SqlChars) { - string sch = new string(ValueTypeConverter.Convert(ref value).Value); + string sch = new string(ValueTypeConverter.Convert(value).Value); return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); - return WriteEncodingChar(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); + return WriteEncodingChar(ValueTypeConverter.Convert(value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } @@ -10865,21 +10865,21 @@ private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actual if (value is SqlChars) { - return WriteCharArray(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteCharArray(ValueTypeConverter.Convert(value).Value, actualLength, offset, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); - return WriteString(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteString(ValueTypeConverter.Convert(value).Value, actualLength, offset, stateObj, canAccumulate: false); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - WriteSqlDecimal(ValueTypeConverter.Convert(ref value), stateObj); + WriteSqlDecimal(ValueTypeConverter.Convert(value), stateObj); break; case TdsEnums.SQLDATETIMN: - SqlDateTime dt = ValueTypeConverter.Convert(ref value); + SqlDateTime dt = ValueTypeConverter.Convert(value); if (type.FixedLength == 4) { @@ -10899,7 +10899,7 @@ private Task WriteUnterminatedSqlValue(ref T value, MetaType type, int actual case TdsEnums.SQLMONEYN: { - WriteSqlMoney(ValueTypeConverter.Convert(ref value), type.FixedLength, stateObj); + WriteSqlMoney(ValueTypeConverter.Convert(value), type.FixedLength, stateObj); break; } @@ -11369,15 +11369,15 @@ private Task NullIfCompletedWriteTask(Task task) } } - private Task WriteValue(ref T value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) + private Task WriteValue(T value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) { - return GetTerminationTask(WriteUnterminatedValue(ref value, type, scale, actualLength, encodingByteSize, offset, stateObj, paramSize, isDataFeed), + return GetTerminationTask(WriteUnterminatedValue(value, type, scale, actualLength, encodingByteSize, offset, stateObj, paramSize, isDataFeed), type, actualLength, stateObj, isDataFeed); } // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) + private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) { Debug.Assert((null != value) && !(value is DBNull), "unexpected missing or empty object"); @@ -11386,11 +11386,11 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat(ValueTypeConverter.Convert(ref value), stateObj); + WriteFloat(ValueTypeConverter.Convert(value), stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble(ValueTypeConverter.Convert(ref value), stateObj); + WriteDouble(ValueTypeConverter.Convert(value), stateObj); } break; @@ -11407,7 +11407,7 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i if (isDataFeed) { Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!"); - return NullIfCompletedWriteTask(WriteStreamFeed(ValueTypeConverter.Convert(ref value), stateObj, paramSize)); + return NullIfCompletedWriteTask(WriteStreamFeed(ValueTypeConverter.Convert(value), stateObj, paramSize)); } else { @@ -11415,7 +11415,7 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i { WriteInt(actualLength, stateObj); // chunk length } - return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value), actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(value), actualLength, offset, canAccumulate: false); } } @@ -11423,7 +11423,7 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i { Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); Span b = stackalloc byte[16]; - FillGuidBytes(ValueTypeConverter.Convert(ref value), b); + FillGuidBytes(ValueTypeConverter.Convert(value), b); stateObj.WriteByteSpan(b); break; } @@ -11431,7 +11431,7 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); - if (ValueTypeConverter.Convert(ref value) == true) + if (ValueTypeConverter.Convert(value) == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -11441,15 +11441,15 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i case TdsEnums.SQLINTN: if (type.FixedLength == 1) - stateObj.WriteByte(ValueTypeConverter.Convert(ref value)); + stateObj.WriteByte(ValueTypeConverter.Convert(value)); else if (type.FixedLength == 2) - WriteShort(ValueTypeConverter.Convert(ref value), stateObj); + WriteShort(ValueTypeConverter.Convert(value), stateObj); else if (type.FixedLength == 4) - WriteInt(ValueTypeConverter.Convert(ref value), stateObj); + WriteInt(ValueTypeConverter.Convert(value), stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong(ValueTypeConverter.Convert(ref value), stateObj); + WriteLong(ValueTypeConverter.Convert(value), stateObj); } break; @@ -11467,7 +11467,7 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { - return NullIfCompletedWriteTask(WriteXmlFeed(ValueTypeConverter.Convert(ref value), stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); + return NullIfCompletedWriteTask(WriteXmlFeed(ValueTypeConverter.Convert(value), stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); } else { @@ -11482,11 +11482,11 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value - return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value), actualLength, 0, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(value), actualLength, 0, canAccumulate: false); } else { - return WriteEncodingChar(ValueTypeConverter.Convert(ref value), actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); + return WriteEncodingChar(ValueTypeConverter.Convert(value), actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } } } @@ -11504,7 +11504,7 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { - return NullIfCompletedWriteTask(WriteXmlFeed(ValueTypeConverter.Convert(ref value), stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); + return NullIfCompletedWriteTask(WriteXmlFeed(ValueTypeConverter.Convert(value), stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); } else { @@ -11527,26 +11527,26 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value - return stateObj.WriteByteArray(ValueTypeConverter.Convert(ref value), actualLength, 0, canAccumulate: false); + return stateObj.WriteByteArray(ValueTypeConverter.Convert(value), actualLength, 0, canAccumulate: false); } else { // convert to cchars instead of cbytes actualLength >>= 1; - return WriteString(ValueTypeConverter.Convert(ref value), actualLength, offset, stateObj, canAccumulate: false); + return WriteString(ValueTypeConverter.Convert(value), actualLength, offset, stateObj, canAccumulate: false); } } } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - var d = ValueTypeConverter.Convert(ref value); + var d = ValueTypeConverter.Convert(value); WriteDecimal(ref d, stateObj); break; case TdsEnums.SQLDATETIMN: Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length"); - TdsDateTime dt = MetaType.FromDateTime(ValueTypeConverter.Convert(ref value), (byte)type.FixedLength); + TdsDateTime dt = MetaType.FromDateTime(ValueTypeConverter.Convert(value), (byte)type.FixedLength); if (type.FixedLength == 4) { @@ -11566,13 +11566,13 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i case TdsEnums.SQLMONEYN: { - WriteCurrency(ValueTypeConverter.Convert(ref value), type.FixedLength, stateObj); + WriteCurrency(ValueTypeConverter.Convert(value), type.FixedLength, stateObj); break; } case TdsEnums.SQLDATE: { - WriteDate(ValueTypeConverter.Convert(ref value), stateObj); + WriteDate(ValueTypeConverter.Convert(value), stateObj); break; } @@ -11581,7 +11581,7 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i { throw SQL.TimeScaleValueOutOfRange(scale); } - WriteTime(ValueTypeConverter.Convert(ref value), scale, actualLength, stateObj); + WriteTime(ValueTypeConverter.Convert(value), scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIME2: @@ -11589,11 +11589,11 @@ private Task WriteUnterminatedValue(ref T value, MetaType type, byte scale, i { throw SQL.TimeScaleValueOutOfRange(scale); } - WriteDateTime2(ValueTypeConverter.Convert(ref value), scale, actualLength, stateObj); + WriteDateTime2(ValueTypeConverter.Convert(value), scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: - WriteDateTimeOffset(ValueTypeConverter.Convert(ref value), scale, actualLength, stateObj); + WriteDateTimeOffset(ValueTypeConverter.Convert(value), scale, actualLength, stateObj); break; default: @@ -11837,7 +11837,7 @@ private byte[] SerializeUnencryptedValue(object value, MetaType type, byte scale // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private byte[] SerializeUnencryptedSqlValue(ref T value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) + private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) { Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) || (value is INullable && !((INullable)value).IsNull)), @@ -11854,12 +11854,12 @@ private byte[] SerializeUnencryptedSqlValue(ref T value, MetaType type, int a case TdsEnums.SQLFLTN: if (type.FixedLength == 4) { - return SerializeFloat(ValueTypeConverter.Convert(ref value).Value); + return SerializeFloat(ValueTypeConverter.Convert(value).Value); } else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - return SerializeDouble(ValueTypeConverter.Convert(ref value).Value); + return SerializeDouble(ValueTypeConverter.Convert(value).Value); } case TdsEnums.SQLBIGBINARY: @@ -11870,20 +11870,20 @@ private byte[] SerializeUnencryptedSqlValue(ref T value, MetaType type, int a if (typeof(T) == typeof(SqlBinary)) { - Buffer.BlockCopy(ValueTypeConverter.Convert(ref value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(ValueTypeConverter.Convert(value).Value, offset, b, 0, actualLength); } else { Debug.Assert(value is SqlBytes); - Buffer.BlockCopy(ValueTypeConverter.Convert(ref value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(ValueTypeConverter.Convert(value).Value, offset, b, 0, actualLength); } - + return b; } case TdsEnums.SQLUNIQUEID: { - byte[] b = ValueTypeConverter.Convert(ref value).ToByteArray(); + byte[] b = ValueTypeConverter.Convert(value).ToByteArray(); Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); return b; @@ -11894,23 +11894,23 @@ private byte[] SerializeUnencryptedSqlValue(ref T value, MetaType type, int a Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); // We normalize to allow conversion across data types. BIT is serialized into a BIGINT. - return SerializeLong(ValueTypeConverter.Convert(ref value).Value == true ? 1 : 0, stateObj); + return SerializeLong(ValueTypeConverter.Convert(value).Value == true ? 1 : 0, stateObj); } case TdsEnums.SQLINTN: // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT. if (type.FixedLength == 1) - return SerializeLong(ValueTypeConverter.Convert(ref value).Value, stateObj); + return SerializeLong(ValueTypeConverter.Convert(value).Value, stateObj); if (type.FixedLength == 2) - return SerializeLong(ValueTypeConverter.Convert(ref value).Value, stateObj); + return SerializeLong(ValueTypeConverter.Convert(value).Value, stateObj); if (type.FixedLength == 4) - return SerializeLong(ValueTypeConverter.Convert(ref value).Value, stateObj); + return SerializeLong(ValueTypeConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - return SerializeLong(ValueTypeConverter.Convert(ref value).Value, stateObj); + return SerializeLong(ValueTypeConverter.Convert(value).Value, stateObj); } case TdsEnums.SQLBIGCHAR: @@ -11918,13 +11918,13 @@ private byte[] SerializeUnencryptedSqlValue(ref T value, MetaType type, int a case TdsEnums.SQLTEXT: if (value is SqlChars) { - String sch = new String(ValueTypeConverter.Convert(ref value).Value); + String sch = new String(ValueTypeConverter.Convert(value).Value); return SerializeEncodingChar(sch, actualLength, offset, _defaultEncoding); } else { Debug.Assert(value is SqlString); - return SerializeEncodingChar(ValueTypeConverter.Convert(ref value).Value, actualLength, offset, _defaultEncoding); + return SerializeEncodingChar(ValueTypeConverter.Convert(value).Value, actualLength, offset, _defaultEncoding); } @@ -11939,20 +11939,20 @@ private byte[] SerializeUnencryptedSqlValue(ref T value, MetaType type, int a if (value is SqlChars) { - return SerializeCharArray(ValueTypeConverter.Convert(ref value).Value, actualLength, offset); + return SerializeCharArray(ValueTypeConverter.Convert(value).Value, actualLength, offset); } else { Debug.Assert(value is SqlString); - return SerializeString(ValueTypeConverter.Convert(ref value).Value, actualLength, offset); + return SerializeString(ValueTypeConverter.Convert(value).Value, actualLength, offset); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - return SerializeSqlDecimal(ValueTypeConverter.Convert(ref value), stateObj); + return SerializeSqlDecimal(ValueTypeConverter.Convert(value), stateObj); case TdsEnums.SQLDATETIMN: - SqlDateTime dt = ValueTypeConverter.Convert(ref value); + SqlDateTime dt = ValueTypeConverter.Convert(value); if (type.FixedLength == 4) { @@ -11998,7 +11998,7 @@ private byte[] SerializeUnencryptedSqlValue(ref T value, MetaType type, int a case TdsEnums.SQLMONEYN: { - return SerializeSqlMoney(ValueTypeConverter.Convert(ref value), type.FixedLength, stateObj); + return SerializeSqlMoney(ValueTypeConverter.Convert(value), type.FixedLength, stateObj); } default: diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs index aceb98fd5f..0a53080bff 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs @@ -8,37 +8,9 @@ namespace Microsoft.Data.SqlClient { - /// - /// Delegate representing a converter to pass in by reference. - /// - /// - /// - /// - /// - public delegate TOut RefConverter(ref TIn input); - - /// - /// Converts value types from a generic to the desired, underlying type without boxing. - /// - /// - /// - public static class ValueTypeConverter + internal static class ValueTypeConverter { - /// - /// Converts the value to the TOut type - /// - public static readonly RefConverter Convert; - - static ValueTypeConverter() - { - var paramExpr = Expression.Parameter(typeof(TIn).MakeByRefType(), "input"); - - var lambda = typeof(TIn) != typeof(TOut) - ? Expression.Lambda>(Expression.Convert(paramExpr, typeof(TOut)), paramExpr) - : Expression.Lambda>(paramExpr, paramExpr) - ; - - Convert = lambda.Compile(); - } + // This leverages the same assumptions in SqlBuffer that the JIT will optimize out the boxing / unboxing when TIn == TOut + public static TOut Convert(TIn value) => (TOut)(object)value; } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverterBackup.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverterBackup.cs new file mode 100644 index 0000000000..359e5b80d7 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverterBackup.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient +{ + /// + /// Delegate representing a converter to pass in by reference. + /// + /// + /// + /// + /// + public delegate TOut RefConverter(ref TIn input); + + /// + /// Converts value types from a generic to the desired, underlying type without boxing. + /// + /// + /// + public static class ValueTypeConverterBackup + { + /// + /// Converts the value to the TOut type + /// + public static readonly RefConverter Convert; + + static ValueTypeConverterBackup() + { + var paramExpr = Expression.Parameter(typeof(TIn).MakeByRefType(), "input"); + + var lambda = typeof(TIn) != typeof(TOut) + ? Expression.Lambda>(Expression.Convert(paramExpr, typeof(TOut)), paramExpr) + : Expression.Lambda>(paramExpr, paramExpr) + ; + + Convert = lambda.Compile(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index c413700973..afcdea9b91 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -9,6 +9,7 @@ $(BinFolder)$(Configuration).FunctionalTests + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs index 9eb30d3043..6a91316f7c 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs @@ -15,7 +15,7 @@ public void ObjectConversionSuccessful() { object test = 0m; - decimal converted = ValueTypeConverter.Convert(ref test); + decimal converted = ValueTypeConverter.Convert(test); Assert.Equal(test, converted); } @@ -25,7 +25,7 @@ public void SelfConversionSuccessful() { decimal test = 0m; - decimal converted = ValueTypeConverter.Convert(ref test); + decimal converted = ValueTypeConverter.Convert(test); Assert.Equal(test, converted); } @@ -35,7 +35,7 @@ public void AssignableConversionSuccessful() { SqlDecimal test = 0m; - decimal converted = ValueTypeConverter.Convert(ref test); + decimal converted = ValueTypeConverter.Convert(test); Assert.Equal(test, converted); } From 020dbc823bd60dd20782246f133471f293a16911 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 31 Dec 2019 11:13:43 -0600 Subject: [PATCH 05/31] more PR feedback --- .../src/Microsoft.Data.SqlClient.csproj | 3 +- .../Data/Common/AdapterUtil.SqlClient.cs | 2 +- ...ueTypeConverter.cs => GenericConverter.cs} | 2 +- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 12 +- .../Microsoft/Data/SqlClient/SqlParameter.cs | 14 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 178 +++++++++--------- .../SqlClient/ValueTypeConverterBackup.cs | 44 ----- .../netfx/src/Microsoft.Data.SqlClient.csproj | 2 +- .../Data/SqlClient/GenericConverter.cs | 39 ++++ .../Data/SqlClient/ValueTypeConverter.cs | 44 ----- ...erterTests.cs => GenericConverterTests.cs} | 8 +- .../Microsoft.Data.SqlClient.Tests.csproj | 4 +- 12 files changed, 151 insertions(+), 201 deletions(-) rename src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/{ValueTypeConverter.cs => GenericConverter.cs} (90%) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverterBackup.cs create mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs rename src/Microsoft.Data.SqlClient/tests/FunctionalTests/{ValueTypeConverterTests.cs => GenericConverterTests.cs} (70%) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 043e46bd5b..3db8c8eced 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -579,8 +579,7 @@ - - + True True diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/AdapterUtil.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/AdapterUtil.SqlClient.cs index 35d6f1bd6a..eb32c6f5b8 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/AdapterUtil.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/AdapterUtil.SqlClient.cs @@ -703,7 +703,7 @@ private static void IsDirectionValid(ParameterDirection value) internal static void IsNullOrSqlType(object value, out bool isNull, out bool isSqlType) { - if ((value == null) || (value is DBNull)) + if ((value == null) || (value == DBNull.Value)) { isNull = true; isSqlType = false; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs similarity index 90% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs rename to src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs index 0a53080bff..7ff0e256f5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs @@ -8,7 +8,7 @@ namespace Microsoft.Data.SqlClient { - internal static class ValueTypeConverter + internal static class GenericConverter { // This leverages the same assumptions in SqlBuffer that the JIT will optimize out the boxing / unboxing when TIn == TOut public static TOut Convert(TIn value) => (TOut)(object)value; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 20f1b1c118..e0a96b6e22 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1496,11 +1496,11 @@ private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, case TdsEnums.SQLDECIMALN: if (typeof(T) == typeof(decimal)) { - decValue = new SqlDecimal(ValueTypeConverter.Convert(value)); + decValue = new SqlDecimal(GenericConverter.Convert(value)); } else if (typeof(T) == typeof(SqlDecimal)) { - decValue = ValueTypeConverter.Convert(value); + decValue = GenericConverter.Convert(value); } else { @@ -1571,8 +1571,8 @@ private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, string str = typeChanged ? (string)objValue : isSqlType - ? ValueTypeConverter.Convert(value).Value - : ValueTypeConverter.Convert(value) + ? GenericConverter.Convert(value).Value + : GenericConverter.Convert(value) ; int maxStringLength = length / 2; @@ -2244,11 +2244,11 @@ private Task DoWriteValueAsync(T value, int col, bool isSqlType, bool isDataF if (variantInternalType == SqlBuffer.StorageType.DateTime2 && value is DateTime) { - _parser.WriteSqlVariantDateTime2(ValueTypeConverter.Convert(value), _stateObj); + _parser.WriteSqlVariantDateTime2(GenericConverter.Convert(value), _stateObj); } else if (variantInternalType == SqlBuffer.StorageType.Date && value is DateTime d) { - _parser.WriteSqlVariantDate(ValueTypeConverter.Convert(value), _stateObj); + _parser.WriteSqlVariantDate(GenericConverter.Convert(value), _stateObj); } else { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index c0fdfb8b8e..f867a9b7be 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1031,7 +1031,7 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o // For Xml data, destination Type is always string if (typeof(SqlXml) == currentType) { - var xmlValue = ValueTypeConverter.Convert(value); + var xmlValue = GenericConverter.Convert(value); objValue = MetaType.GetStringFromXml(xmlValue.CreateReader()); } else if (typeof(SqlString) == currentType) @@ -1052,12 +1052,12 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o } else if (typeof(char[]) == currentType) { - var charArrayValue = ValueTypeConverter.Convert(value); + var charArrayValue = GenericConverter.Convert(value); objValue = new string(charArrayValue); } else if (typeof(SqlChars) == currentType) { - var sqlCharsValue = ValueTypeConverter.Convert(value); + var sqlCharsValue = GenericConverter.Convert(value); objValue = new string(sqlCharsValue.Value); } else if (value is TextReader tr && allowStreaming) @@ -1072,7 +1072,7 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o } else if ((DbType.Currency == destinationType.DbType) && (typeof(string) == currentType)) { - objValue = decimal.Parse(ValueTypeConverter.Convert(value), NumberStyles.Currency, (IFormatProvider)null); + objValue = decimal.Parse(GenericConverter.Convert(value), NumberStyles.Currency, (IFormatProvider)null); } else if ((typeof(SqlBytes) == currentType) && (typeof(byte[]) == destinationType.ClassType)) { @@ -1080,15 +1080,15 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o } else if ((typeof(string) == currentType) && (SqlDbType.Time == destinationType.SqlDbType)) { - objValue = TimeSpan.Parse(ValueTypeConverter.Convert(value)); + objValue = TimeSpan.Parse(GenericConverter.Convert(value)); } else if ((typeof(string) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) { - objValue = DateTimeOffset.Parse(ValueTypeConverter.Convert(value), (IFormatProvider)null); + objValue = DateTimeOffset.Parse(GenericConverter.Convert(value), (IFormatProvider)null); } else if ((typeof(DateTime) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) { - objValue = new DateTimeOffset(ValueTypeConverter.Convert(value)); + objValue = new DateTimeOffset(GenericConverter.Convert(value)); } else if (TdsEnums.SQLTABLE == destinationType.TDSType && ( value is DataTable || diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index a6eff9f265..df0c64eaeb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -6443,44 +6443,44 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state if (metatype.IsAnsiType && value is string) { - length = GetEncodingCharLength(ValueTypeConverter.Convert(value), length, 0, _defaultEncoding); + length = GetEncodingCharLength(GenericConverter.Convert(value), length, 0, _defaultEncoding); } switch (metatype.TDSType) { case TdsEnums.SQLFLT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteFloat(ValueTypeConverter.Convert(value), stateObj); + WriteFloat(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLFLT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteDouble(ValueTypeConverter.Convert(value), stateObj); + WriteDouble(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteLong(ValueTypeConverter.Convert(value), stateObj); + WriteLong(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteInt(ValueTypeConverter.Convert(value), stateObj); + WriteInt(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT2: WriteSqlVariantHeader(4, metatype.TDSType, metatype.PropBytes, stateObj); - WriteShort(ValueTypeConverter.Convert(value), stateObj); + WriteShort(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT1: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); - stateObj.WriteByte(ValueTypeConverter.Convert(value)); + stateObj.WriteByte(GenericConverter.Convert(value)); break; case TdsEnums.SQLBIT: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); - if (ValueTypeConverter.Convert(value)) + if (GenericConverter.Convert(value)) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -6489,7 +6489,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state case TdsEnums.SQLBIGVARBINARY: { - byte[] b = ValueTypeConverter.Convert(value); + byte[] b = GenericConverter.Convert(value); length = b.Length; WriteSqlVariantHeader(4 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6499,7 +6499,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state case TdsEnums.SQLBIGVARCHAR: { - string s = ValueTypeConverter.Convert(value); + string s = GenericConverter.Convert(value); length = s.Length; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6511,7 +6511,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state case TdsEnums.SQLUNIQUEID: { - Guid guid = ValueTypeConverter.Convert(value); + Guid guid = GenericConverter.Convert(value); Span b = stackalloc byte[16]; FillGuidBytes(guid, b); @@ -6524,7 +6524,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state case TdsEnums.SQLNVARCHAR: { - string s = ValueTypeConverter.Convert(value); + string s = GenericConverter.Convert(value); length = s.Length * 2; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -6539,7 +6539,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state case TdsEnums.SQLDATETIME: { - TdsDateTime dt = MetaType.FromDateTime(ValueTypeConverter.Convert(value), 8); + TdsDateTime dt = MetaType.FromDateTime(GenericConverter.Convert(value), 8); WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); WriteInt(dt.days, stateObj); @@ -6550,7 +6550,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state case TdsEnums.SQLMONEY: { WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteCurrency(ValueTypeConverter.Convert(value), 8, stateObj); + WriteCurrency(GenericConverter.Convert(value), 8, stateObj); break; } @@ -6558,7 +6558,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state { WriteSqlVariantHeader(21, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Precision); //propbytes: precision - var decValue = ValueTypeConverter.Convert(value); + var decValue = GenericConverter.Convert(value); stateObj.WriteByte((byte)((decimal.GetBits(decValue)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale WriteDecimal(ref decValue, stateObj); break; @@ -6567,13 +6567,13 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state case TdsEnums.SQLTIME: WriteSqlVariantHeader(8, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale - WriteTime(ValueTypeConverter.Convert(value), metatype.Scale, 5, stateObj); + WriteTime(GenericConverter.Convert(value), metatype.Scale, 5, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: WriteSqlVariantHeader(13, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale - WriteDateTimeOffset(ValueTypeConverter.Convert(value), metatype.Scale, 10, stateObj); + WriteDateTimeOffset(GenericConverter.Convert(value), metatype.Scale, 10, stateObj); break; default: @@ -10146,8 +10146,8 @@ internal byte[] EncryptColumnValue(T value, SqlMetaDataPriv metadata, string // to report the size of data to be copied out (for serialization). If we underreport the // size, truncation will happen for us! actualLengthInBytes = (isSqlType) - ? ValueTypeConverter.Convert(value).Length - : ValueTypeConverter.Convert(value).Length; + ? GenericConverter.Convert(value).Length + : GenericConverter.Convert(value).Length; if (metadata.baseTI.length > 0 && actualLengthInBytes > metadata.baseTI.length) @@ -10169,8 +10169,8 @@ internal byte[] EncryptColumnValue(T value, SqlMetaDataPriv metadata, string } string stringValue = (isSqlType) - ? ValueTypeConverter.Convert(value).Value - : ValueTypeConverter.Convert(value); + ? GenericConverter.Convert(value).Value + : GenericConverter.Convert(value); actualLengthInBytes = _defaultEncoding.GetByteCount(stringValue); @@ -10186,8 +10186,8 @@ internal byte[] EncryptColumnValue(T value, SqlMetaDataPriv metadata, string case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: actualLengthInBytes = (isSqlType - ? ValueTypeConverter.Convert(value).Value.Length - : ValueTypeConverter.Convert(value).Length) + ? GenericConverter.Convert(value).Value.Length + : GenericConverter.Convert(value).Length) * 2; if (metadata.baseTI.length > 0 && @@ -10300,8 +10300,8 @@ internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParser case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: ccb = (isSqlType) - ? ValueTypeConverter.Convert(value).Length - : ValueTypeConverter.Convert(value).Length; + ? GenericConverter.Convert(value).Length + : GenericConverter.Convert(value).Length; break; case TdsEnums.SQLUNIQUEID: ccb = GUID_SIZE; @@ -10317,11 +10317,11 @@ internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParser string stringValue = null; if (isSqlType) { - stringValue = ValueTypeConverter.Convert(value).Value; + stringValue = GenericConverter.Convert(value).Value; } else { - stringValue = ValueTypeConverter.Convert(value); + stringValue = GenericConverter.Convert(value); } ccb = stringValue.Length; @@ -10331,8 +10331,8 @@ internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParser case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: ccb = (isSqlType - ? ValueTypeConverter.Convert(value).Value.Length - : ValueTypeConverter.Convert(value).Length + ? GenericConverter.Convert(value).Value.Length + : GenericConverter.Convert(value).Length ) * 2; break; case TdsEnums.SQLXMLTYPE: @@ -10342,8 +10342,8 @@ internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParser objValue = MetaType.GetStringFromXml(xr); } ccb = (isSqlType - ? ValueTypeConverter.Convert(value).Value.Length - : ValueTypeConverter.Convert(value).Length + ? GenericConverter.Convert(value).Value.Length + : GenericConverter.Convert(value).Length ) * 2; break; @@ -10411,7 +10411,7 @@ internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParser else { WriteShort(ccb, stateObj); - internalWriteTask = stateObj.WriteByteArray(ValueTypeConverter.Convert(value), ccb, 0); + internalWriteTask = stateObj.WriteByteArray(GenericConverter.Convert(value), ccb, 0); } #if DEBUG @@ -10744,11 +10744,11 @@ private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLeng { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat(ValueTypeConverter.Convert(value).Value, stateObj); + WriteFloat(GenericConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble(ValueTypeConverter.Convert(value).Value, stateObj); + WriteDouble(GenericConverter.Convert(value).Value, stateObj); } break; @@ -10764,12 +10764,12 @@ private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLeng if (value is SqlBinary) { - return stateObj.WriteByteArray(ValueTypeConverter.Convert(value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value).Value, actualLength, offset, canAccumulate: false); } else { Debug.Assert(value is SqlBytes); - return stateObj.WriteByteArray(ValueTypeConverter.Convert(value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value).Value, actualLength, offset, canAccumulate: false); } } @@ -10777,7 +10777,7 @@ private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLeng { Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); Span b = stackalloc byte[16]; - SqlGuid sqlGuid = ValueTypeConverter.Convert(value); + SqlGuid sqlGuid = GenericConverter.Convert(value); if (sqlGuid.IsNull) { @@ -10795,7 +10795,7 @@ private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLeng case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); - if (ValueTypeConverter.Convert(value).Value == true) + if (GenericConverter.Convert(value).Value == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -10805,17 +10805,17 @@ private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLeng case TdsEnums.SQLINTN: if (type.FixedLength == 1) - stateObj.WriteByte(ValueTypeConverter.Convert(value).Value); + stateObj.WriteByte(GenericConverter.Convert(value).Value); else if (type.FixedLength == 2) - WriteShort(ValueTypeConverter.Convert(value).Value, stateObj); + WriteShort(GenericConverter.Convert(value).Value, stateObj); else if (type.FixedLength == 4) - WriteInt(ValueTypeConverter.Convert(value).Value, stateObj); + WriteInt(GenericConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong(ValueTypeConverter.Convert(value).Value, stateObj); + WriteLong(GenericConverter.Convert(value).Value, stateObj); } break; @@ -10829,14 +10829,14 @@ private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLeng } if (value is SqlChars) { - string sch = new string(ValueTypeConverter.Convert(value).Value); + string sch = new string(GenericConverter.Convert(value).Value); return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); - return WriteEncodingChar(ValueTypeConverter.Convert(value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); + return WriteEncodingChar(GenericConverter.Convert(value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } @@ -10865,21 +10865,21 @@ private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLeng if (value is SqlChars) { - return WriteCharArray(ValueTypeConverter.Convert(value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteCharArray(GenericConverter.Convert(value).Value, actualLength, offset, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); - return WriteString(ValueTypeConverter.Convert(value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteString(GenericConverter.Convert(value).Value, actualLength, offset, stateObj, canAccumulate: false); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - WriteSqlDecimal(ValueTypeConverter.Convert(value), stateObj); + WriteSqlDecimal(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLDATETIMN: - SqlDateTime dt = ValueTypeConverter.Convert(value); + SqlDateTime dt = GenericConverter.Convert(value); if (type.FixedLength == 4) { @@ -10899,7 +10899,7 @@ private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLeng case TdsEnums.SQLMONEYN: { - WriteSqlMoney(ValueTypeConverter.Convert(value), type.FixedLength, stateObj); + WriteSqlMoney(GenericConverter.Convert(value), type.FixedLength, stateObj); break; } @@ -11386,11 +11386,11 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat(ValueTypeConverter.Convert(value), stateObj); + WriteFloat(GenericConverter.Convert(value), stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble(ValueTypeConverter.Convert(value), stateObj); + WriteDouble(GenericConverter.Convert(value), stateObj); } break; @@ -11407,7 +11407,7 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a if (isDataFeed) { Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!"); - return NullIfCompletedWriteTask(WriteStreamFeed(ValueTypeConverter.Convert(value), stateObj, paramSize)); + return NullIfCompletedWriteTask(WriteStreamFeed(GenericConverter.Convert(value), stateObj, paramSize)); } else { @@ -11415,7 +11415,7 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a { WriteInt(actualLength, stateObj); // chunk length } - return stateObj.WriteByteArray(ValueTypeConverter.Convert(value), actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value), actualLength, offset, canAccumulate: false); } } @@ -11423,7 +11423,7 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a { Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); Span b = stackalloc byte[16]; - FillGuidBytes(ValueTypeConverter.Convert(value), b); + FillGuidBytes(GenericConverter.Convert(value), b); stateObj.WriteByteSpan(b); break; } @@ -11431,7 +11431,7 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); - if (ValueTypeConverter.Convert(value) == true) + if (GenericConverter.Convert(value) == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -11441,15 +11441,15 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a case TdsEnums.SQLINTN: if (type.FixedLength == 1) - stateObj.WriteByte(ValueTypeConverter.Convert(value)); + stateObj.WriteByte(GenericConverter.Convert(value)); else if (type.FixedLength == 2) - WriteShort(ValueTypeConverter.Convert(value), stateObj); + WriteShort(GenericConverter.Convert(value), stateObj); else if (type.FixedLength == 4) - WriteInt(ValueTypeConverter.Convert(value), stateObj); + WriteInt(GenericConverter.Convert(value), stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong(ValueTypeConverter.Convert(value), stateObj); + WriteLong(GenericConverter.Convert(value), stateObj); } break; @@ -11467,7 +11467,7 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { - return NullIfCompletedWriteTask(WriteXmlFeed(ValueTypeConverter.Convert(value), stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); + return NullIfCompletedWriteTask(WriteXmlFeed(GenericConverter.Convert(value), stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); } else { @@ -11482,11 +11482,11 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value - return stateObj.WriteByteArray(ValueTypeConverter.Convert(value), actualLength, 0, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value), actualLength, 0, canAccumulate: false); } else { - return WriteEncodingChar(ValueTypeConverter.Convert(value), actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); + return WriteEncodingChar(GenericConverter.Convert(value), actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } } } @@ -11504,7 +11504,7 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { - return NullIfCompletedWriteTask(WriteXmlFeed(ValueTypeConverter.Convert(value), stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); + return NullIfCompletedWriteTask(WriteXmlFeed(GenericConverter.Convert(value), stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); } else { @@ -11527,26 +11527,26 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value - return stateObj.WriteByteArray(ValueTypeConverter.Convert(value), actualLength, 0, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value), actualLength, 0, canAccumulate: false); } else { // convert to cchars instead of cbytes actualLength >>= 1; - return WriteString(ValueTypeConverter.Convert(value), actualLength, offset, stateObj, canAccumulate: false); + return WriteString(GenericConverter.Convert(value), actualLength, offset, stateObj, canAccumulate: false); } } } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - var d = ValueTypeConverter.Convert(value); + var d = GenericConverter.Convert(value); WriteDecimal(ref d, stateObj); break; case TdsEnums.SQLDATETIMN: Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length"); - TdsDateTime dt = MetaType.FromDateTime(ValueTypeConverter.Convert(value), (byte)type.FixedLength); + TdsDateTime dt = MetaType.FromDateTime(GenericConverter.Convert(value), (byte)type.FixedLength); if (type.FixedLength == 4) { @@ -11566,13 +11566,13 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a case TdsEnums.SQLMONEYN: { - WriteCurrency(ValueTypeConverter.Convert(value), type.FixedLength, stateObj); + WriteCurrency(GenericConverter.Convert(value), type.FixedLength, stateObj); break; } case TdsEnums.SQLDATE: { - WriteDate(ValueTypeConverter.Convert(value), stateObj); + WriteDate(GenericConverter.Convert(value), stateObj); break; } @@ -11581,7 +11581,7 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a { throw SQL.TimeScaleValueOutOfRange(scale); } - WriteTime(ValueTypeConverter.Convert(value), scale, actualLength, stateObj); + WriteTime(GenericConverter.Convert(value), scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIME2: @@ -11589,11 +11589,11 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a { throw SQL.TimeScaleValueOutOfRange(scale); } - WriteDateTime2(ValueTypeConverter.Convert(value), scale, actualLength, stateObj); + WriteDateTime2(GenericConverter.Convert(value), scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: - WriteDateTimeOffset(ValueTypeConverter.Convert(value), scale, actualLength, stateObj); + WriteDateTimeOffset(GenericConverter.Convert(value), scale, actualLength, stateObj); break; default: @@ -11854,12 +11854,12 @@ private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actua case TdsEnums.SQLFLTN: if (type.FixedLength == 4) { - return SerializeFloat(ValueTypeConverter.Convert(value).Value); + return SerializeFloat(GenericConverter.Convert(value).Value); } else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - return SerializeDouble(ValueTypeConverter.Convert(value).Value); + return SerializeDouble(GenericConverter.Convert(value).Value); } case TdsEnums.SQLBIGBINARY: @@ -11870,12 +11870,12 @@ private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actua if (typeof(T) == typeof(SqlBinary)) { - Buffer.BlockCopy(ValueTypeConverter.Convert(value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(GenericConverter.Convert(value).Value, offset, b, 0, actualLength); } else { Debug.Assert(value is SqlBytes); - Buffer.BlockCopy(ValueTypeConverter.Convert(value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(GenericConverter.Convert(value).Value, offset, b, 0, actualLength); } return b; @@ -11883,7 +11883,7 @@ private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actua case TdsEnums.SQLUNIQUEID: { - byte[] b = ValueTypeConverter.Convert(value).ToByteArray(); + byte[] b = GenericConverter.Convert(value).ToByteArray(); Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); return b; @@ -11894,23 +11894,23 @@ private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actua Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); // We normalize to allow conversion across data types. BIT is serialized into a BIGINT. - return SerializeLong(ValueTypeConverter.Convert(value).Value == true ? 1 : 0, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value == true ? 1 : 0, stateObj); } case TdsEnums.SQLINTN: // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT. if (type.FixedLength == 1) - return SerializeLong(ValueTypeConverter.Convert(value).Value, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value, stateObj); if (type.FixedLength == 2) - return SerializeLong(ValueTypeConverter.Convert(value).Value, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value, stateObj); if (type.FixedLength == 4) - return SerializeLong(ValueTypeConverter.Convert(value).Value, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - return SerializeLong(ValueTypeConverter.Convert(value).Value, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value, stateObj); } case TdsEnums.SQLBIGCHAR: @@ -11918,13 +11918,13 @@ private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actua case TdsEnums.SQLTEXT: if (value is SqlChars) { - String sch = new String(ValueTypeConverter.Convert(value).Value); + String sch = new String(GenericConverter.Convert(value).Value); return SerializeEncodingChar(sch, actualLength, offset, _defaultEncoding); } else { Debug.Assert(value is SqlString); - return SerializeEncodingChar(ValueTypeConverter.Convert(value).Value, actualLength, offset, _defaultEncoding); + return SerializeEncodingChar(GenericConverter.Convert(value).Value, actualLength, offset, _defaultEncoding); } @@ -11939,20 +11939,20 @@ private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actua if (value is SqlChars) { - return SerializeCharArray(ValueTypeConverter.Convert(value).Value, actualLength, offset); + return SerializeCharArray(GenericConverter.Convert(value).Value, actualLength, offset); } else { Debug.Assert(value is SqlString); - return SerializeString(ValueTypeConverter.Convert(value).Value, actualLength, offset); + return SerializeString(GenericConverter.Convert(value).Value, actualLength, offset); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - return SerializeSqlDecimal(ValueTypeConverter.Convert(value), stateObj); + return SerializeSqlDecimal(GenericConverter.Convert(value), stateObj); case TdsEnums.SQLDATETIMN: - SqlDateTime dt = ValueTypeConverter.Convert(value); + SqlDateTime dt = GenericConverter.Convert(value); if (type.FixedLength == 4) { @@ -11998,7 +11998,7 @@ private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actua case TdsEnums.SQLMONEYN: { - return SerializeSqlMoney(ValueTypeConverter.Convert(value), type.FixedLength, stateObj); + return SerializeSqlMoney(GenericConverter.Convert(value), type.FixedLength, stateObj); } default: diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverterBackup.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverterBackup.cs deleted file mode 100644 index 359e5b80d7..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/ValueTypeConverterBackup.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlTypes; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Data.SqlClient -{ - /// - /// Delegate representing a converter to pass in by reference. - /// - /// - /// - /// - /// - public delegate TOut RefConverter(ref TIn input); - - /// - /// Converts value types from a generic to the desired, underlying type without boxing. - /// - /// - /// - public static class ValueTypeConverterBackup - { - /// - /// Converts the value to the TOut type - /// - public static readonly RefConverter Convert; - - static ValueTypeConverterBackup() - { - var paramExpr = Expression.Parameter(typeof(TIn).MakeByRefType(), "input"); - - var lambda = typeof(TIn) != typeof(TOut) - ? Expression.Lambda>(Expression.Convert(paramExpr, typeof(TOut)), paramExpr) - : Expression.Lambda>(paramExpr, paramExpr) - ; - - Convert = lambda.Compile(); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index baee06db89..14d0531c74 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -100,6 +100,7 @@ + @@ -117,7 +118,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs new file mode 100644 index 0000000000..1053ac6e87 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient +{ + internal static class GenericConverter + { + public static TOut Convert(TIn value) + { + return GenericConverterHelper.Convert(value); + } + + private static class GenericConverterHelper + { + /// + /// Converts the value to the TOut type + /// + public static readonly Func Convert; + + static GenericConverterHelper() + { + var paramExpr = Expression.Parameter(typeof(TIn), "input"); + + var lambda = typeof(TIn) != typeof(TOut) + ? Expression.Lambda>(Expression.Convert(paramExpr, typeof(TOut)), paramExpr) + : Expression.Lambda>(paramExpr, paramExpr) + ; + + Convert = lambda.Compile(); + } + } + } + +} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs deleted file mode 100644 index aceb98fd5f..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/ValueTypeConverter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlTypes; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Data.SqlClient -{ - /// - /// Delegate representing a converter to pass in by reference. - /// - /// - /// - /// - /// - public delegate TOut RefConverter(ref TIn input); - - /// - /// Converts value types from a generic to the desired, underlying type without boxing. - /// - /// - /// - public static class ValueTypeConverter - { - /// - /// Converts the value to the TOut type - /// - public static readonly RefConverter Convert; - - static ValueTypeConverter() - { - var paramExpr = Expression.Parameter(typeof(TIn).MakeByRefType(), "input"); - - var lambda = typeof(TIn) != typeof(TOut) - ? Expression.Lambda>(Expression.Convert(paramExpr, typeof(TOut)), paramExpr) - : Expression.Lambda>(paramExpr, paramExpr) - ; - - Convert = lambda.Compile(); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs similarity index 70% rename from src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs rename to src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs index 6a91316f7c..dcf67c25b7 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/ValueTypeConverterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs @@ -8,14 +8,14 @@ namespace Microsoft.Data.SqlClient.Tests { - public class ValueTypeConverterTests + public class GenericConverterTests { [Fact] public void ObjectConversionSuccessful() { object test = 0m; - decimal converted = ValueTypeConverter.Convert(test); + decimal converted = GenericConverter.Convert(test); Assert.Equal(test, converted); } @@ -25,7 +25,7 @@ public void SelfConversionSuccessful() { decimal test = 0m; - decimal converted = ValueTypeConverter.Convert(test); + decimal converted = GenericConverter.Convert(test); Assert.Equal(test, converted); } @@ -35,7 +35,7 @@ public void AssignableConversionSuccessful() { SqlDecimal test = 0m; - decimal converted = ValueTypeConverter.Convert(test); + decimal converted = GenericConverter.Convert(test); Assert.Equal(test, converted); } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index afcdea9b91..504647a7e2 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -9,7 +9,7 @@ $(BinFolder)$(Configuration).FunctionalTests - + @@ -48,7 +48,7 @@ - + From ec7d01e30801d64f2b08dd6d451e417c134bf7fb Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 31 Dec 2019 12:59:27 -0600 Subject: [PATCH 06/31] moving type resolution outside of hot path --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 154 +++++++++++++----- 1 file changed, 117 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index e0a96b6e22..b2d77a0852 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -137,7 +137,8 @@ private enum ValueSourceType } // Enum for specifying SqlDataReader.Get method used - private enum ValueMethod : byte + // TODO -- should this be Flags? + private enum ValueMethod { GetValue, SqlTypeSqlDecimal, @@ -145,7 +146,19 @@ private enum ValueMethod : byte SqlTypeSqlSingle, DataFeedStream, DataFeedText, - DataFeedXml + DataFeedXml, + GetInt32, + GetString, + GetDouble, + GetDecimal, + GetInt16, + GetInt64, + GetChar, + GetByte, + GetBoolean, + GetDateTime, + GetGuid, + GetFloat } // Used to hold column metadata for SqlDataReader case @@ -539,6 +552,9 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i StringBuilder updateBulkCommandText = new StringBuilder(); + bool isDataReader = _rowSourceType == ValueSourceType.IDataReader + || _rowSourceType == ValueSourceType.DbDataReader; + if (0 == internalResults[CollationResultId].Count) { throw SQL.BulkLoadNoCollation(); @@ -960,42 +976,45 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) } else { - var fieldType = r.GetFieldType(sourceOrdinal); - - var typeCode = fieldType != null && !r.IsDBNull(sourceOrdinal) - ? Type.GetTypeCode(fieldType) //TODO can be optimized out - : TypeCode.Empty; - - switch (typeCode) + if (_currentRowMetadata[sourceOrdinal].Method == ValueMethod.GetValue || r.IsDBNull(sourceOrdinal)) { - case TypeCode.Int32: - return WriteValueAsync(r.GetInt32(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.String: - return WriteValueAsync(r.GetString(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Double: - return WriteValueAsync(r.GetDouble(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Decimal: - return WriteValueAsync(r.GetDecimal(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Int16: - return WriteValueAsync(r.GetInt16(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Int64: - return WriteValueAsync(r.GetInt64(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Char: - return WriteValueAsync(r.GetChar(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Byte: - return WriteValueAsync(r.GetByte(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Boolean: - return WriteValueAsync(r.GetBoolean(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.DateTime: - return WriteValueAsync(r.GetDateTime(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Object when fieldType == typeof(Guid): - return WriteValueAsync(r.GetGuid(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - case TypeCode.Object when fieldType == typeof(float): - return WriteValueAsync(r.GetFloat(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); - default: - object columnValue = r.GetValue(sourceOrdinal); - ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + object columnValue = r.GetValue(sourceOrdinal); + ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); + return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + } + else + { + switch (_currentRowMetadata[sourceOrdinal].Method) + { + case ValueMethod.GetInt32: + return WriteValueAsync(r.GetInt32(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetString: + return WriteValueAsync(r.GetString(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetDouble: + return WriteValueAsync(r.GetDouble(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetDecimal: + return WriteValueAsync(r.GetDecimal(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetInt16: + return WriteValueAsync(r.GetInt16(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetInt64: + return WriteValueAsync(r.GetInt64(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetChar: + return WriteValueAsync(r.GetChar(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetByte: + return WriteValueAsync(r.GetByte(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetBoolean: + return WriteValueAsync(r.GetBoolean(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetDateTime: + return WriteValueAsync(r.GetDateTime(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetGuid: + return WriteValueAsync(r.GetGuid(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetFloat: + return WriteValueAsync(r.GetFloat(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + default: + object columnValue = r.GetValue(sourceOrdinal); + ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); + return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + } } } } @@ -1275,8 +1294,69 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) method = ValueMethod.GetValue; } } + else if (_rowSourceType == ValueSourceType.IDataReader) + { + isSqlType = false; + isDataFeed = false; + + Type t = ((IDataReader)_rowSource).GetFieldType(ordinal); + + if (t == typeof(bool)) + { + method = ValueMethod.GetBoolean; + } + else if (t == typeof(byte)) + { + method = ValueMethod.GetByte; + } + else if (t == typeof(char)) + { + method = ValueMethod.GetChar; + } + else if (t == typeof(DateTime)) + { + method = ValueMethod.GetDateTime; + } + else if (t == typeof(decimal)) + { + method = ValueMethod.GetDecimal; + } + else if (t == typeof(double)) + { + method = ValueMethod.GetDouble; + } + else if (t == typeof(float)) + { + method = ValueMethod.GetFloat; + } + else if (t == typeof(Guid)) + { + method = ValueMethod.GetGuid; + } + else if (t == typeof(short)) + { + method = ValueMethod.GetInt16; + } + else if (t == typeof(int)) + { + method = ValueMethod.GetInt32; + } + else if (t == typeof(long)) + { + method = ValueMethod.GetInt64; + } + else if (t == typeof(string)) + { + method = ValueMethod.GetString; + } + else + { + method = ValueMethod.GetValue; + } + } else { + isSqlType = false; isDataFeed = false; method = ValueMethod.GetValue; From 795e4ff0724ea3f14feaf9da404a574a99235c86 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Thu, 2 Jan 2020 12:43:23 -0600 Subject: [PATCH 07/31] fixing test --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 27 +++++++------------ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 17 ++++++------ 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index b2d77a0852..82c4377e03 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -552,9 +552,6 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i StringBuilder updateBulkCommandText = new StringBuilder(); - bool isDataReader = _rowSourceType == ValueSourceType.IDataReader - || _rowSourceType == ValueSourceType.DbDataReader; - if (0 == internalResults[CollationResultId].Count) { throw SQL.BulkLoadNoCollation(); @@ -892,25 +889,21 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) if (_DbDataReaderRowSource.IsDBNull(sourceOrdinal)) { isNull = true; - var dbnull = DBNull.Value; - return WriteValueAsync(dbnull, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(DBNull.Value, destRowIndex, isSqlType, isDataFeed, isNull); } else { switch (_currentRowMetadata[destRowIndex].Method) { case ValueMethod.DataFeedStream: - var stream = new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)); - return WriteValueAsync(stream, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.DataFeedText: - var text = new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)); - return WriteValueAsync(text, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.DataFeedXml: // Only SqlDataReader supports an XmlReader // There is no GetXmlReader on DbDataReader, however if GetValue returns XmlReader we will read it as stream if it is assigned to XML field Debug.Assert(_SqlDataReaderRowSource != null, "Should not be reading row as an XmlReader if bulk copy source is not a SqlDataReader"); - var xml = new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)); - return WriteValueAsync(xml, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)), destRowIndex, isSqlType, isDataFeed, isNull); default: Debug.Fail($"Current column is marked as being a DataFeed, but no DataFeed compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); isDataFeed = false; @@ -1047,8 +1040,7 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) if (!float.IsNaN(f)) { isSqlType = true; - var sqlDec = new SqlDecimal(f); - return WriteValueAsync(sqlDec, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(new SqlDecimal(f), destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -1069,8 +1061,7 @@ private Task ReadWriteValueGenericAsync(int destRowIndex) if (!double.IsNaN(d)) { isSqlType = true; - var sqlValue = new SqlDecimal(d); - return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(new SqlDecimal(d), destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -2322,17 +2313,17 @@ private Task DoWriteValueAsync(T value, int col, bool isSqlType, bool isDataF variantInternalType = _SqlDataReaderRowSource.GetVariantInternalStorageType(_sortedColumnMappings[col]._sourceColumnOrdinal); } - if (variantInternalType == SqlBuffer.StorageType.DateTime2 && value is DateTime) + if (variantInternalType == SqlBuffer.StorageType.DateTime2) { _parser.WriteSqlVariantDateTime2(GenericConverter.Convert(value), _stateObj); } - else if (variantInternalType == SqlBuffer.StorageType.Date && value is DateTime d) + else if (variantInternalType == SqlBuffer.StorageType.Date) { _parser.WriteSqlVariantDate(GenericConverter.Convert(value), _stateObj); } else { - writeTask = _parser.WriteSqlVariantDataRowValue(value, _stateObj); //returns Task/Null + writeTask = _parser.WriteSqlVariantDataRowValue(value, isNull, _stateObj); //returns Task/Null } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index df0c64eaeb..1640628182 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -6394,7 +6394,7 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars stateObj.WriteByte(mt.Precision); //propbytes: precision stateObj.WriteByte((byte)((decimal.GetBits((decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale var d = (decimal)value; - WriteDecimal(ref d, stateObj); + WriteDecimal(d, stateObj); break; } @@ -6429,10 +6429,10 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars // Therefore the sql_variant value must not include the MaxLength. This is the major difference // between this method and WriteSqlVariantValue above. // - internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject stateObj, bool canAccumulate = true) + internal Task WriteSqlVariantDataRowValue(T value, bool isNull, TdsParserStateObject stateObj, bool canAccumulate = true) { // handle null values - if ((null == value) || typeof(T) == typeof(DBNull)) + if (isNull) { WriteInt(TdsEnums.FIXEDNULL, stateObj); return null; @@ -6441,7 +6441,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state MetaType metatype = MetaType.GetMetaTypeFromValue(value); int length = 0; - if (metatype.IsAnsiType && value is string) + if (metatype.IsAnsiType) { length = GetEncodingCharLength(GenericConverter.Convert(value), length, 0, _defaultEncoding); } @@ -6560,7 +6560,7 @@ internal Task WriteSqlVariantDataRowValue(T value, TdsParserStateObject state stateObj.WriteByte(metatype.Precision); //propbytes: precision var decValue = GenericConverter.Convert(value); stateObj.WriteByte((byte)((decimal.GetBits(decValue)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale - WriteDecimal(ref decValue, stateObj); + WriteDecimal(decValue, stateObj); break; } @@ -6994,7 +6994,7 @@ struct { return bytes; } - private void WriteDecimal(ref decimal value, TdsParserStateObject stateObj) + private void WriteDecimal(decimal value, TdsParserStateObject stateObj) { stateObj._decimalBits = decimal.GetBits(value); Debug.Assert(null != stateObj._decimalBits, "decimalBits should be filled in at TdsExecuteRPC time"); @@ -11539,8 +11539,7 @@ private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int a } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - var d = GenericConverter.Convert(value); - WriteDecimal(ref d, stateObj); + WriteDecimal(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLDATETIMN: @@ -11868,7 +11867,7 @@ private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actua { byte[] b = new byte[actualLength]; - if (typeof(T) == typeof(SqlBinary)) + if (value is SqlBinary) { Buffer.BlockCopy(GenericConverter.Convert(value).Value, offset, b, 0, actualLength); } From 76bcbcff507551e14f30673696ab9f574df02e46 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Fri, 3 Jan 2020 09:21:16 -0600 Subject: [PATCH 08/31] pr feedback --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 82c4377e03..2b6f2e0438 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -136,29 +136,29 @@ private enum ValueSourceType DbDataReader } - // Enum for specifying SqlDataReader.Get method used - // TODO -- should this be Flags? + // Enum for specifying SqlDataReader.Get / IDataReader Get method used + [Flags] private enum ValueMethod { - GetValue, - SqlTypeSqlDecimal, - SqlTypeSqlDouble, - SqlTypeSqlSingle, - DataFeedStream, - DataFeedText, - DataFeedXml, - GetInt32, - GetString, - GetDouble, - GetDecimal, - GetInt16, - GetInt64, - GetChar, - GetByte, - GetBoolean, - GetDateTime, - GetGuid, - GetFloat + GetValue = 0, + SqlTypeSqlDecimal = 1, + SqlTypeSqlDouble = 1 << 1, + SqlTypeSqlSingle = 1 << 2, + DataFeedStream = 1 << 3, + DataFeedText = 1 << 4, + DataFeedXml = 1 << 5, + GetInt32 = 1 << 6, + GetString = 1 << 7, + GetDouble = 1 << 8, + GetDecimal = 1 << 9, + GetInt16 = 1 << 10, + GetInt64 = 1 << 11, + GetChar = 1 << 12, + GetByte = 1 << 13, + GetBoolean = 1 << 14, + GetDateTime = 1 << 15, + GetGuid = 1 << 16, + GetFloat = 1 << 17 } // Used to hold column metadata for SqlDataReader case @@ -869,8 +869,11 @@ private void Dispose(bool disposing) } } - // Unified method to read a value from the current row - private Task ReadWriteValueGenericAsync(int destRowIndex) + // Reads a cell and then writes it. + // Read may block at this moment since there is no getValueAsync or DownStream async at this moment. + // When _isAsyncBulkCopy == true: Write will return Task (when async method runs asynchronously) or Null (when async call actually ran synchronously) for performance. + // When _isAsyncBulkCopy == false: Writes are purely sync. This method return null at the end. + private Task ReadWriteColumnValueAsync(int destRowIndex) { _SqlMetaData metadata = _sortedColumnMappings[destRowIndex]._metadata; int sourceOrdinal = _sortedColumnMappings[destRowIndex]._sourceColumnOrdinal; @@ -2252,17 +2255,6 @@ private bool FireRowsCopiedEvent(long rowsCopied) return eventArgs.Abort; } - // Reads a cell and then writes it. - // Read may block at this moment since there is no getValueAsync or DownStream async at this moment. - // When _isAsyncBulkCopy == true: Write will return Task (when async method runs asynchronously) or Null (when async call actually ran synchronously) for performance. - // When _isAsyncBulkCopy == false: Writes are purely sync. This method return null at the end. - private Task ReadWriteColumnValueAsync(int col) - { - var writeTask = ReadWriteValueGenericAsync(col); //this will return Task/null in future: as rTask - - return writeTask; - } - private Task WriteValueAsync(T value, int col, bool isSqlType, bool isDataFeed, bool isNull) { _SqlMetaData metadata = _sortedColumnMappings[col]._metadata; From da005d6bc699325acabf9c0ce0bf1741195dc773 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 9 Jun 2020 00:48:41 -0500 Subject: [PATCH 09/31] PR feedback --- .../Data/SqlClient/GenericConverter.cs | 6 ++- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 51 +++++++++++-------- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- .../Data/SqlClient/GenericConverter.cs | 9 +++- .../FunctionalTests/GenericConverterTests.cs | 49 +++++++++++++----- 5 files changed, 78 insertions(+), 39 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs index 7ff0e256f5..2e6a0117ac 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Data.SqlTypes; using System.Linq; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index f58f1f9032..04c5a60a1c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -137,28 +137,27 @@ private enum ValueSourceType } // Enum for specifying SqlDataReader.Get / IDataReader Get method used - [Flags] private enum ValueMethod { - GetValue = 0, - SqlTypeSqlDecimal = 1, - SqlTypeSqlDouble = 1 << 1, - SqlTypeSqlSingle = 1 << 2, - DataFeedStream = 1 << 3, - DataFeedText = 1 << 4, - DataFeedXml = 1 << 5, - GetInt32 = 1 << 6, - GetString = 1 << 7, - GetDouble = 1 << 8, - GetDecimal = 1 << 9, - GetInt16 = 1 << 10, - GetInt64 = 1 << 11, - GetChar = 1 << 12, - GetByte = 1 << 13, - GetBoolean = 1 << 14, - GetDateTime = 1 << 15, - GetGuid = 1 << 16, - GetFloat = 1 << 17 + GetValue, + SqlTypeSqlDecimal, + SqlTypeSqlDouble, + SqlTypeSqlSingle, + DataFeedStream, + DataFeedText, + DataFeedXml, + GetInt32, + GetString, + GetDouble, + GetDecimal, + GetInt16, + GetInt64, + GetChar, + GetByte, + GetBoolean, + GetDateTime, + GetGuid, + GetFloat } // Used to hold column metadata for SqlDataReader case @@ -1339,8 +1338,16 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) isSqlType = false; isDataFeed = false; - Type t = ((IDataReader)_rowSource).GetFieldType(ordinal); - + Type t = null; + try + { + t = ((IDataReader)_rowSource).GetFieldType(ordinal); + } + catch + { + // do some error logging here?? + } + if (t == typeof(bool)) { method = ValueMethod.GetBoolean; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 3b9c5c1927..3e230e3dd3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1,4 +1,4 @@ - // Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs index 1053ac6e87..bd8600b36b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Data.SqlTypes; using System.Linq; @@ -15,6 +19,9 @@ public static TOut Convert(TIn value) return GenericConverterHelper.Convert(value); } + /// + /// Note: this file is inherently different because the .NET Core JIT can leverage some better "smarts" to optimize out unnecessary casts + /// private static class GenericConverterHelper { /// diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs index dcf67c25b7..8f3d8e37fd 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs @@ -8,36 +8,57 @@ namespace Microsoft.Data.SqlClient.Tests { + public class DecimalTheoryData: TheoryData + { + public DecimalTheoryData() + { + Add(-100); + Add(0); + Add(.75m); + Add(525); + } + } public class GenericConverterTests { - [Fact] - public void ObjectConversionSuccessful() + [Theory] + [ClassData(typeof(DecimalTheoryData))] + public void ObjectToDecimal_ConversionSuccessful(decimal val) { - object test = 0m; + object objVal = val; - decimal converted = GenericConverter.Convert(test); + decimal converted = GenericConverter.Convert(objVal); - Assert.Equal(test, converted); + Assert.Equal(val, converted); } [Fact] - public void SelfConversionSuccessful() + public void ObjectToString_ConversionSuccessful() { - decimal test = 0m; + string testVal = "abcd1234"; - decimal converted = GenericConverter.Convert(test); + string converted = GenericConverter.Convert(testVal); - Assert.Equal(test, converted); + Assert.Equal(testVal, converted); } - [Fact] - public void AssignableConversionSuccessful() + [Theory] + [ClassData(typeof(DecimalTheoryData))] + public void SelfConversionSuccessful(decimal val) + { + decimal converted = GenericConverter.Convert(val); + + Assert.Equal(val, converted); + } + + [Theory] + [ClassData(typeof(DecimalTheoryData))] + public void AssignableConversionSuccessful(decimal val) { - SqlDecimal test = 0m; + SqlDecimal sqlVal = new SqlDecimal(val); - decimal converted = GenericConverter.Convert(test); + decimal converted = GenericConverter.Convert(sqlVal); - Assert.Equal(test, converted); + Assert.Equal(val, converted); } } } From 128a731ca4acc6f914fb494aead4a59b6449d359 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 9 Jun 2020 00:55:35 -0500 Subject: [PATCH 10/31] whitespace --- .../netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs | 4 ++-- .../tests/FunctionalTests/GenericConverterTests.cs | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index 3d1668e907..bd0d4c363a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -107,7 +107,7 @@ public sealed partial class SqlParameter : DbParameter, IDbDataParameter, IClone /// /// Indicates if the parameter encryption metadata received by sp_describe_parameter_encryption. - /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate + /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate /// that no encryption is needed). /// internal bool HasReceivedMetadata { get; set; } @@ -407,7 +407,7 @@ internal SmiParameterMetaData MetaDataForSmi(out ParameterPeekAheadValue peekAhe long actualLen = GetActualSize(); long maxLen = this.Size; - // GetActualSize returns bytes length, but smi expects char length for + // GetActualSize returns bytes length, but smi expects char length for // character types, so adjust if (!mt.IsLong) { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs index 8f3d8e37fd..1ce65dd7f0 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs @@ -25,9 +25,7 @@ public class GenericConverterTests public void ObjectToDecimal_ConversionSuccessful(decimal val) { object objVal = val; - decimal converted = GenericConverter.Convert(objVal); - Assert.Equal(val, converted); } @@ -35,9 +33,7 @@ public void ObjectToDecimal_ConversionSuccessful(decimal val) public void ObjectToString_ConversionSuccessful() { string testVal = "abcd1234"; - string converted = GenericConverter.Convert(testVal); - Assert.Equal(testVal, converted); } @@ -46,7 +42,6 @@ public void ObjectToString_ConversionSuccessful() public void SelfConversionSuccessful(decimal val) { decimal converted = GenericConverter.Convert(val); - Assert.Equal(val, converted); } @@ -55,9 +50,7 @@ public void SelfConversionSuccessful(decimal val) public void AssignableConversionSuccessful(decimal val) { SqlDecimal sqlVal = new SqlDecimal(val); - decimal converted = GenericConverter.Convert(sqlVal); - Assert.Equal(val, converted); } } From fb14f24e8644faa087d891265399c5fd84d224de Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 9 Jun 2020 01:10:57 -0500 Subject: [PATCH 11/31] fix build --- .../netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 04c5a60a1c..5460ae701a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1653,7 +1653,8 @@ private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, } catch (SqlTruncateException) { - throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(sqlValue)); + mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); + throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(decValue.Value)); } } From 908aca2458da6729359e372a88deb9cc802e9483 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 14 Jul 2020 12:01:40 -0500 Subject: [PATCH 12/31] PR feedback + removing objValue + decValue --- .../Data/SqlClient/GenericConverter.cs | 8 - .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 80 +++++---- .../Microsoft/Data/SqlClient/SqlParameter.cs | 160 +++++++++--------- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 11 +- .../Data/SqlClient/GenericConverter.cs | 6 - .../FunctionalTests/GenericConverterTests.cs | 143 ++++++++++++---- .../Microsoft.Data.SqlClient.Tests.csproj | 1 - 7 files changed, 236 insertions(+), 173 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs index 2e6a0117ac..90cd380f22 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs @@ -2,14 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Data.SqlTypes; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - namespace Microsoft.Data.SqlClient { internal static class GenericConverter diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 5460ae701a..c2c5447844 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -941,25 +941,34 @@ private Task ReadWriteColumnValueAsync(int destRowIndex) } else { + isDataFeed = true; + + object feedColumnValue; + switch (_currentRowMetadata[destRowIndex].Method) { case ValueMethod.DataFeedStream: - return WriteValueAsync(new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)), destRowIndex, isSqlType, isDataFeed, isNull); + feedColumnValue = new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)); + break; case ValueMethod.DataFeedText: - return WriteValueAsync(new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)), destRowIndex, isSqlType, isDataFeed, isNull); + feedColumnValue = new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)); + break; case ValueMethod.DataFeedXml: // Only SqlDataReader supports an XmlReader // There is no GetXmlReader on DbDataReader, however if GetValue returns XmlReader we will read it as stream if it is assigned to XML field Debug.Assert(_SqlDataReaderRowSource != null, "Should not be reading row as an XmlReader if bulk copy source is not a SqlDataReader"); - return WriteValueAsync(new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)), destRowIndex, isSqlType, isDataFeed, isNull); + feedColumnValue = new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)); + break; default: Debug.Fail($"Current column is marked as being a DataFeed, but no DataFeed compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); isDataFeed = false; - object columnValue = _DbDataReaderRowSource.GetValue(sourceOrdinal); - ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - - return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + feedColumnValue = _DbDataReaderRowSource.GetValue(sourceOrdinal); + ADP.IsNullOrSqlType(feedColumnValue, out isNull, out isSqlType); + break; } + + //specifically choosing to use the object overload here to simplify TdsParser logic for the XmlReader scenario + return WriteValueAsync(feedColumnValue, destRowIndex, isSqlType, isDataFeed, isNull); } } // SqlDataReader-specific logic @@ -1007,19 +1016,19 @@ private Task ReadWriteColumnValueAsync(int destRowIndex) } else { - IDataReader r = (IDataReader)_rowSource; + IDataReader rowSourceAsIDataReader = (IDataReader)_rowSource; // Only use IsDbNull when streaming is enabled and only for non-SqlDataReader - if ((_enableStreaming) && (_SqlDataReaderRowSource == null) && (r.IsDBNull(sourceOrdinal))) + if ((_enableStreaming) && (_SqlDataReaderRowSource == null) && (rowSourceAsIDataReader.IsDBNull(sourceOrdinal))) { isNull = true; return WriteValueAsync(DBNull.Value, destRowIndex, isSqlType, isDataFeed, isNull); } else { - if (_currentRowMetadata[sourceOrdinal].Method == ValueMethod.GetValue || r.IsDBNull(sourceOrdinal)) + if (_currentRowMetadata[sourceOrdinal].Method == ValueMethod.GetValue || rowSourceAsIDataReader.IsDBNull(sourceOrdinal)) { - object columnValue = r.GetValue(sourceOrdinal); + object columnValue = rowSourceAsIDataReader.GetValue(sourceOrdinal); ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); } @@ -1028,31 +1037,33 @@ private Task ReadWriteColumnValueAsync(int destRowIndex) switch (_currentRowMetadata[sourceOrdinal].Method) { case ValueMethod.GetInt32: - return WriteValueAsync(r.GetInt32(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetInt32(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, false); case ValueMethod.GetString: - return WriteValueAsync(r.GetString(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + var strValue = rowSourceAsIDataReader.GetString(sourceOrdinal); + isNull = strValue == null; + return WriteValueAsync(strValue, destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetDouble: - return WriteValueAsync(r.GetDouble(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetDouble(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetDecimal: - return WriteValueAsync(r.GetDecimal(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetDecimal(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetInt16: - return WriteValueAsync(r.GetInt16(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetInt16(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetInt64: - return WriteValueAsync(r.GetInt64(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetInt64(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetChar: - return WriteValueAsync(r.GetChar(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetChar(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetByte: - return WriteValueAsync(r.GetByte(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetByte(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetBoolean: - return WriteValueAsync(r.GetBoolean(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetBoolean(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetDateTime: - return WriteValueAsync(r.GetDateTime(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetDateTime(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetGuid: - return WriteValueAsync(r.GetGuid(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetGuid(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); case ValueMethod.GetFloat: - return WriteValueAsync(r.GetFloat(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + return WriteValueAsync(rowSourceAsIDataReader.GetFloat(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); default: - object columnValue = r.GetValue(sourceOrdinal); + object columnValue = rowSourceAsIDataReader.GetValue(sourceOrdinal); ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); } @@ -1596,7 +1607,6 @@ private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, MetaType type = metadata.metaType; bool typeChanged = false; object objValue = null; - SqlDecimal? decValue = null; // If the column is encrypted then we are going to transparently encrypt this column // (based on connection string setting)- Use the metaType for the underlying @@ -1621,6 +1631,7 @@ private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, { case TdsEnums.SQLNUMERICN: case TdsEnums.SQLDECIMALN: + SqlDecimal decValue; if (typeof(T) == typeof(decimal)) { decValue = new SqlDecimal(GenericConverter.Convert(value)); @@ -1640,21 +1651,21 @@ private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, // the source and destination weren't the same. The BCP protocol, specifies the // scale of the incoming data in the insert statement, we just tell the server we // are inserting the same scale back. - if (decValue.Value.Scale != scale) + if (decValue.Scale != scale) { - decValue = TdsParser.AdjustSqlDecimalScale(decValue.Value, scale); + decValue = TdsParser.AdjustSqlDecimalScale(decValue, scale); } - if (decValue.Value.Precision > precision) + if (decValue.Precision > precision) { try { - decValue = SqlDecimal.ConvertToPrecScale(decValue.Value, precision, decValue.Value.Scale); + decValue = SqlDecimal.ConvertToPrecScale(decValue, precision, decValue.Scale); } catch (SqlTruncateException) { mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(decValue.Value)); + throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(decValue)); } } @@ -1662,7 +1673,8 @@ private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, isSqlType = true; typeChanged = false; // Setting this to false as SqlParameter.CoerceValue will only set it to true when converting to a CLR type - break; + // returning here to avoid unnecessary decValue initialization for all types + return WriteConvertedValue(decValue, col, isSqlType, isNull, coercedToDataFeed, metadata); case TdsEnums.SQLINTN: case TdsEnums.SQLFLTN: @@ -1761,11 +1773,7 @@ private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e); } - if (decValue.HasValue) - { - return WriteConvertedValue(decValue.Value, col, isSqlType, isNull, coercedToDataFeed, metadata); - } - else if (typeChanged) + if (typeChanged) { // All type changes change to CLR types isSqlType = false; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index bd0d4c363a..0612cb9f95 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1015,113 +1015,113 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o ? value.GetType() // only call GetType if we know boxing has already occurred. : typeof(T); - if (typeof(object) == destinationType.ClassType || - currentType == destinationType.ClassType || - currentType == destinationType.SqlType && SqlDbType.Xml != destinationType.SqlDbType) - // Special case for Xml types (since we need to convert SqlXml into a string) - { - return false; - } + if ((typeof(object) != destinationType.ClassType) && + (currentType != destinationType.ClassType) && + ((currentType != destinationType.SqlType) || (SqlDbType.Xml == destinationType.SqlDbType))) + { // Special case for Xml types (since we need to convert SqlXml into a string) - try - { - // Assume that the type changed - if ((typeof(string) == destinationType.ClassType)) + try { - // For Xml data, destination Type is always string - if (typeof(SqlXml) == currentType) - { - var xmlValue = GenericConverter.Convert(value); - objValue = MetaType.GetStringFromXml(xmlValue.CreateReader()); - } - else if (typeof(SqlString) == currentType) + // Assume that the type changed + if ((typeof(string) == destinationType.ClassType)) { - return false; - } - else if (value is XmlReader xmlReader) - { - if (allowStreaming) + // For Xml data, destination Type is always string + if (typeof(SqlXml) == currentType) + { + var xmlValue = GenericConverter.Convert(value); + objValue = MetaType.GetStringFromXml(xmlValue.CreateReader()); + } + else if (typeof(SqlString) == currentType) + { + return false; + } + else if (value is XmlReader xmlReader) + { + if (allowStreaming) + { + coercedToDataFeed = true; + objValue = new XmlDataFeed(xmlReader); + } + else + { + objValue = MetaType.GetStringFromXml(xmlReader); + } + } + else if (typeof(char[]) == currentType) + { + objValue = new string(GenericConverter.Convert(value)); + } + else if (typeof(SqlChars) == currentType) + { + objValue = new string(GenericConverter.Convert(value).Value); + } + else if (value is TextReader tr && allowStreaming) { coercedToDataFeed = true; - objValue = new XmlDataFeed(xmlReader); + objValue = new TextDataFeed(tr); } else { - objValue = MetaType.GetStringFromXml(xmlReader); + objValue = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); } } - else if (typeof(char[]) == currentType) + else if ((DbType.Currency == destinationType.DbType) && (typeof(string) == currentType)) + { + objValue = decimal.Parse(GenericConverter.Convert(value), NumberStyles.Currency, (IFormatProvider)null); + } + else if ((typeof(SqlBytes) == currentType) && (typeof(byte[]) == destinationType.ClassType)) + { + return false;// Do nothing + } + else if ((typeof(string) == currentType) && (SqlDbType.Time == destinationType.SqlDbType)) + { + objValue = TimeSpan.Parse(GenericConverter.Convert(value)); + } + else if ((typeof(string) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) { - var charArrayValue = GenericConverter.Convert(value); - objValue = new string(charArrayValue); + objValue = DateTimeOffset.Parse(GenericConverter.Convert(value), (IFormatProvider)null); } - else if (typeof(SqlChars) == currentType) + else if ((typeof(DateTime) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) { - var sqlCharsValue = GenericConverter.Convert(value); - objValue = new string(sqlCharsValue.Value); + objValue = new DateTimeOffset(GenericConverter.Convert(value)); } - else if (value is TextReader tr && allowStreaming) + else if (TdsEnums.SQLTABLE == destinationType.TDSType && ( + value is DataTable || + value is DbDataReader || + value is System.Collections.Generic.IEnumerable)) + { + // no conversion for TVPs. + return false; + } + else if (destinationType.ClassType == typeof(byte[]) && allowStreaming && value is Stream stream) { coercedToDataFeed = true; - objValue = new TextDataFeed(tr); + objValue = new StreamDataFeed(stream); } else { objValue = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); } } - else if ((DbType.Currency == destinationType.DbType) && (typeof(string) == currentType)) - { - objValue = decimal.Parse(GenericConverter.Convert(value), NumberStyles.Currency, (IFormatProvider)null); - } - else if ((typeof(SqlBytes) == currentType) && (typeof(byte[]) == destinationType.ClassType)) - { - return false;// Do nothing - } - else if ((typeof(string) == currentType) && (SqlDbType.Time == destinationType.SqlDbType)) - { - objValue = TimeSpan.Parse(GenericConverter.Convert(value)); - } - else if ((typeof(string) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) - { - objValue = DateTimeOffset.Parse(GenericConverter.Convert(value), (IFormatProvider)null); - } - else if ((typeof(DateTime) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) - { - objValue = new DateTimeOffset(GenericConverter.Convert(value)); - } - else if (TdsEnums.SQLTABLE == destinationType.TDSType && ( - value is DataTable || - value is DbDataReader || - value is System.Collections.Generic.IEnumerable)) - { - // no conversion for TVPs. - return false; - } - else if (destinationType.ClassType == typeof(byte[]) && allowStreaming && value is Stream stream) + catch (Exception e) { - coercedToDataFeed = true; - objValue = new StreamDataFeed(stream); - } - else - { - objValue = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); + if (!ADP.IsCatchableExceptionType(e)) + { + throw; + } + + throw ADP.ParameterConversionFailed(value, destinationType.ClassType, e); } + + Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); + Debug.Assert(value.GetType() != currentType, "Incorrect value for typeChanged"); + + return true; } - catch (Exception e) + else { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - - throw ADP.ParameterConversionFailed(value, destinationType.ClassType, e); + return false; } - - Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); - Debug.Assert(value.GetType() != currentType, "Incorrect value for typeChanged"); - - return true; } internal void FixStreamDataForNonPLP() diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 3e230e3dd3..c2d7fc7f0f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -10444,7 +10444,6 @@ internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParser MetaType metatype = metadata.metaType; int ccb = 0; int ccbStringBytes = 0; - object objValue = null; if (isNull) { @@ -10510,9 +10509,10 @@ internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParser break; case TdsEnums.SQLXMLTYPE: // Value here could be string or XmlReader - if (value is XmlReader xr) + // the XmlReader scenario can only occur when T is object (enforced during SqlBulkCopy.ReadWriteColumnValueAsync) + if (typeof(T) == typeof(object) && value is XmlReader xr) { - objValue = MetaType.GetStringFromXml(xr); + value = GenericConverter.Convert(MetaType.GetStringFromXml(xr)); } ccb = (isSqlType ? GenericConverter.Convert(value).Value.Length @@ -10570,11 +10570,8 @@ internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParser else if (metatype.SqlDbType != SqlDbType.Udt || metatype.IsLong) { // we only have to consider a conversion from above in this case. + internalWriteTask = WriteValue(value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed); - internalWriteTask = objValue != null - ? WriteValue(objValue, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed) - : WriteValue(value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed) - ; if ((internalWriteTask == null) && (_asyncWrite)) { internalWriteTask = stateObj.WaitForAccumulatedWrites(); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs index bd8600b36b..fbd51d482a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs @@ -3,12 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Data.SqlTypes; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.Data.SqlClient { @@ -42,5 +37,4 @@ static GenericConverterHelper() } } } - } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs index 1ce65dd7f0..5f98415ce9 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Data.SqlTypes; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Microsoft.Data.SqlClient.Tests @@ -20,38 +20,111 @@ public DecimalTheoryData() } public class GenericConverterTests { - [Theory] - [ClassData(typeof(DecimalTheoryData))] - public void ObjectToDecimal_ConversionSuccessful(decimal val) - { - object objVal = val; - decimal converted = GenericConverter.Convert(objVal); - Assert.Equal(val, converted); - } + // commenting out until i receive feedback for how to implement tests w/o linking file + //[Theory] + //[ClassData(typeof(DecimalTheoryData))] + //public void ObjectToDecimal_ConversionSuccessful(decimal val) + //{ + // object objVal = val; + // decimal converted = GenericConverter.Convert(objVal); + // Assert.Equal(val, converted); + //} - [Fact] - public void ObjectToString_ConversionSuccessful() - { - string testVal = "abcd1234"; - string converted = GenericConverter.Convert(testVal); - Assert.Equal(testVal, converted); - } + //[Fact] + //public void ObjectToString_ConversionSuccessful() + //{ + // string testVal = "abcd1234"; + // string converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} - [Theory] - [ClassData(typeof(DecimalTheoryData))] - public void SelfConversionSuccessful(decimal val) - { - decimal converted = GenericConverter.Convert(val); - Assert.Equal(val, converted); - } + //[Fact] + //public void ObjectToInt_ConversionSuccessful() + //{ + // int testVal = 123; + // int converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} - [Theory] - [ClassData(typeof(DecimalTheoryData))] - public void AssignableConversionSuccessful(decimal val) - { - SqlDecimal sqlVal = new SqlDecimal(val); - decimal converted = GenericConverter.Convert(sqlVal); - Assert.Equal(val, converted); - } + //[Fact] + //public void ObjectToDouble_ConversionSuccessful() + //{ + // double testVal = 123d; + // double converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} + + //[Fact] + //public void ObjectToFloat_ConversionSuccessful() + //{ + // float testVal = 123f; + // float converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} + + //[Fact] + //public void ObjectToGuid_ConversionSuccessful() + //{ + // Guid testVal = Guid.NewGuid(); + // Guid converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} + + //[Fact] + //public void ObjectToDateTime_ConversionSuccessful() + //{ + // DateTime testVal = DateTime.Now; + // DateTime converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} + + //[Fact] + //public void ObjectToBool_ConversionSuccessful() + //{ + // bool testVal = false; + // bool converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} + + //[Fact] + //public void ObjectToShort_ConversionSuccessful() + //{ + // short testVal = 123; + // short converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} + + //[Fact] + //public void ObjectToByte_ConversionSuccessful() + //{ + // byte testVal = new byte(); + // byte converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} + + //[Fact] + //public void ObjectToChar_ConversionSuccessful() + //{ + // char testVal = 'a'; + // char converted = GenericConverter.Convert(testVal); + // Assert.Equal(testVal, converted); + //} + + //[Theory] + //[ClassData(typeof(DecimalTheoryData))] + //public void SelfConversionSuccessful(decimal val) + //{ + // decimal converted = GenericConverter.Convert(val); + // Assert.Equal(val, converted); + //} + + //[Theory] + //[ClassData(typeof(DecimalTheoryData))] + //public void AssignableConversionSuccessful(decimal val) + //{ + // SqlDecimal sqlVal = new SqlDecimal(val); + // decimal converted = GenericConverter.Convert(sqlVal); + // Assert.Equal(val, converted); + //} } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index 223f50fec4..e55f27b43e 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -10,7 +10,6 @@ $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) - From 4a5f3c622c4696a40e0b138ba7fe31a4f1efe6ca Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 14 Jul 2020 12:12:44 -0500 Subject: [PATCH 13/31] fixups --- .../Microsoft/Data/SqlClient/SqlParameter.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index 0612cb9f95..814b9c77c4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1015,11 +1015,13 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o ? value.GetType() // only call GetType if we know boxing has already occurred. : typeof(T); + var typeChanged = false; + if ((typeof(object) != destinationType.ClassType) && (currentType != destinationType.ClassType) && ((currentType != destinationType.SqlType) || (SqlDbType.Xml == destinationType.SqlDbType))) { // Special case for Xml types (since we need to convert SqlXml into a string) - + typeChanged = true; try { // Assume that the type changed @@ -1033,7 +1035,7 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o } else if (typeof(SqlString) == currentType) { - return false; + typeChanged = false; // Do nothing } else if (value is XmlReader xmlReader) { @@ -1071,7 +1073,7 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o } else if ((typeof(SqlBytes) == currentType) && (typeof(byte[]) == destinationType.ClassType)) { - return false;// Do nothing + typeChanged = false; // Do nothing } else if ((typeof(string) == currentType) && (SqlDbType.Time == destinationType.SqlDbType)) { @@ -1091,7 +1093,7 @@ value is DbDataReader || value is System.Collections.Generic.IEnumerable)) { // no conversion for TVPs. - return false; + typeChanged = false; } else if (destinationType.ClassType == typeof(byte[]) && allowStreaming && value is Stream stream) { @@ -1112,16 +1114,12 @@ value is DbDataReader || throw ADP.ParameterConversionFailed(value, destinationType.ClassType, e); } + } - Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); - Debug.Assert(value.GetType() != currentType, "Incorrect value for typeChanged"); + Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); + Debug.Assert(value.GetType() != currentType, "Incorrect value for typeChanged"); - return true; - } - else - { - return false; - } + return typeChanged; } internal void FixStreamDataForNonPLP() From 8e151276901638d0e64c63443c117eeb8ac11472 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 14 Jul 2020 12:14:51 -0500 Subject: [PATCH 14/31] fixup --- .../netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index 814b9c77c4..db104c2e6c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1021,17 +1021,16 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o (currentType != destinationType.ClassType) && ((currentType != destinationType.SqlType) || (SqlDbType.Xml == destinationType.SqlDbType))) { // Special case for Xml types (since we need to convert SqlXml into a string) - typeChanged = true; try { + typeChanged = true; // Assume that the type changed if ((typeof(string) == destinationType.ClassType)) { // For Xml data, destination Type is always string if (typeof(SqlXml) == currentType) { - var xmlValue = GenericConverter.Convert(value); - objValue = MetaType.GetStringFromXml(xmlValue.CreateReader()); + objValue = MetaType.GetStringFromXml(GenericConverter.Convert(value).CreateReader()); } else if (typeof(SqlString) == currentType) { @@ -1117,8 +1116,7 @@ value is DbDataReader || } Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); - Debug.Assert(value.GetType() != currentType, "Incorrect value for typeChanged"); - + Debug.Assert(value.GetType() == currentType ^ typeChanged, "Incorrect value for typeChanged"); return typeChanged; } From ecf8916a9f25ca6b5c4fe31af630145515c89258 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 14 Jul 2020 12:16:29 -0500 Subject: [PATCH 15/31] comment --- .../netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index db104c2e6c..fbab84fccc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1023,8 +1023,8 @@ internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, o { // Special case for Xml types (since we need to convert SqlXml into a string) try { - typeChanged = true; // Assume that the type changed + typeChanged = true; if ((typeof(string) == destinationType.ClassType)) { // For Xml data, destination Type is always string From 5b5a0208609fd73c8043dacde6e2d093bbca3b23 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 14 Jul 2020 12:28:53 -0500 Subject: [PATCH 16/31] adding benchmark --- ....Data.SqlClient.ManualTesting.Tests.csproj | 1 + .../SQL/SqlBulkCopyTest/GenericsBencmark.cs | 509 ++++++++++++++++++ 2 files changed, 510 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 52c66b3d1b..5a4b943184 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -62,6 +62,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs new file mode 100644 index 0000000000..8214c48bc6 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs @@ -0,0 +1,509 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.MicrosoftDataSqlClient.tests.ManualTests.SQL.SqlBulkCopyTest +{ + //class Program + //{ + // static void Main(string[] args) + // { + // if (args.Length > 0 && args[0] == "--direct") + // { + // var b = new GenericsBencmark(); + + // b.Setup(); + + // for (int i = 0; i < 10; i++) + // { + // var sw = Stopwatch.StartNew(); + + // Console.WriteLine($"Starting Batch {i}"); + // b.All(); + + // sw.Stop(); + + // Console.WriteLine($"Finished batch {i} in {sw.ElapsedMilliseconds} ms."); + // } + + // return; + // } + + // var config = ManualConfig.Create(DefaultConfig.Instance); + // config.Options = ConfigOptions.DisableOptimizationsValidator; + // config.Add(new ConsoleLogger()); + + // BenchmarkDotNet.Running.BenchmarkRunner.Run(typeof(GenericsBencmark), config); + // } + //} + + //[SimpleJob(launchCount: 1)] + //[MarkdownExporter, RankColumn, MemoryDiagnoser] + public class GenericsBencmark + { + + private const string DB = "cmeyertons_benchmark"; + private const string _allTable = "dbo._alltable"; + private const string _decimalTable = "dbo._decimalTable"; + private const string _stringTable = "dbo._stringTable"; + private const string _intTable = "dbo._intTable"; + private const string _boolTable = "dbo._boolTable"; + private const int _count = 100000; + + private static readonly IEnumerable _items; + private static readonly string _connString; + + private IDataReader _allReader; + private IDataReader _decimalReader; + private IDataReader _stringReader; + private IDataReader _intReader; + private IDataReader _boolReader; + + private class ItemToCopy + { + // keeping this data static so the performance of the benchmark is not varied by the data size & shape + public decimal DecimalColumn { get; } = 123456.789m; + public string StringColumn { get; } = "abcdefgijk"; + public int IntColumn { get; } = 123456; + public bool BoolColumn { get; } = true; + } + + static GenericsBencmark() + { + var csb = new SqlConnectionStringBuilder() + { + DataSource = ".", + InitialCatalog = "master", + IntegratedSecurity = true, + }; + + var masterCs = csb.ToString(); + _connString = new SqlConnectionStringBuilder(masterCs) + { + InitialCatalog = DB, + }.ToString(); + + using var masterc = new SqlConnection(masterCs); + using var c = new SqlConnection(_connString); + + // these were using Dapper in cmeyertons benchmark project + //masterc.Execute(@$" + // DROP DATABASE IF EXISTS {DB}; + // CREATE DATABASE {DB}; + //"); + + //c.Execute($@" + // USE {DB}; + // CREATE TABLE {_allTable} ( + // DecimalColumn DECIMAL NOT NULL, + // StringColumn NVARCHAR(50) NULL, + // IntColumn INT NOT NULL, + // BoolColumn BIT NOT NULL + // ) + // CREATE TABLE {_decimalTable} ( + // DecimalColumn DECIMAL NOT NULL + // ) + // CREATE TABLE {_stringTable} ( + // StringColumn NVARCHAR(50) NULL + // ) + // CREATE TABLE {_intTable} ( + // IntColumn INT NOT NULL + // ) + // CREATE TABLE {_boolTable} ( + // BoolColumn BIT NOT NULL + // ) + //"); + + var item = new ItemToCopy(); + + _items = Enumerable.Range(0, _count).Select(x => item).ToArray(); + } + + //[GlobalSetup] + public void Setup() + { + _allReader = new EnumerableDataReaderFactoryBuilder(_allTable) + .Add("DecimalColumn", i => i.DecimalColumn) + .Add("StringColumn", i => i.StringColumn) + .Add("IntColumn", i => i.IntColumn) + .Add("BoolColumn", i => i.BoolColumn) + .BuildFactory() + .CreateReader(_items) + ; + + _decimalReader = new EnumerableDataReaderFactoryBuilder(_decimalTable) + .Add("DecimalColumn", i => i.DecimalColumn) + .BuildFactory() + .CreateReader(_items) + ; + + _stringReader = new EnumerableDataReaderFactoryBuilder(_stringTable) + .Add("StringColumn", i => i.StringColumn) + .BuildFactory() + .CreateReader(_items) + ; + + _intReader = new EnumerableDataReaderFactoryBuilder(_intTable) + .Add("IntColumn", i => i.IntColumn) + .BuildFactory() + .CreateReader(_items) + ; + + _boolReader = new EnumerableDataReaderFactoryBuilder(_boolTable) + .Add("BoolColumn", i => i.BoolColumn) + .BuildFactory() + .CreateReader(_items) + ; + } + + // [Benchmark] + public void All() => BulkCopy(_allReader, _allTable); + + // [Benchmark] + public void Decimal() => BulkCopy(_decimalReader, _decimalTable); + + // [Benchmark] + public void String() => BulkCopy(_stringReader, _stringTable); + + // [Benchmark] + public void Int() => BulkCopy(_intReader, _intTable); + + // [Benchmark] + public void Bool() => BulkCopy(_boolReader, _boolTable); + + private static void BulkCopy(IDataReader reader, string tableName) + { + reader.Close(); // this resets the reader + + using var bc = new SqlBulkCopy(_connString, Microsoft.Data.SqlClient.SqlBulkCopyOptions.TableLock); + + bc.BatchSize = _count; + bc.DestinationTableName = tableName; + bc.BulkCopyTimeout = 60; + + bc.WriteToServer(reader); + } + + //all code here and below is a custom data reader implementation to support the benchmark. + private interface IEnumerableDataReaderFactoryBuilder + { + string Name { get; } + + EnumerableDataReaderFactoryBuilder Add(string column, Expression> expression); + EnumerableDataReaderFactory BuildFactory(); + } + + private class EnumerableDataReaderFactoryBuilder : IEnumerableDataReaderFactoryBuilder + { + private readonly List _expressions = new List(); + private readonly List> _objExpressions = new List>(); + private readonly DataTable _schemaTable; + + public EnumerableDataReaderFactoryBuilder(string tableName) + { + this.Name = tableName; + _schemaTable = new DataTable(); + } + + private static readonly HashSet _validTypes = new[] + { + typeof(decimal), + typeof(decimal?), + typeof(string), + typeof(int), + typeof(int?), + typeof(double), + typeof(bool), + typeof(bool?), + typeof(Guid), + typeof(DateTime), + }.ToHashSet(); + + private static readonly object _boxedTrue = (object)true; + private static readonly object _boxedFalse = (object)false; + + public EnumerableDataReaderFactoryBuilder Add(string column, Expression> expression) + { + var t = typeof(TColumn); + + var func = expression.Compile(); + + Expression> objExpression; + + if (typeof(T) == typeof(bool)) + { + //use a pre-boxed value -- the JIT should optimize out the (bool)(object) boxing + objExpression = o => ((bool)(object)func(o) ? _boxedTrue : _boxedFalse); + } + else + { + objExpression = o => func(o); + } + + _objExpressions.Add(objExpression.Compile()); + + if (_validTypes.Contains(t)) + { + t = Nullable.GetUnderlyingType(t) ?? t; // data table doesn't accept nullable. + _schemaTable.Columns.Add(column, t); + _expressions.Add(expression); + } + else + { + Console.WriteLine($"Could not matching return type for {this.Name}.{column} of: {t.Name}"); + _schemaTable.Columns.Add(column); //add w/o type to force using GetValue + + _expressions.Add(objExpression); + } + + return this; + } + + public EnumerableDataReaderFactory BuildFactory() => new EnumerableDataReaderFactory(_schemaTable, _expressions, _objExpressions); + + public string Name { get; } + } + + public class EnumerableDataReaderFactory + { + public DataTable SchemaTable { get; } + public Func[] ObjectGetters { get; } + public Func[] DecimalGetters { get; } + public Func[] NullableDecimalGetters { get; } + public Func[] StringGetters { get; } + public Func[] DoubleGetters { get; } + public Func[] IntGetters { get; } + public Func[] NullableIntGetters { get; } + public Func[] BoolGetters { get; } + + public Func[] NullableBoolGetters { get; } + + public Func[] GuidGetters { get; } + public Func[] DateTimeGetters { get; } + public bool[] NullableIndexes { get; } + + public EnumerableDataReaderFactory(DataTable schemaTable, List expressions, List> objectGetters) + { + SchemaTable = schemaTable; + DecimalGetters = new Func[expressions.Count]; + NullableDecimalGetters = new Func[expressions.Count]; + StringGetters = new Func[expressions.Count]; + DoubleGetters = new Func[expressions.Count]; + IntGetters = new Func[expressions.Count]; + NullableIntGetters = new Func[expressions.Count]; + BoolGetters = new Func[expressions.Count]; + NullableBoolGetters = new Func[expressions.Count]; + GuidGetters = new Func[expressions.Count]; + DateTimeGetters = new Func[expressions.Count]; + NullableIndexes = new bool[expressions.Count]; + + ObjectGetters = objectGetters.ToArray(); + + for (int i = 0; i < expressions.Count; i++) + { + var expression = expressions[i]; + + NullableIndexes[i] = !expression.ReturnType.IsValueType || Nullable.GetUnderlyingType(expression.ReturnType) != null; + + switch (expression) + { + case Expression> e: + break; // do nothing + case Expression> e: + DecimalGetters[i] = e.Compile(); + break; + case Expression> e: + NullableDecimalGetters[i] = e.Compile(); + break; + case Expression> e: + StringGetters[i] = e.Compile(); + break; + case Expression> e: + DoubleGetters[i] = e.Compile(); + break; + case Expression> e: + IntGetters[i] = e.Compile(); + break; + case Expression> e: + NullableIntGetters[i] = e.Compile(); + break; + case Expression> e: + BoolGetters[i] = e.Compile(); + break; + case Expression> e: + NullableBoolGetters[i] = e.Compile(); + break; + case Expression> e: + GuidGetters[i] = e.Compile(); + break; + case Expression> e: + DateTimeGetters[i] = e.Compile(); + break; + default: + throw new Exception($"Type missing: {expression.GetType().FullName}"); + } + } + } + + public IDataReader CreateReader(IEnumerable items) => new EnumerableDataReader(this, items.GetEnumerator()); + } + + public class EnumerableDataReader : IDataReader + { + private readonly IEnumerator _source; + private readonly EnumerableDataReaderFactory _context; + + public EnumerableDataReader(EnumerableDataReaderFactory context, IEnumerator source) + { + _source = source; + _context = context; + } + + public object GetValue(int i) + { + var v = _context.ObjectGetters[i](_source.Current); + return v; + } + + public int FieldCount => _context.ObjectGetters.Length; + + public bool Read() => _source.MoveNext(); + + public void Close() => _source.Reset(); + + public void Dispose() => this.Close(); + + public bool NextResult() => throw new NotImplementedException(); + + public int Depth => 0; + + public bool IsClosed => false; + + public int RecordsAffected => -1; + + public DataTable GetSchemaTable() => _context.SchemaTable; + + public object this[string name] => throw new NotImplementedException(); + + public object this[int i] => GetValue(i); + + public bool GetBoolean(int i) + { + var g = _context.BoolGetters[i]; + + if (g != null) + return g(_source.Current); + + return _context.NullableBoolGetters[i](_source.Current).Value; + } + + public byte GetByte(int i) => throw new NotImplementedException(); + + public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => throw new NotImplementedException(); + + public char GetChar(int i) => throw new NotImplementedException(); + public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => -1; + + public IDataReader GetData(int i) => throw new NotImplementedException(); + + public string GetDataTypeName(int i) => throw new NotImplementedException(); + + public DateTime GetDateTime(int i) => _context.DateTimeGetters[i](_source.Current); + + public decimal GetDecimal(int i) + { + var g = _context.DecimalGetters[i]; + + if (g != null) + return g(_source.Current); + + return _context.NullableDecimalGetters[i](_source.Current).Value; + } + + public double GetDouble(int i) => _context.DoubleGetters[i](_source.Current); + + public Type GetFieldType(int i) => _context.SchemaTable.Columns[i].DataType; + + public float GetFloat(int i) => throw new NotImplementedException(); + + public Guid GetGuid(int i) => _context.GuidGetters[i](_source.Current); + + public short GetInt16(int i) => throw new NotImplementedException(); + + public int GetInt32(int i) + { + var g = _context.IntGetters[i]; + + if (g != null) + return g(_source.Current); + + return _context.NullableIntGetters[i](_source.Current).Value; + } + + public long GetInt64(int i) => throw new NotImplementedException(); + + public string GetName(int i) + { + if (_context.SchemaTable.Columns.Count > i) + { + return _context.SchemaTable.Columns[i].ColumnName; + } + throw new IndexOutOfRangeException($"No column for index {i}"); + } + + public int GetOrdinal(string name) + { + if (_context.SchemaTable.Columns.Count == 0) + { + throw new Exception("Schema table is empty"); + } + return _context.SchemaTable.Columns.IndexOf(name); + } + + public string GetString(int i) => _context.StringGetters[i](_source.Current); + + public int GetValues(object[] values) => throw new NotImplementedException(); + + public bool IsDBNull(int i) + { + // short circuit for non-nullable types + if (!_context.NullableIndexes[i]) + { + return false; + } + + // otherwise find the first one -- starting w/ most occurring to least + + var ig = _context.NullableIntGetters[i]; + if (ig != null) + { + return ig(_source.Current) == null; + } + + var sg = _context.StringGetters[i]; + if (sg != null) + { + return sg(_source.Current) == null; + } + + var bg = _context.NullableBoolGetters[i]; + if (bg != null) + { + return bg(_source.Current) == null; + } + + var dg = _context.NullableDecimalGetters[i]; + if (dg != null) + { + return dg(_source.Current) == null; + } + + return false; + } + } + } +} From 14a8cfa74a01a88336a06d68e5a5ef4e8dea66db Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 14 Jul 2020 12:40:10 -0500 Subject: [PATCH 17/31] fixing test + license --- .../SQL/SqlBulkCopyTest/GenericsBencmark.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs index 8214c48bc6..f5df0f9508 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs @@ -87,10 +87,10 @@ static GenericsBencmark() InitialCatalog = DB, }.ToString(); - using var masterc = new SqlConnection(masterCs); - using var c = new SqlConnection(_connString); - // these were using Dapper in cmeyertons benchmark project + //using var masterc = new SqlConnection(masterCs); + //using var c = new SqlConnection(_connString); + //masterc.Execute(@$" // DROP DATABASE IF EXISTS {DB}; // CREATE DATABASE {DB}; @@ -179,13 +179,14 @@ private static void BulkCopy(IDataReader reader, string tableName) { reader.Close(); // this resets the reader - using var bc = new SqlBulkCopy(_connString, Microsoft.Data.SqlClient.SqlBulkCopyOptions.TableLock); - - bc.BatchSize = _count; - bc.DestinationTableName = tableName; - bc.BulkCopyTimeout = 60; + using (var bc = new SqlBulkCopy(_connString, SqlBulkCopyOptions.TableLock)) + { + bc.BatchSize = _count; + bc.DestinationTableName = tableName; + bc.BulkCopyTimeout = 60; - bc.WriteToServer(reader); + bc.WriteToServer(reader); + } } //all code here and below is a custom data reader implementation to support the benchmark. From a5ff01429cba73b3cc3ba96d7142b8af99963b82 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 14 Jul 2020 12:42:02 -0500 Subject: [PATCH 18/31] license --- .../ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs index f5df0f9508..14f8c8c25a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs @@ -1,10 +1,12 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.MicrosoftDataSqlClient.tests.ManualTests.SQL.SqlBulkCopyTest { From 0e728744c3802010d64f10418f64c66d4f442c45 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Tue, 14 Jul 2020 13:15:38 -0500 Subject: [PATCH 19/31] fixing test + bad file name --- .../netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs | 2 +- .../Microsoft.Data.SqlClient.ManualTesting.Tests.csproj | 2 +- .../SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs | 2 +- .../{GenericsBencmark.cs => GenericsBenchmark.cs} | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/{GenericsBencmark.cs => GenericsBenchmark.cs} (99%) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index c2c5447844..058dd5b874 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1026,7 +1026,7 @@ private Task ReadWriteColumnValueAsync(int destRowIndex) } else { - if (_currentRowMetadata[sourceOrdinal].Method == ValueMethod.GetValue || rowSourceAsIDataReader.IsDBNull(sourceOrdinal)) + if (_currentRowMetadata[destRowIndex].Method == ValueMethod.GetValue || rowSourceAsIDataReader.IsDBNull(sourceOrdinal)) { object columnValue = rowSourceAsIDataReader.GetValue(sourceOrdinal); ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 5a4b943184..96388d6450 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -62,7 +62,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs index 9b56183ccf..1bcc1fa83d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs @@ -159,7 +159,7 @@ private bool StringToIntTest(SqlConnection cnn, string targetTable, SourceType s string expectedErrorMsg = string.Format(pattern, args); - Assert.True(ex.Message.Contains(expectedErrorMsg), "Unexpected error message: " + ex.Message); + Assert.True(ex.Message.Contains(expectedErrorMsg), $"Unexpected error message: {ex}"); // write out stack trace for unexpected messages hitException = true; } return hitException; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBenchmark.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBenchmark.cs index 14f8c8c25a..2f1bd05fe7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBencmark.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBenchmark.cs @@ -45,7 +45,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.MicrosoftDataSqlClient.te //[SimpleJob(launchCount: 1)] //[MarkdownExporter, RankColumn, MemoryDiagnoser] - public class GenericsBencmark + public class GenericsBenchmark { private const string DB = "cmeyertons_benchmark"; @@ -74,7 +74,7 @@ private class ItemToCopy public bool BoolColumn { get; } = true; } - static GenericsBencmark() + static GenericsBenchmark() { var csb = new SqlConnectionStringBuilder() { From c91ddcce024e01698507b44a1c38bfc7f08112ea Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Fri, 11 Sep 2020 11:42:46 -0500 Subject: [PATCH 20/31] getting fully working benchmark test --- src/Microsoft.Data.SqlClient.sln | 16 +- ....Data.SqlClient.ManualTesting.Tests.csproj | 3 +- ...ricsBenchmark.cs => NoBoxingValueTypes.cs} | 256 ++++++------------ 3 files changed, 94 insertions(+), 181 deletions(-) rename src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/{GenericsBenchmark.cs => NoBoxingValueTypes.cs} (64%) diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 9077063825..6bba980cba 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -876,16 +876,16 @@ Global {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp2.1-Release|x86.Build.0 = netcoreapp2.1-Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|Any CPU.ActiveCfg = Debug|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|Any CPU.Build.0 = Debug|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x64.ActiveCfg = netcoreapp3.1-Debug|x64 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x64.Build.0 = netcoreapp3.1-Debug|x64 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x86.ActiveCfg = netcoreapp3.1-Debug|x86 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x86.Build.0 = netcoreapp3.1-Debug|x86 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x64.ActiveCfg = netcoreapp2.1-Debug|x64 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x64.Build.0 = netcoreapp2.1-Debug|x64 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x86.ActiveCfg = netcoreapp2.1-Debug|x86 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x86.Build.0 = netcoreapp2.1-Debug|x86 {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|Any CPU.ActiveCfg = Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|Any CPU.Build.0 = Release|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x64.ActiveCfg = netcoreapp3.1-Release|x64 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x64.Build.0 = netcoreapp3.1-Release|x64 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x86.ActiveCfg = netcoreapp3.1-Release|x86 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x86.Build.0 = netcoreapp3.1-Release|x86 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x64.ActiveCfg = netcoreapp2.1-Debug|x64 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x64.Build.0 = netcoreapp2.1-Debug|x64 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x86.ActiveCfg = netcoreapp2.1-Debug|x86 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x86.Build.0 = netcoreapp2.1-Debug|x86 {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|Any CPU.Build.0 = Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|x64.ActiveCfg = Release|Any CPU diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 96388d6450..e6f979fe31 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -62,7 +62,7 @@ - + @@ -273,6 +273,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBenchmark.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs similarity index 64% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBenchmark.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index 2f1bd05fe7..834f24703a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/GenericsBenchmark.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -7,200 +7,124 @@ using System.Data; using System.Linq; using System.Linq.Expressions; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.MicrosoftDataSqlClient.tests.ManualTests.SQL.SqlBulkCopyTest +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Diagnostics.Runtime.Interop; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests { - //class Program - //{ - // static void Main(string[] args) - // { - // if (args.Length > 0 && args[0] == "--direct") - // { - // var b = new GenericsBencmark(); - - // b.Setup(); - - // for (int i = 0; i < 10; i++) - // { - // var sw = Stopwatch.StartNew(); - - // Console.WriteLine($"Starting Batch {i}"); - // b.All(); - - // sw.Stop(); - - // Console.WriteLine($"Finished batch {i} in {sw.ElapsedMilliseconds} ms."); - // } - - // return; - // } - - // var config = ManualConfig.Create(DefaultConfig.Instance); - // config.Options = ConfigOptions.DisableOptimizationsValidator; - // config.Add(new ConsoleLogger()); - - // BenchmarkDotNet.Running.BenchmarkRunner.Run(typeof(GenericsBencmark), config); - // } - //} - - //[SimpleJob(launchCount: 1)] - //[MarkdownExporter, RankColumn, MemoryDiagnoser] - public class GenericsBenchmark + public class NoBoxingValueTypes : IDisposable { - - private const string DB = "cmeyertons_benchmark"; - private const string _allTable = "dbo._alltable"; - private const string _decimalTable = "dbo._decimalTable"; - private const string _stringTable = "dbo._stringTable"; - private const string _intTable = "dbo._intTable"; - private const string _boolTable = "dbo._boolTable"; - private const int _count = 100000; - + private static readonly string _table = DataTestUtility.GetUniqueNameForSqlServer(nameof(NoBoxingValueTypes)); + private const int _count = 5000; + private static readonly ItemToCopy _item; private static readonly IEnumerable _items; - private static readonly string _connString; + private static readonly IDataReader _reader; - private IDataReader _allReader; - private IDataReader _decimalReader; - private IDataReader _stringReader; - private IDataReader _intReader; - private IDataReader _boolReader; + private static readonly string _connString = DataTestUtility.TCPConnectionString; private class ItemToCopy { // keeping this data static so the performance of the benchmark is not varied by the data size & shape - public decimal DecimalColumn { get; } = 123456.789m; - public string StringColumn { get; } = "abcdefgijk"; public int IntColumn { get; } = 123456; public bool BoolColumn { get; } = true; } - static GenericsBenchmark() + static NoBoxingValueTypes() { - var csb = new SqlConnectionStringBuilder() - { - DataSource = ".", - InitialCatalog = "master", - IntegratedSecurity = true, - }; + _item = new ItemToCopy(); - var masterCs = csb.ToString(); - _connString = new SqlConnectionStringBuilder(masterCs) - { - InitialCatalog = DB, - }.ToString(); - - // these were using Dapper in cmeyertons benchmark project - //using var masterc = new SqlConnection(masterCs); - //using var c = new SqlConnection(_connString); - - //masterc.Execute(@$" - // DROP DATABASE IF EXISTS {DB}; - // CREATE DATABASE {DB}; - //"); - - //c.Execute($@" - // USE {DB}; - // CREATE TABLE {_allTable} ( - // DecimalColumn DECIMAL NOT NULL, - // StringColumn NVARCHAR(50) NULL, - // IntColumn INT NOT NULL, - // BoolColumn BIT NOT NULL - // ) - // CREATE TABLE {_decimalTable} ( - // DecimalColumn DECIMAL NOT NULL - // ) - // CREATE TABLE {_stringTable} ( - // StringColumn NVARCHAR(50) NULL - // ) - // CREATE TABLE {_intTable} ( - // IntColumn INT NOT NULL - // ) - // CREATE TABLE {_boolTable} ( - // BoolColumn BIT NOT NULL - // ) - //"); - - var item = new ItemToCopy(); - - _items = Enumerable.Range(0, _count).Select(x => item).ToArray(); - } + _items = Enumerable.Range(0, _count).Select(x => _item).ToArray(); - //[GlobalSetup] - public void Setup() - { - _allReader = new EnumerableDataReaderFactoryBuilder(_allTable) - .Add("DecimalColumn", i => i.DecimalColumn) - .Add("StringColumn", i => i.StringColumn) + _reader = new EnumerableDataReaderFactoryBuilder(_table) .Add("IntColumn", i => i.IntColumn) .Add("BoolColumn", i => i.BoolColumn) .BuildFactory() .CreateReader(_items) ; + } - _decimalReader = new EnumerableDataReaderFactoryBuilder(_decimalTable) - .Add("DecimalColumn", i => i.DecimalColumn) - .BuildFactory() - .CreateReader(_items) - ; - - _stringReader = new EnumerableDataReaderFactoryBuilder(_stringTable) - .Add("StringColumn", i => i.StringColumn) - .BuildFactory() - .CreateReader(_items) - ; + public NoBoxingValueTypes() + { + using (var conn = new SqlConnection(_connString)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + Helpers.TryExecute(cmd, $@" + CREATE TABLE {_table} ( + IntColumn INT NOT NULL, + BoolColumn BIT NOT NULL + ) + "); + } + } - _intReader = new EnumerableDataReaderFactoryBuilder(_intTable) - .Add("IntColumn", i => i.IntColumn) - .BuildFactory() - .CreateReader(_items) - ; + - _boolReader = new EnumerableDataReaderFactoryBuilder(_boolTable) - .Add("BoolColumn", i => i.BoolColumn) - .BuildFactory() - .CreateReader(_items) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Should_Not_Box() + { // in debug mode, the double boxing DOES occur, which causes this test to fail (does the JIT "do" less in debug?) +#if DEBUG + return; +#endif + var config = ManualConfig.Create(DefaultConfig.Instance) + .WithOptions(ConfigOptions.DisableOptimizationsValidator) + .AddJob(Job.InProcess.WithLaunchCount(1).WithInvocationCount(1).WithIterationCount(1).WithWarmupCount(0).WithStrategy(RunStrategy.ColdStart)) + .AddDiagnoser(MemoryDiagnoser.Default) ; - } - - // [Benchmark] - public void All() => BulkCopy(_allReader, _allTable); - // [Benchmark] - public void Decimal() => BulkCopy(_decimalReader, _decimalTable); + var summary = BenchmarkRunner.Run(config); - // [Benchmark] - public void String() => BulkCopy(_stringReader, _stringTable); + var numValueTypeColumns = 2; + var totalBytesWhenBoxed = IntPtr.Size * _count * numValueTypeColumns; - // [Benchmark] - public void Int() => BulkCopy(_intReader, _intTable); + var report = summary.Reports.First(); - // [Benchmark] - public void Bool() => BulkCopy(_boolReader, _boolTable); + Assert.Equal(1, report.AllMeasurements.Count); + Assert.True(report.GcStats.BytesAllocatedPerOperation < totalBytesWhenBoxed); + } - private static void BulkCopy(IDataReader reader, string tableName) + public class NoBoxingValueTypesBenchmark { - reader.Close(); // this resets the reader - - using (var bc = new SqlBulkCopy(_connString, SqlBulkCopyOptions.TableLock)) + [Benchmark] + public void BulkCopy() { - bc.BatchSize = _count; - bc.DestinationTableName = tableName; - bc.BulkCopyTimeout = 60; + _reader.Close(); // this resets the reader - bc.WriteToServer(reader); + using (var bc = new SqlBulkCopy(DataTestUtility.TCPConnectionString, SqlBulkCopyOptions.TableLock)) + { + bc.BatchSize = _count; + bc.DestinationTableName = _table; + bc.BulkCopyTimeout = 60; + + bc.WriteToServer(_reader); + } } } - //all code here and below is a custom data reader implementation to support the benchmark. - private interface IEnumerableDataReaderFactoryBuilder + public void Dispose() { - string Name { get; } - - EnumerableDataReaderFactoryBuilder Add(string column, Expression> expression); - EnumerableDataReaderFactory BuildFactory(); + using (var conn = new SqlConnection(_connString)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + Helpers.TryExecute(cmd, $@" + DROP TABLE IF EXISTS {_table} + "); + } } - private class EnumerableDataReaderFactoryBuilder : IEnumerableDataReaderFactoryBuilder + //all code here and below is a custom data reader implementation to support the benchmark + + private class EnumerableDataReaderFactoryBuilder { private readonly List _expressions = new List(); private readonly List> _objExpressions = new List>(); @@ -208,7 +132,7 @@ private class EnumerableDataReaderFactoryBuilder : IEnumerableDataReaderFacto public EnumerableDataReaderFactoryBuilder(string tableName) { - this.Name = tableName; + Name = tableName; _schemaTable = new DataTable(); } @@ -226,26 +150,14 @@ public EnumerableDataReaderFactoryBuilder(string tableName) typeof(DateTime), }.ToHashSet(); - private static readonly object _boxedTrue = (object)true; - private static readonly object _boxedFalse = (object)false; - public EnumerableDataReaderFactoryBuilder Add(string column, Expression> expression) { var t = typeof(TColumn); var func = expression.Compile(); - Expression> objExpression; - - if (typeof(T) == typeof(bool)) - { - //use a pre-boxed value -- the JIT should optimize out the (bool)(object) boxing - objExpression = o => ((bool)(object)func(o) ? _boxedTrue : _boxedFalse); - } - else - { - objExpression = o => func(o); - } + // don't do any optimizations for boxing bools here to detect boxing occurring properly. + Expression> objExpression= o => func(o); _objExpressions.Add(objExpression.Compile()); @@ -257,7 +169,7 @@ public EnumerableDataReaderFactoryBuilder Add(string column, Express } else { - Console.WriteLine($"Could not matching return type for {this.Name}.{column} of: {t.Name}"); + Console.WriteLine($"Could not matching return type for {Name}.{column} of: {t.Name}"); _schemaTable.Columns.Add(column); //add w/o type to force using GetValue _expressions.Add(objExpression); From e2f7f64af06a5b6314af3cc2023326167adc3f3f Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Fri, 11 Sep 2020 11:46:52 -0500 Subject: [PATCH 21/31] cleanup namepsaces --- .../ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index 834f24703a..865f88d20f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -12,11 +12,7 @@ using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Engines; using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Loggers; -using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.Diagnostics.Runtime.Interop; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests From f0917f2c110c43a93d69cf445643d2563816610c Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Fri, 11 Sep 2020 11:52:32 -0500 Subject: [PATCH 22/31] removing try / catch --- .../src/Microsoft/Data/SqlClient/SqlBulkCopy.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index bb0a120296..c337268907 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1390,15 +1390,7 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) isSqlType = false; isDataFeed = false; - Type t = null; - try - { - t = ((IDataReader)_rowSource).GetFieldType(ordinal); - } - catch - { - // do some error logging here?? - } + Type t = ((IDataReader)_rowSource).GetFieldType(ordinal); if (t == typeof(bool)) { From a21881b739dd148d99d79f0f3cfb675097a22aa2 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Fri, 11 Sep 2020 12:05:48 -0500 Subject: [PATCH 23/31] fixups --- src/Microsoft.Data.SqlClient.sln | 16 ++++++++-------- .../SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 3 --- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 6445fd2dab..855baeedaa 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -828,16 +828,16 @@ Global {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp2.1-Release|x86.Build.0 = netcoreapp2.1-Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|Any CPU.ActiveCfg = Debug|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|Any CPU.Build.0 = Debug|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x64.ActiveCfg = netcoreapp2.1-Debug|x64 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x64.Build.0 = netcoreapp2.1-Debug|x64 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x86.ActiveCfg = netcoreapp2.1-Debug|x86 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x86.Build.0 = netcoreapp2.1-Debug|x86 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x64.ActiveCfg = netcoreapp3.1-Debug|x64 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x64.Build.0 = netcoreapp3.1-Debug|x64 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x86.ActiveCfg = netcoreapp3.1-Debug|x86 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Debug|x86.Build.0 = netcoreapp3.1-Debug|x86 {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|Any CPU.ActiveCfg = Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|Any CPU.Build.0 = Release|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x64.ActiveCfg = netcoreapp2.1-Debug|x64 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x64.Build.0 = netcoreapp2.1-Debug|x64 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x86.ActiveCfg = netcoreapp2.1-Debug|x86 - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x86.Build.0 = netcoreapp2.1-Debug|x86 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x64.ActiveCfg = netcoreapp3.1-Release|x64 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x64.Build.0 = netcoreapp3.1-Release|x64 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x86.ActiveCfg = netcoreapp3.1-Release|x86 + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.netcoreapp3.1-Release|x86.Build.0 = netcoreapp3.1-Release|x86 {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|Any CPU.Build.0 = Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|x64.ActiveCfg = Release|Any CPU diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index 865f88d20f..f433e006fb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -63,8 +63,6 @@ BoolColumn BIT NOT NULL } } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] public void Should_Not_Box() { // in debug mode, the double boxing DOES occur, which causes this test to fail (does the JIT "do" less in debug?) @@ -119,7 +117,6 @@ DROP TABLE IF EXISTS {_table} } //all code here and below is a custom data reader implementation to support the benchmark - private class EnumerableDataReaderFactoryBuilder { private readonly List _expressions = new List(); From 70b885c7aeef4be1e11a72ae162d553dc87b7472 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Fri, 11 Sep 2020 12:56:35 -0500 Subject: [PATCH 24/31] removing test for platform compatibility --- ....Data.SqlClient.ManualTesting.Tests.csproj | 1 - .../SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 31 ++++++++----------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index e29b8d6b10..e8f102ff75 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -272,7 +272,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index f433e006fb..33f337ba7e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -7,13 +7,6 @@ using System.Data; using System.Linq; using System.Linq.Expressions; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Running; -using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -69,21 +62,23 @@ public void Should_Not_Box() #if DEBUG return; #endif - var config = ManualConfig.Create(DefaultConfig.Instance) - .WithOptions(ConfigOptions.DisableOptimizationsValidator) - .AddJob(Job.InProcess.WithLaunchCount(1).WithInvocationCount(1).WithIterationCount(1).WithWarmupCount(0).WithStrategy(RunStrategy.ColdStart)) - .AddDiagnoser(MemoryDiagnoser.Default) - ; + // cannot figure out an easy way to get this to work on all platforms + + //var config = ManualConfig.Create(DefaultConfig.Instance) + // .WithOptions(ConfigOptions.DisableOptimizationsValidator) + // .AddJob(Job.InProcess.WithLaunchCount(1).WithInvocationCount(1).WithIterationCount(1).WithWarmupCount(0).WithStrategy(RunStrategy.ColdStart)) + // .AddDiagnoser(MemoryDiagnoser.Default) + //; - var summary = BenchmarkRunner.Run(config); + //var summary = BenchmarkRunner.Run(config); - var numValueTypeColumns = 2; - var totalBytesWhenBoxed = IntPtr.Size * _count * numValueTypeColumns; + //var numValueTypeColumns = 2; + //var totalBytesWhenBoxed = IntPtr.Size * _count * numValueTypeColumns; - var report = summary.Reports.First(); + //var report = summary.Reports.First(); - Assert.Equal(1, report.AllMeasurements.Count); - Assert.True(report.GcStats.BytesAllocatedPerOperation < totalBytesWhenBoxed); + //Assert.Equal(1, report.AllMeasurements.Count); + //Assert.True(report.GcStats.BytesAllocatedPerOperation < totalBytesWhenBoxed); } public class NoBoxingValueTypesBenchmark From de79bcb0bedff7de15d39f5b36d0c660bc515d69 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Fri, 11 Sep 2020 13:24:52 -0500 Subject: [PATCH 25/31] fixup --- .../tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index 33f337ba7e..a4d7bd5ae6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -83,7 +83,7 @@ public void Should_Not_Box() public class NoBoxingValueTypesBenchmark { - [Benchmark] + // [Benchmark] public void BulkCopy() { _reader.Close(); // this resets the reader From 1745d6fd5949cc39253af3b58b530f344ea014a3 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Fri, 11 Sep 2020 13:26:03 -0500 Subject: [PATCH 26/31] fixup --- .../tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index a4d7bd5ae6..f7e3d32e44 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -7,6 +7,7 @@ using System.Data; using System.Linq; using System.Linq.Expressions; +using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { From b8d1475e890e5babbab8242d22cc9ba09d09109b Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Sat, 12 Sep 2020 08:22:45 -0500 Subject: [PATCH 27/31] netfx fixup --- .../ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index f7e3d32e44..0632b61a0a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -125,7 +125,7 @@ public EnumerableDataReaderFactoryBuilder(string tableName) _schemaTable = new DataTable(); } - private static readonly HashSet _validTypes = new[] + private static readonly HashSet _validTypes = new HashSet { typeof(decimal), typeof(decimal?), @@ -137,7 +137,7 @@ public EnumerableDataReaderFactoryBuilder(string tableName) typeof(bool?), typeof(Guid), typeof(DateTime), - }.ToHashSet(); + }; public EnumerableDataReaderFactoryBuilder Add(string column, Expression> expression) { From b748e3515815d624514bedee5d52c88586cb41ed Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Thu, 24 Sep 2020 10:02:24 -0500 Subject: [PATCH 28/31] netfx port + getting benchmark fully working --- .../Data/SqlClient/GenericConverter.cs | 9 +- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 1 - .../Microsoft/Data/SqlClient/SqlParameter.cs | 4 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 6 +- .../Data/SqlClient/GenericConverter.cs | 32 +- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 480 +++++++++++------- .../Microsoft/Data/SqlClient/SqlParameter.cs | 77 +-- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 244 +++++---- ....Data.SqlClient.ManualTesting.Tests.csproj | 3 +- .../SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 49 +- tools/props/Versions.props | 3 +- 11 files changed, 538 insertions(+), 370 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs index 90cd380f22..d4298afa2d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/GenericConverter.cs @@ -4,9 +4,14 @@ namespace Microsoft.Data.SqlClient { + /// + /// Serves to convert generic to out type by casting to object first. Relies on JIT to optimize out unneccessary casts and prevent double boxing. + /// internal static class GenericConverter { - // This leverages the same assumptions in SqlBuffer that the JIT will optimize out the boxing / unboxing when TIn == TOut - public static TOut Convert(TIn value) => (TOut)(object)value; + public static TOut Convert(TIn value) + { + return (TOut)(object)value; + } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index c337268907..4e87235780 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1447,7 +1447,6 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) } else { - isSqlType = false; isDataFeed = false; method = ValueMethod.GetValue; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index e051d7a57a..61fd05229b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -107,7 +107,7 @@ public sealed partial class SqlParameter : DbParameter, IDbDataParameter, IClone /// /// Indicates if the parameter encryption metadata received by sp_describe_parameter_encryption. - /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate + /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate /// that no encryption is needed). /// internal bool HasReceivedMetadata { get; set; } @@ -407,7 +407,7 @@ internal SmiParameterMetaData MetaDataForSmi(out ParameterPeekAheadValue peekAhe long actualLen = GetActualSize(); long maxLen = this.Size; - // GetActualSize returns bytes length, but smi expects char length for + // GetActualSize returns bytes length, but smi expects char length for // character types, so adjust if (!mt.IsLong) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 0381a249a6..51194adb8c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -2907,7 +2907,7 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio int count; // This is added back since removing it from here introduces regressions in Managed SNI. - // It forces SqlDataReader.ReadAsync() method to run synchronously, + // It forces SqlDataReader.ReadAsync() method to run synchronously, // and will block the calling thread until data is fed from SQL Server. // TODO Investigate better solution to support non-blocking ReadAsync(). stateObj._syncOverAsync = true; @@ -6629,8 +6629,8 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars { stateObj.WriteByte(mt.Precision); //propbytes: precision stateObj.WriteByte((byte)((decimal.GetBits((decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale - var d = (decimal)value; - WriteDecimal(d, stateObj); + WriteDecimal((decimal)value, stateObj); + WriteDecimal((decimal)value, stateObj); break; } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs index fbd51d482a..2d6eb82fba 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/GenericConverter.cs @@ -2,39 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Linq.Expressions; - namespace Microsoft.Data.SqlClient { + // This leverages the same assumptions in SqlBuffer that the JIT will optimize out the boxing / unboxing when TIn == TOut + // This behavior is proven out in the NoBoxingValueTypes BulkCopy unit test that benchmarks and measures the allocations internal static class GenericConverter { public static TOut Convert(TIn value) { - return GenericConverterHelper.Convert(value); - } - - /// - /// Note: this file is inherently different because the .NET Core JIT can leverage some better "smarts" to optimize out unnecessary casts - /// - private static class GenericConverterHelper - { - /// - /// Converts the value to the TOut type - /// - public static readonly Func Convert; - - static GenericConverterHelper() - { - var paramExpr = Expression.Parameter(typeof(TIn), "input"); - - var lambda = typeof(TIn) != typeof(TOut) - ? Expression.Lambda>(Expression.Convert(paramExpr, typeof(TOut)), paramExpr) - : Expression.Lambda>(paramExpr, paramExpr) - ; - - Convert = lambda.Compile(); - } - } + return (TOut)(object)value; + } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index fdfbc1a71b..c4beb7d98e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -186,8 +186,8 @@ private enum ValueSourceType DbDataReader } - // Enum for specifying SqlDataReader.Get method used - private enum ValueMethod : byte + // Enum for specifying SqlDataReader.Get / IDataReader Get method used + private enum ValueMethod { GetValue, SqlTypeSqlDecimal, @@ -195,7 +195,19 @@ private enum ValueMethod : byte SqlTypeSqlSingle, DataFeedStream, DataFeedText, - DataFeedXml + DataFeedXml, + GetInt32, + GetString, + GetDouble, + GetDecimal, + GetInt16, + GetInt64, + GetChar, + GetByte, + GetBoolean, + GetDateTime, + GetGuid, + GetFloat } // Used to hold column metadata for SqlDataReader case @@ -309,7 +321,7 @@ private int RowNumber // for debug purpose only. // TODO: I will make this internal to use Reflection. #if DEBUG - internal static bool _setAlwaysTaskOnWrite = false; //when set and in DEBUG mode, TdsParser::WriteBulkCopyValue will always return a task + internal static bool _setAlwaysTaskOnWrite = false; //when set and in DEBUG mode, TdsParser::WriteBulkCopyValue will always return a task internal static bool SetAlwaysTaskOnWrite { set @@ -541,8 +553,8 @@ private bool IsCopyOption(SqlBulkCopyOptions copyOption) return (_copyOptions & copyOption) == copyOption; } - //Creates the initial query string, but does not execute it. - // + //Creates the initial query string, but does not execute it. + // private string CreateInitialQuery() { string[] parts; @@ -563,7 +575,7 @@ private string CreateInitialQuery() TDSCommand = "select @@trancount; SET FMTONLY ON select * from " + ADP.BuildMultiPartName(parts) + " SET FMTONLY OFF "; if (_connection.IsShiloh) { - // If its a temp DB then try to connect + // If its a temp DB then try to connect string TableCollationsStoredProc; if (_connection.IsKatmaiOrNewer) @@ -626,9 +638,9 @@ private string CreateInitialQuery() } // Creates and then executes initial query to get information about the targettable - // When __isAsyncBulkCopy == false (i.e. it is Sync copy): out result contains the resulset. Returns null. - // When __isAsyncBulkCopy == true (i.e. it is Async copy): This still uses the _parser.Run method synchronously and return Task. - // We need to have a _parser.RunAsync to make it real async. + // When __isAsyncBulkCopy == false (i.e. it is Sync copy): out result contains the resulset. Returns null. + // When __isAsyncBulkCopy == true (i.e. it is Async copy): This still uses the _parser.Run method synchronously and return Task. + // We need to have a _parser.RunAsync to make it real async. private Task CreateAndExecuteInitialQueryAsync(out BulkCopySimpleResultSet result) { string TDSCommand = CreateInitialQuery(); @@ -1055,13 +1067,19 @@ private void Dispose(bool disposing) // free unmanaged objects } - // unified method to read a value from the current row - // - private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out bool isDataFeed, out bool isNull) + // Reads a cell and then writes it. + // Read may block at this moment since there is no getValueAsync or DownStream async at this moment. + // When _isAsyncBulkCopy == true: Write will return Task (when async method runs asynchronously) or Null (when async call actually ran synchronously) for performance. + // When _isAsyncBulkCopy == false: Writes are purely sync. This method return null at the end. + private Task ReadWriteColumnValueAsync(int destRowIndex) { _SqlMetaData metadata = _sortedColumnMappings[destRowIndex]._metadata; int sourceOrdinal = _sortedColumnMappings[destRowIndex]._sourceColumnOrdinal; + bool isSqlType = false; + bool isDataFeed = false; + bool isNull = false; + switch (_rowSourceType) { case ValueSourceType.IDataReader: @@ -1071,34 +1089,39 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b { if (_DbDataReaderRowSource.IsDBNull(sourceOrdinal)) { - isSqlType = false; - isDataFeed = false; isNull = true; - return DBNull.Value; + return WriteValueAsync(DBNull.Value, destRowIndex, isSqlType, isDataFeed, isNull); } else { - isSqlType = false; isDataFeed = true; - isNull = false; + + object feedColumnValue; + switch (_currentRowMetadata[destRowIndex].Method) { case ValueMethod.DataFeedStream: - return new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)); + feedColumnValue = new StreamDataFeed(_DbDataReaderRowSource.GetStream(sourceOrdinal)); + break; case ValueMethod.DataFeedText: - return new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)); + feedColumnValue = new TextDataFeed(_DbDataReaderRowSource.GetTextReader(sourceOrdinal)); + break; case ValueMethod.DataFeedXml: // Only SqlDataReader supports an XmlReader - // There is no GetXmlReader on DbDataReader, however if GetValue returns XmlReader we will read it as stream if it is assigned to XML field + // There is no GetXmlReader on DbDataReader, however if GetValue returns XmlReader we will read it as stream if it is assigned to XML field Debug.Assert(_SqlDataReaderRowSource != null, "Should not be reading row as an XmlReader if bulk copy source is not a SqlDataReader"); - return new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)); + feedColumnValue = new XmlDataFeed(_SqlDataReaderRowSource.GetXmlReader(sourceOrdinal)); + break; default: - Debug.Assert(false, string.Format("Current column is marked as being a DataFeed, but no DataFeed compatible method was provided. Method: {0}", _currentRowMetadata[destRowIndex].Method)); + Debug.Fail($"Current column is marked as being a DataFeed, but no DataFeed compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); isDataFeed = false; - object columnValue = _DbDataReaderRowSource.GetValue(sourceOrdinal); - ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return columnValue; + feedColumnValue = _DbDataReaderRowSource.GetValue(sourceOrdinal); + ADP.IsNullOrSqlType(feedColumnValue, out isNull, out isSqlType); + break; } + + //specifically choosing to use the object overload here to simplify TdsParser logic for the XmlReader scenario + return WriteValueAsync(feedColumnValue, destRowIndex, isSqlType, isDataFeed, isNull); } } // SqlDataReader-specific logic @@ -1106,36 +1129,28 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b { if (_currentRowMetadata[destRowIndex].IsSqlType) { - INullable value; isSqlType = true; - isDataFeed = false; switch (_currentRowMetadata[destRowIndex].Method) { case ValueMethod.SqlTypeSqlDecimal: - value = _SqlDataReaderRowSource.GetSqlDecimal(sourceOrdinal); - break; + var value = _SqlDataReaderRowSource.GetSqlDecimal(sourceOrdinal); + return WriteValueAsync(value, destRowIndex, isSqlType, isDataFeed, value.IsNull); case ValueMethod.SqlTypeSqlDouble: // use cast to handle IsNull correctly because no public constructor allows it - value = (SqlDecimal)_SqlDataReaderRowSource.GetSqlDouble(sourceOrdinal); - break; + var dblValue = (SqlDecimal)_SqlDataReaderRowSource.GetSqlDouble(sourceOrdinal); + return WriteValueAsync(dblValue, destRowIndex, isSqlType, isDataFeed, dblValue.IsNull); case ValueMethod.SqlTypeSqlSingle: - // use cast to handle IsNull correctly because no public constructor allows it - value = (SqlDecimal)_SqlDataReaderRowSource.GetSqlSingle(sourceOrdinal); - break; + // use cast to handle value.IsNull correctly because no public constructor allows it + var singleValue = (SqlDecimal)_SqlDataReaderRowSource.GetSqlSingle(sourceOrdinal); + return WriteValueAsync(singleValue, destRowIndex, isSqlType, isDataFeed, singleValue.IsNull); default: - Debug.Assert(false, string.Format("Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {0}", _currentRowMetadata[destRowIndex].Method)); - value = (INullable)_SqlDataReaderRowSource.GetSqlValue(sourceOrdinal); - break; + Debug.Fail($"Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); + var sqlValue = (INullable)_SqlDataReaderRowSource.GetSqlValue(sourceOrdinal); + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, sqlValue.IsNull); } - - isNull = value.IsNull; - return value; } else { - isSqlType = false; - isDataFeed = false; - object value = _SqlDataReaderRowSource.GetValue(sourceOrdinal); isNull = ((value == null) || (value == DBNull.Value)); if ((!isNull) && (metadata.type == SqlDbType.Udt)) @@ -1148,31 +1163,66 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b { Debug.Assert(!(value is INullable) || !((INullable)value).IsNull, "IsDBNull returned false, but GetValue returned a null INullable"); } -#endif - return value; +#endif + return WriteValueAsync(value, destRowIndex, isSqlType, isDataFeed, isNull); } } else { - isDataFeed = false; - IDataReader rowSourceAsIDataReader = (IDataReader)_rowSource; - // Back-compat with 4.0 and 4.5 - only use IsDbNull when streaming is enabled and only for non-SqlDataReader + // Only use IsDbNull when streaming is enabled and only for non-SqlDataReader if ((_enableStreaming) && (_SqlDataReaderRowSource == null) && (rowSourceAsIDataReader.IsDBNull(sourceOrdinal))) { - isSqlType = false; isNull = true; - return DBNull.Value; + return WriteValueAsync(DBNull.Value, destRowIndex, isSqlType, isDataFeed, isNull); } else { - object columnValue = rowSourceAsIDataReader.GetValue(sourceOrdinal); - ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return columnValue; + if (_currentRowMetadata[destRowIndex].Method == ValueMethod.GetValue || rowSourceAsIDataReader.IsDBNull(sourceOrdinal)) + { + object columnValue = rowSourceAsIDataReader.GetValue(sourceOrdinal); + ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); + return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + } + else + { + switch (_currentRowMetadata[sourceOrdinal].Method) + { + case ValueMethod.GetInt32: + return WriteValueAsync(rowSourceAsIDataReader.GetInt32(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, false); + case ValueMethod.GetString: + var strValue = rowSourceAsIDataReader.GetString(sourceOrdinal); + isNull = strValue == null; + return WriteValueAsync(strValue, destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetDouble: + return WriteValueAsync(rowSourceAsIDataReader.GetDouble(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetDecimal: + return WriteValueAsync(rowSourceAsIDataReader.GetDecimal(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetInt16: + return WriteValueAsync(rowSourceAsIDataReader.GetInt16(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetInt64: + return WriteValueAsync(rowSourceAsIDataReader.GetInt64(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetChar: + return WriteValueAsync(rowSourceAsIDataReader.GetChar(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetByte: + return WriteValueAsync(rowSourceAsIDataReader.GetByte(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetBoolean: + return WriteValueAsync(rowSourceAsIDataReader.GetBoolean(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetDateTime: + return WriteValueAsync(rowSourceAsIDataReader.GetDateTime(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetGuid: + return WriteValueAsync(rowSourceAsIDataReader.GetGuid(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + case ValueMethod.GetFloat: + return WriteValueAsync(rowSourceAsIDataReader.GetFloat(sourceOrdinal), destRowIndex, isSqlType, isDataFeed, isNull); + default: + object columnValue = rowSourceAsIDataReader.GetValue(sourceOrdinal); + ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); + return WriteValueAsync(columnValue, destRowIndex, isSqlType, isDataFeed, isNull); + } + } } } - case ValueSourceType.DataTable: case ValueSourceType.RowArray: { @@ -1180,6 +1230,7 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b Debug.Assert(sourceOrdinal < _currentRowLength, "inconsistency of length of rows from rowsource!"); isDataFeed = false; + // unfortunately this has to be boxed due to DataRow's API. object currentRowValue = _currentRow[sourceOrdinal]; ADP.IsNullOrSqlType(currentRowValue, out isNull, out isSqlType); @@ -1192,7 +1243,8 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b { if (isSqlType) { - return new SqlDecimal(((SqlSingle)currentRowValue).Value); + var sqlDec = new SqlDecimal(((SqlSingle)currentRowValue).Value); + return WriteValueAsync(sqlDec, destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -1200,16 +1252,20 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b if (!float.IsNaN(f)) { isSqlType = true; - return new SqlDecimal(f); + return WriteValueAsync(new SqlDecimal(f), destRowIndex, isSqlType, isDataFeed, isNull); + } + else + { + return WriteValueAsync(currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } - break; } } case ValueMethod.SqlTypeSqlDouble: { if (isSqlType) { - return new SqlDecimal(((SqlDouble)currentRowValue).Value); + var sqlValue = new SqlDecimal(((SqlDouble)currentRowValue).Value); + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } else { @@ -1217,37 +1273,44 @@ private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out b if (!double.IsNaN(d)) { isSqlType = true; - return new SqlDecimal(d); + return WriteValueAsync(new SqlDecimal(d), destRowIndex, isSqlType, isDataFeed, isNull); + } + else + { + return WriteValueAsync(currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } - break; } } case ValueMethod.SqlTypeSqlDecimal: { if (isSqlType) { - return (SqlDecimal)currentRowValue; + var sqlValue = (SqlDecimal)currentRowValue; + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } else { isSqlType = true; - return new SqlDecimal((Decimal)currentRowValue); + var sqlValue = new SqlDecimal((decimal)currentRowValue); + return WriteValueAsync(sqlValue, destRowIndex, isSqlType, isDataFeed, isNull); } } default: { - Debug.Assert(false, string.Format("Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {0}", _currentRowMetadata[destRowIndex].Method)); - break; + Debug.Fail($"Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); + // If we are here then either the value is null, there was no special storage type for this column or the special storage type wasn't handled (e.g. if the currentRowValue is NaN) + return WriteValueAsync(currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); } } } - - // If we are here then either the value is null, there was no special storage type for this column or the special storage type wasn't handled (e.g. if the currentRowValue is NaN) - return currentRowValue; + else + { + return WriteValueAsync(currentRowValue, destRowIndex, isSqlType, isDataFeed, isNull); + } } default: { - Debug.Assert(false, "ValueSourcType unspecified"); + Debug.Fail("ValueSourcType unspecified"); throw ADP.NotSupported(); } } @@ -1261,7 +1324,7 @@ private Task ReadFromRowSourceAsync(CancellationToken cts) { if (_isAsyncBulkCopy && (_DbDataReaderRowSource != null)) { - //This will call ReadAsync for DbDataReader (for SqlDataReader it will be truely async read; for non-SqlDataReader it may block.) + //This will call ReadAsync for DbDataReader (for SqlDataReader it will be truely async read; for non-SqlDataReader it may block.) return _DbDataReaderRowSource.ReadAsync(cts).ContinueWith((t) => { if (t.Status == TaskStatus.RanToCompletion) @@ -1374,7 +1437,7 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) else if (typeof(SqlSingle) == t || typeof(float) == t) { isSqlType = true; - method = ValueMethod.SqlTypeSqlSingle; // Source Type SqlSingle + method = ValueMethod.SqlTypeSqlSingle; // Source Type SqlSingle } else { @@ -1439,6 +1502,66 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) method = ValueMethod.GetValue; } } + else if (_rowSourceType == ValueSourceType.IDataReader) + { + isSqlType = false; + isDataFeed = false; + + Type t = ((IDataReader)_rowSource).GetFieldType(ordinal); + + if (t == typeof(bool)) + { + method = ValueMethod.GetBoolean; + } + else if (t == typeof(byte)) + { + method = ValueMethod.GetByte; + } + else if (t == typeof(char)) + { + method = ValueMethod.GetChar; + } + else if (t == typeof(DateTime)) + { + method = ValueMethod.GetDateTime; + } + else if (t == typeof(decimal)) + { + method = ValueMethod.GetDecimal; + } + else if (t == typeof(double)) + { + method = ValueMethod.GetDouble; + } + else if (t == typeof(float)) + { + method = ValueMethod.GetFloat; + } + else if (t == typeof(Guid)) + { + method = ValueMethod.GetGuid; + } + else if (t == typeof(short)) + { + method = ValueMethod.GetInt16; + } + else if (t == typeof(int)) + { + method = ValueMethod.GetInt32; + } + else if (t == typeof(long)) + { + method = ValueMethod.GetInt64; + } + else if (t == typeof(string)) + { + method = ValueMethod.GetString; + } + else + { + method = ValueMethod.GetValue; + } + } else { isSqlType = false; @@ -1449,8 +1572,6 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) return new SourceColumnMetadata(method, isSqlType, isDataFeed); } - // - // private void CreateOrValidateConnection(string method) { if (null == _connection) @@ -1581,13 +1702,14 @@ private string UnquotedName(string name) return name; } - private object ValidateBulkCopyVariant(object value) + private bool ValidateBulkCopyVariantIfNeeded(T value, out object variantValue) { - // from the spec: + variantValue = null; + + // From the spec: // "The only acceptable types are ..." // GUID, BIGVARBINARY, BIGBINARY, BIGVARCHAR, BIGCHAR, NVARCHAR, NCHAR, BIT, INT1, INT2, INT4, INT8, // MONEY4, MONEY, DECIMALN, NUMERICN, FTL4, FLT8, DATETIME4 and DATETIME - // MetaType metatype = MetaType.GetMetaTypeFromValue(value); switch (metatype.TDSType) { @@ -1610,21 +1732,22 @@ private object ValidateBulkCopyVariant(object value) case TdsEnums.SQLDATETIME2: case TdsEnums.SQLDATETIMEOFFSET: if (value is INullable) - { // Current limitation in the SqlBulkCopy Variant code limits BulkCopy to CLR/COM Types. - return MetaType.GetComValueFromSqlVariant(value); + { // Current limitation in the SqlBulkCopy Variant code limits BulkCopy to CLR/COM Types. + variantValue = MetaType.GetComValueFromSqlVariant(value); + return true; } else { - return value; + return false; } default: throw SQL.BulkLoadInvalidVariantValue(); } } - private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, ref bool isSqlType, out bool coercedToDataFeed) + private Task ConvertWriteValueAsync(T value, int col, _SqlMetaData metadata, bool isNull, bool isSqlType) { - coercedToDataFeed = false; + bool coercedToDataFeed = false; if (isNull) { @@ -1632,11 +1755,13 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re { throw SQL.BulkLoadBulkLoadNotAllowDBNull(metadata.column); } - return value; + + return DoWriteValueAsync(value, col, isSqlType, coercedToDataFeed, isNull, metadata); } MetaType type = metadata.metaType; bool typeChanged = false; + object objValue = null; // If the column is encrypted then we are going to transparently encrypt this column // (based on connection string setting)- Use the metaType for the underlying @@ -1661,56 +1786,55 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re { case TdsEnums.SQLNUMERICN: case TdsEnums.SQLDECIMALN: - mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); - - // Convert Source Decimal Precision and Scale to Destination Precision and Scale - // Fix Bug: 385971 sql decimal data could get corrupted on insert if the scale of - // the source and destination weren't the same. The BCP protocol, specifies the - // scale of the incoming data in the insert statement, we just tell the server we - // are inserting the same scale back. This then created a bug inside the BCP operation - // if the scales didn't match. The fix is to do the same thing that SQL Parameter does, - // and adjust the scale before writing. In Orcas is scale adjustment should be removed from - // SqlParameter and SqlBulkCopy and Isolated inside SqlParameter.CoerceValue, but because of - // where we are in the cycle, the changes must be kept at minimum, so I'm just bringing the - // code over to SqlBulkCopy. - - SqlDecimal sqlValue; - if ((isSqlType) && (!typeChanged)) + SqlDecimal decValue; + if (typeof(T) == typeof(decimal)) + { + decValue = new SqlDecimal(GenericConverter.Convert(value)); + } + else if (typeof(T) == typeof(SqlDecimal)) { - sqlValue = (SqlDecimal)value; + decValue = GenericConverter.Convert(value); } else { - sqlValue = new SqlDecimal((Decimal)value); + mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); + decValue = new SqlDecimal((decimal)SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false)); } - if (sqlValue.Scale != scale) + // Convert Source Decimal Precision and Scale to Destination Precision and Scale + // Sql decimal data could get corrupted on insert if the scale of + // the source and destination weren't the same. The BCP protocol, specifies the + // scale of the incoming data in the insert statement, we just tell the server we + // are inserting the same scale back. + if (decValue.Scale != scale) { - sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, scale); + decValue = TdsParser.AdjustSqlDecimalScale(decValue, scale); } - if (sqlValue.Precision > precision) + if (decValue.Precision > precision) { try { - sqlValue = SqlDecimal.ConvertToPrecScale(sqlValue, precision, sqlValue.Scale); + decValue = SqlDecimal.ConvertToPrecScale(decValue, precision, decValue.Scale); } catch (SqlTruncateException) { - throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(sqlValue)); + mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); + throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(decValue)); } catch (Exception e) { + mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e); } } // Perf: It is more efficient to write a SqlDecimal than a decimal since we need to break it into its 'bits' when writing - value = sqlValue; isSqlType = true; - typeChanged = false; // Setting this to false as SqlParameter.CoerceValue will only set it to true when converting to a CLR type - break; + typeChanged = false; // Setting this to false as SqlParameter.CoerceValue will only set it to true when converting to a CLR type + + // returning here to avoid unnecessary decValue initialization for all types + return WriteConvertedValue(decValue, col, isSqlType, isNull, coercedToDataFeed, metadata); case TdsEnums.SQLINTN: case TdsEnums.SQLFLTN: @@ -1734,16 +1858,22 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re case TdsEnums.SQLDATETIME2: case TdsEnums.SQLDATETIMEOFFSET: mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); + typeChanged = SqlParameter.CoerceValueIfNeeded(value, mt, out objValue, out coercedToDataFeed); break; case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); + typeChanged = SqlParameter.CoerceValueIfNeeded(value, mt, out objValue, out coercedToDataFeed, false); if (!coercedToDataFeed) { // We do not need to test for TextDataFeed as it is only assigned to (N)VARCHAR(MAX) - string str = ((isSqlType) && (!typeChanged)) ? ((SqlString)value).Value : ((string)value); + string str = typeChanged + ? (string)objValue + : isSqlType + ? GenericConverter.Convert(value).Value + : GenericConverter.Convert(value) + ; + int maxStringLength = length / 2; if (str.Length > maxStringLength) { @@ -1762,8 +1892,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re } break; case TdsEnums.SQLVARIANT: - value = ValidateBulkCopyVariant(value); - typeChanged = true; + typeChanged = ValidateBulkCopyVariantIfNeeded(value, out objValue); break; case TdsEnums.SQLUDT: // UDTs are sent as varbinary so we need to get the raw bytes @@ -1774,33 +1903,25 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re // in byte[] form. if (!(value is byte[])) { - value = _connection.GetBytes(value); + objValue = _connection.GetBytes(value); typeChanged = true; } break; case TdsEnums.SQLXMLTYPE: - // Could be either string, SqlCachedBuffer, XmlReader or XmlDataFeed + // Could be either string, SqlCachedBuffer, XmlReader or XmlDataFeed Debug.Assert((value is XmlReader) || (value is SqlCachedBuffer) || (value is string) || (value is SqlString) || (value is XmlDataFeed), "Invalid value type of Xml datatype"); - if (value is XmlReader) + if (value is XmlReader xmlReader) { - value = new XmlDataFeed((XmlReader)value); + objValue = new XmlDataFeed(xmlReader); typeChanged = true; coercedToDataFeed = true; } break; default: - Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null)); + Debug.Fail("Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null)); throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), null); } - - if (typeChanged) - { - // All type changes change to CLR types - isSqlType = false; - } - - return value; } catch (Exception e) { @@ -1810,6 +1931,17 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re } throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e); } + + if (typeChanged) + { + // All type changes change to CLR types + isSqlType = false; + return WriteConvertedValue(objValue, col, isSqlType, isNull, coercedToDataFeed, metadata); + } + else + { + return WriteConvertedValue(value, col, isSqlType, isNull, coercedToDataFeed, metadata); + } } /// @@ -1842,7 +1974,7 @@ public void WriteToServer(DbDataReader reader) _dataTableSource = null; _rowSourceType = ValueSourceType.DbDataReader; _isAsyncBulkCopy = false; - WriteRowSourceToServerAsync(reader.FieldCount, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; + WriteRowSourceToServerAsync(reader.FieldCount, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; } finally { @@ -1879,7 +2011,7 @@ public void WriteToServer(IDataReader reader) _dataTableSource = null; _rowSourceType = ValueSourceType.IDataReader; _isAsyncBulkCopy = false; - WriteRowSourceToServerAsync(reader.FieldCount, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; + WriteRowSourceToServerAsync(reader.FieldCount, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; } finally { @@ -1920,7 +2052,7 @@ public void WriteToServer(DataTable table, DataRowState rowState) _rowEnumerator = table.Rows.GetEnumerator(); _isAsyncBulkCopy = false; - WriteRowSourceToServerAsync(table.Columns.Count, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; + WriteRowSourceToServerAsync(table.Columns.Count, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; } finally { @@ -1964,7 +2096,7 @@ public void WriteToServer(DataRow[] rows) _rowEnumerator = rows.GetEnumerator(); _isAsyncBulkCopy = false; - WriteRowSourceToServerAsync(table.Columns.Count, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; + WriteRowSourceToServerAsync(table.Columns.Count, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; } finally { @@ -2024,7 +2156,7 @@ public Task WriteToServerAsync(DataRow[] rows, CancellationToken cancellationTok _rowSourceType = ValueSourceType.RowArray; _rowEnumerator = rows.GetEnumerator(); _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; + resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; } finally { @@ -2065,7 +2197,7 @@ public Task WriteToServerAsync(DbDataReader reader, CancellationToken cancellati _dataTableSource = null; _rowSourceType = ValueSourceType.DbDataReader; _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; + resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; } finally { @@ -2106,7 +2238,7 @@ public Task WriteToServerAsync(IDataReader reader, CancellationToken cancellatio _dataTableSource = null; _rowSourceType = ValueSourceType.IDataReader; _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; + resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; } finally { @@ -2160,7 +2292,7 @@ public Task WriteToServerAsync(DataTable table, DataRowState rowState, Cancellat _rowSourceType = ValueSourceType.DataTable; _rowEnumerator = table.Rows.GetEnumerator(); _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; + resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; } finally { @@ -2169,7 +2301,7 @@ public Task WriteToServerAsync(DataTable table, DataRowState rowState, Cancellat return resultTask; } - // Writes row source. + // Writes row source. // private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctoken) { @@ -2427,34 +2559,40 @@ private bool FireRowsCopiedEvent(long rowsCopied) return eventArgs.Abort; } - // Reads a cell and then writes it. - // Read may block at this moment since there is no getValueAsync or DownStream async at this moment. - // When _isAsyncBulkCopy == true: Write will return Task (when async method runs asynchronously) or Null (when async call actually ran synchronously) for performance. - // When _isAsyncBulkCopy == false: Writes are purely sync. This method reutrn null at the end. - // - private Task ReadWriteColumnValueAsync(int col) + private Task WriteValueAsync(T value, int col, bool isSqlType, bool isDataFeed, bool isNull) { - bool isSqlType; - bool isDataFeed; - bool isNull; - Object value = GetValueFromSourceRow(col, out isSqlType, out isDataFeed, out isNull); //this will return Task/null in future: as rTask - _SqlMetaData metadata = _sortedColumnMappings[col]._metadata; - if (!isDataFeed) + if (isDataFeed) { - value = ConvertValue(value, metadata, isNull, ref isSqlType, out isDataFeed); + //nothing to convert, skip straight to write + return DoWriteValueAsync(value, col, isSqlType, isDataFeed, isNull, metadata); + } + else + { + return ConvertWriteValueAsync(value, col, metadata, isNull, isSqlType); + } + } - // If column encryption is requested via connection string option, perform encryption here - if (!isNull && // if value is not NULL - metadata.isEncrypted) - { // If we are transparently encrypting - Debug.Assert(_parser.ShouldEncryptValuesForBulkCopy()); - value = _parser.EncryptColumnValue(value, metadata, metadata.column, _stateObj, isDataFeed, isSqlType); - isSqlType = false; // Its not a sql type anymore - } + private Task WriteConvertedValue(T value, int col, bool isSqlType, bool isNull, bool isDatafeed, _SqlMetaData metadata) + { + // If column encryption is requested via connection string option, perform encryption here + if (!isNull && // if value is not NULL + metadata.isEncrypted) + { // If we are transparently encrypting + Debug.Assert(_parser.ShouldEncryptValuesForBulkCopy()); + var bytesValue = _parser.EncryptColumnValue(value, metadata, metadata.column, _stateObj, isDatafeed, isSqlType); + isSqlType = false; // Its not a sql type anymore + + return DoWriteValueAsync(bytesValue, col, isSqlType, isDatafeed, isNull, metadata); } + else + { + return DoWriteValueAsync(value, col, isSqlType, isDatafeed, isNull, metadata); + } + } - //write part + private Task DoWriteValueAsync(T value, int col, bool isSqlType, bool isDataFeed, bool isNull, _SqlMetaData metadata) + { Task writeTask = null; if (metadata.type != SqlDbType.Variant) { @@ -2473,15 +2611,15 @@ private Task ReadWriteColumnValueAsync(int col) if (variantInternalType == SqlBuffer.StorageType.DateTime2) { - _parser.WriteSqlVariantDateTime2(((DateTime)value), _stateObj); + _parser.WriteSqlVariantDateTime2(GenericConverter.Convert(value), _stateObj); } else if (variantInternalType == SqlBuffer.StorageType.Date) { - _parser.WriteSqlVariantDate(((DateTime)value), _stateObj); + _parser.WriteSqlVariantDate(GenericConverter.Convert(value), _stateObj); } else { - writeTask = _parser.WriteSqlVariantDataRowValue(value, _stateObj); //returns Task/Null + writeTask = _parser.WriteSqlVariantDataRowValue(value, isNull, _stateObj); //returns Task/Null } } @@ -2563,7 +2701,7 @@ private void CopyColumnsAsyncSetupContinuation(TaskCompletionSource sour } - // The notification logic. + // The notification logic. // private void CheckAndRaiseNotification() { @@ -2636,11 +2774,11 @@ private void CheckAndRaiseNotification() Debug.Assert(writeTask == null, "Task should not pend while doing sync bulk copy"); RunParser(); AbortTransaction(); - throw exception; //this will be caught and put inside the Task's exception. + throw exception; //this will be caught and put inside the Task's exception. } } - // Checks for cancellation. If cancel requested, cancels the task and returns the cancelled task + // Checks for cancellation. If cancel requested, cancels the task and returns the cancelled task Task CheckForCancellation(CancellationToken cts, TaskCompletionSource tcs) { if (cts.IsCancellationRequested) @@ -2688,7 +2826,7 @@ private Task CopyRowsAsync(int rowsSoFar, int totalRows, CancellationToken cts, int i; try { - //totalRows is batchsize which is 0 by default. In that case, we keep copying till the end (until _hasMoreRowToCopy == false). + //totalRows is batchsize which is 0 by default. In that case, we keep copying till the end (until _hasMoreRowToCopy == false). for (i = rowsSoFar; (totalRows <= 0 || i < totalRows) && _hasMoreRowToCopy == true; i++) { if (_isAsyncBulkCopy == true) @@ -2705,10 +2843,10 @@ private Task CopyRowsAsync(int rowsSoFar, int totalRows, CancellationToken cts, task = CopyColumnsAsync(0); //copy 1 row if (task == null) - { //tsk is done. + { //tsk is done. CheckAndRaiseNotification(); //check notification logic after copying the row - //now we will read the next row. + //now we will read the next row. Task readTask = ReadFromRowSourceAsync(cts); // read the next row. Caution: more is only valid if the task returns null. Otherwise, we wait for Task.Result if (readTask != null) { @@ -3046,7 +3184,7 @@ private void CleanUpStateObject(bool isCancelRequested = true) // The continuation part of WriteToServerInternalRest. Executes when the initial query task is completed. (see, WriteToServerInternalRest). // It carries on the source which is passed from the WriteToServerInternalRest and performs SetResult when the entire copy is done. // The carried on source may be null in case of Sync copy. So no need to SetResult at that time. - // It launches the copy operation. + // It launches the copy operation. // private void WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet internalResults, CancellationToken cts, TaskCompletionSource source) { @@ -3081,7 +3219,7 @@ private void WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet int } AsyncHelper.ContinueTask(task, source, () => { - //Bulk copy task is completed at this moment. + //Bulk copy task is completed at this moment. //Todo: The cases may be combined for code reuse. if (task.IsCanceled) { @@ -3167,7 +3305,7 @@ private void WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet int } } - // Rest of the WriteToServerInternalAsync method. + // Rest of the WriteToServerInternalAsync method. // It carries on the source from its caller WriteToServerInternal. // source is null in case of Sync bcp. But valid in case of Async bcp. // It calls the WriteToServerInternalRestContinuedAsync as a continuation of the initial query task. @@ -3213,7 +3351,7 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio regReconnectCancel = cts.Register(() => cancellableReconnectTS.TrySetCanceled()); } AsyncHelper.ContinueTask(reconnectTask, cancellableReconnectTS, () => { cancellableReconnectTS.SetResult(null); }); - // no need to cancel timer since SqlBulkCopy creates specific task source for reconnection + // no need to cancel timer since SqlBulkCopy creates specific task source for reconnection AsyncHelper.SetTimeoutException(cancellableReconnectTS, BulkCopyTimeout, () => { return SQL.BulkLoadInvalidDestinationTable(_destinationTableName, SQL.CR_ReconnectTimeout()); }, CancellationToken.None); AsyncHelper.ContinueTask(cancellableReconnectTS.Task, source, @@ -3256,7 +3394,7 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio _connection.AddWeakReference(this, SqlReferenceCollection.BulkCopyTag); } - internalConnection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already have the parser lock + internalConnection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already have the parser lock try { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs index 12a9257fbd..6674316123 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -16,10 +16,8 @@ using Microsoft.Data.Common; using Microsoft.Data.SqlClient.Server; - namespace Microsoft.Data.SqlClient { - internal abstract class DataFeed { } @@ -109,7 +107,7 @@ internal SqlCipherMetadata CipherMetadata /// /// Indicates if the parameter encryption metadata received by sp_describe_parameter_encryption. - /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate + /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate /// that no encryption is needed). /// internal bool HasReceivedMetadata { get; set; } @@ -461,7 +459,7 @@ internal SmiParameterMetaData MetaDataForSmi(out ParameterPeekAheadValue peekAhe long actualLen = GetActualSize(); long maxLen = this.Size; - // GetActualSize returns bytes length, but smi expects char length for + // GetActualSize returns bytes length, but smi expects char length for // character types, so adjust if (!mt.IsLong) { @@ -782,10 +780,10 @@ public SqlDbType SqlDbType { MetaType metatype = _metaType; // HACK!!! - // We didn't want to expose SmallVarBinary on SqlDbType so we - // stuck it at the end of SqlDbType in v1.0, except that now + // We didn't want to expose SmallVarBinary on SqlDbType so we + // stuck it at the end of SqlDbType in v1.0, except that now // we have new data types after that and it's smack dab in the - // middle of the valid range. To prevent folks from setting + // middle of the valid range. To prevent folks from setting // this invalid value we have to have this code here until we // can take the time to fix it later. if ((SqlDbType)TdsEnums.SmallVarBinary == value) @@ -1088,14 +1086,25 @@ object ICloneable.Clone() // Coerced Value is also used in SqlBulkCopy.ConvertValue(object value, _SqlMetaData metadata) internal static object CoerceValue(object value, MetaType destinationType, out bool coercedToDataFeed, out bool typeChanged, bool allowStreaming = true) + { + typeChanged = CoerceValueIfNeeded(value, destinationType, out var objValue, out coercedToDataFeed, allowStreaming); + + return typeChanged ? objValue : value; + } + + internal static bool CoerceValueIfNeeded(T value, MetaType destinationType, out object objValue, out bool coercedToDataFeed, bool allowStreaming = true) { Debug.Assert(!(value is DataFeed), "Value provided should not already be a data feed"); Debug.Assert(!ADP.IsNull(value), "Value provided should not be null"); Debug.Assert(null != destinationType, "null destinationType"); coercedToDataFeed = false; - typeChanged = false; - Type currentType = value.GetType(); + objValue = null; + Type currentType = typeof(T) == typeof(object) + ? value.GetType() // only call GetType if we know boxing has already occurred. + : typeof(T); + + var typeChanged = false; if ((typeof(object) != destinationType.ClassType) && (currentType != destinationType.ClassType) && @@ -1110,45 +1119,45 @@ internal static object CoerceValue(object value, MetaType destinationType, out b // For Xml data, destination Type is always string if (typeof(SqlXml) == currentType) { - value = MetaType.GetStringFromXml((XmlReader)(((SqlXml)value).CreateReader())); + objValue = MetaType.GetStringFromXml(GenericConverter.Convert(value).CreateReader()); } else if (typeof(SqlString) == currentType) { typeChanged = false; // Do nothing } - else if (typeof(XmlReader).IsAssignableFrom(currentType)) + else if (value is XmlReader xmlReader) { if (allowStreaming) { coercedToDataFeed = true; - value = new XmlDataFeed((XmlReader)value); + objValue = new XmlDataFeed(xmlReader); } else { - value = MetaType.GetStringFromXml((XmlReader)value); + objValue = MetaType.GetStringFromXml(xmlReader); } } else if (typeof(char[]) == currentType) { - value = new string((char[])value); + objValue = new string(GenericConverter.Convert(value)); } else if (typeof(SqlChars) == currentType) { - value = new string(((SqlChars)value).Value); + objValue = new string(GenericConverter.Convert(value).Value); } - else if (value is TextReader && allowStreaming) + else if (value is TextReader tr && allowStreaming) { coercedToDataFeed = true; - value = new TextDataFeed((TextReader)value); + objValue = new TextDataFeed(tr); } else { - value = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); + objValue = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); } } else if ((DbType.Currency == destinationType.DbType) && (typeof(string) == currentType)) { - value = Decimal.Parse((string)value, NumberStyles.Currency, (IFormatProvider)null); // WebData 99376 + objValue = decimal.Parse(GenericConverter.Convert(value), NumberStyles.Currency, (IFormatProvider)null); } else if ((typeof(SqlBytes) == currentType) && (typeof(byte[]) == destinationType.ClassType)) { @@ -1156,32 +1165,32 @@ internal static object CoerceValue(object value, MetaType destinationType, out b } else if ((typeof(string) == currentType) && (SqlDbType.Time == destinationType.SqlDbType)) { - value = TimeSpan.Parse((string)value); + objValue = TimeSpan.Parse(GenericConverter.Convert(value)); } else if ((typeof(string) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) { - value = DateTimeOffset.Parse((string)value, (IFormatProvider)null); + objValue = DateTimeOffset.Parse(GenericConverter.Convert(value), (IFormatProvider)null); } else if ((typeof(DateTime) == currentType) && (SqlDbType.DateTimeOffset == destinationType.SqlDbType)) { - value = new DateTimeOffset((DateTime)value); + objValue = new DateTimeOffset(GenericConverter.Convert(value)); } - else if (TdsEnums.SQLTABLE == destinationType.TDSType && - (value is DataTable || + else if (TdsEnums.SQLTABLE == destinationType.TDSType && ( + value is DataTable || value is DbDataReader || value is System.Collections.Generic.IEnumerable)) { // no conversion for TVPs. typeChanged = false; } - else if (destinationType.ClassType == typeof(byte[]) && value is Stream && allowStreaming) + else if (destinationType.ClassType == typeof(byte[]) && allowStreaming && value is Stream stream) { coercedToDataFeed = true; - value = new StreamDataFeed((Stream)value); + objValue = new StreamDataFeed(stream); } else { - value = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); + objValue = Convert.ChangeType(value, destinationType.ClassType, (IFormatProvider)null); } } catch (Exception e) @@ -1198,7 +1207,7 @@ value is DbDataReader || Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); Debug.Assert(value.GetType() == currentType ^ typeChanged, "Incorrect value for typeChanged"); - return value; + return typeChanged; } internal void FixStreamDataForNonPLP() @@ -1791,7 +1800,7 @@ internal void Validate(int index, bool isCommandProc) MetaType metaType = GetMetaTypeOnly(); _internalMetaType = metaType; - // NOTE: (General Criteria): SqlParameter does a Size Validation check and would fail if the size is 0. + // NOTE: (General Criteria): SqlParameter does a Size Validation check and would fail if the size is 0. // This condition filters all scenarios where we view a valid size 0. if (ADP.IsDirection(this, ParameterDirection.Output) && !ADP.IsDirection(this, ParameterDirection.ReturnValue) && // SQL BU DT 372370 @@ -1864,15 +1873,15 @@ internal MetaType ValidateTypeLengths(bool yukonOrNewer) // Bug: VSTFDevDiv #636867 // Notes: - // 'actualSizeInBytes' is the size of value passed; + // 'actualSizeInBytes' is the size of value passed; // 'sizeInCharacters' is the parameter size; - // 'actualSizeInBytes' is in bytes; - // 'this.Size' is in charaters; - // 'sizeInCharacters' is in characters; + // 'actualSizeInBytes' is in bytes; + // 'this.Size' is in charaters; + // 'sizeInCharacters' is in characters; // 'TdsEnums.TYPE_SIZE_LIMIT' is in bytes; // For Non-NCharType and for non-Yukon or greater variables, size should be maintained; // Reverting changes from bug VSTFDevDiv # 479739 as it caused an regression; - // Modifed variable names from 'size' to 'sizeInCharacters', 'actualSize' to 'actualSizeInBytes', and + // Modifed variable names from 'size' to 'sizeInCharacters', 'actualSize' to 'actualSizeInBytes', and // 'maxSize' to 'maxSizeInBytes' // The idea is to // 1) revert the regression from bug 479739 diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 9c7bdee4c4..4b404fd00f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -695,7 +695,7 @@ internal void Connect(ServerInfo serverInfo, } // Retrieve the IP and port number from native SNI for TCP protocol. The IP information is stored temporarily in the - // pendingSQLDNSObject but not in the DNS Cache at this point. We only add items to the DNS Cache after we receive the + // pendingSQLDNSObject but not in the DNS Cache at this point. We only add items to the DNS Cache after we receive the // IsSupported flag as true in the feature ext ack from server. internal void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey) { @@ -7422,12 +7422,12 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars // Therefore the sql_variant value must not include the MaxLength. This is the major difference // between this method and WriteSqlVariantValue above. // - internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObj, bool canAccumulate = true) + internal Task WriteSqlVariantDataRowValue(T value, bool isNull, TdsParserStateObject stateObj, bool canAccumulate = true) { Debug.Assert(_isShiloh == true, "Shouldn't be dealing with sql_variant in pre-SQL2000 server!"); // handle null values - if ((null == value) || (DBNull.Value == value)) + if (isNull) { WriteInt(TdsEnums.FIXEDNULL, stateObj); return null; @@ -7438,44 +7438,44 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta if (metatype.IsAnsiType) { - length = GetEncodingCharLength((string)value, length, 0, _defaultEncoding); + length = GetEncodingCharLength(GenericConverter.Convert(value), length, 0, _defaultEncoding); } switch (metatype.TDSType) { case TdsEnums.SQLFLT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteFloat((Single)value, stateObj); + WriteFloat(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLFLT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteDouble((Double)value, stateObj); + WriteDouble(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteLong((Int64)value, stateObj); + WriteLong(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteInt((Int32)value, stateObj); + WriteInt(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT2: WriteSqlVariantHeader(4, metatype.TDSType, metatype.PropBytes, stateObj); - WriteShort((Int16)value, stateObj); + WriteShort(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLINT1: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); - stateObj.WriteByte((byte)value); + stateObj.WriteByte(GenericConverter.Convert(value)); break; case TdsEnums.SQLBIT: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); - if ((bool)value == true) + if (GenericConverter.Convert(value)) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -7484,7 +7484,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLBIGVARBINARY: { - byte[] b = (byte[])value; + byte[] b = GenericConverter.Convert(value); length = b.Length; WriteSqlVariantHeader(4 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -7494,7 +7494,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLBIGVARCHAR: { - string s = (string)value; + string s = GenericConverter.Convert(value); length = s.Length; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -7506,7 +7506,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLUNIQUEID: { - System.Guid guid = (System.Guid)value; + Guid guid = GenericConverter.Convert(value); byte[] b = guid.ToByteArray(); length = b.Length; @@ -7518,7 +7518,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLNVARCHAR: { - string s = (string)value; + string s = GenericConverter.Convert(value); length = s.Length * 2; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); @@ -7533,7 +7533,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLDATETIME: { - TdsDateTime dt = MetaType.FromDateTime((DateTime)value, 8); + TdsDateTime dt = MetaType.FromDateTime(GenericConverter.Convert(value), 8); WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); WriteInt(dt.days, stateObj); @@ -7544,7 +7544,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLMONEY: { WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteCurrency((Decimal)value, 8, stateObj); + WriteCurrency(GenericConverter.Convert(value), 8, stateObj); break; } @@ -7552,21 +7552,22 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta { WriteSqlVariantHeader(21, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Precision); //propbytes: precision - stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale - WriteDecimal((Decimal)value, stateObj); + var decValue = GenericConverter.Convert(value); + stateObj.WriteByte((byte)((decimal.GetBits(decValue)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale + WriteDecimal(decValue, stateObj); break; } case TdsEnums.SQLTIME: WriteSqlVariantHeader(8, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale - WriteTime((TimeSpan)value, metatype.Scale, 5, stateObj); + WriteTime(GenericConverter.Convert(value), metatype.Scale, 5, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: WriteSqlVariantHeader(13, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale - WriteDateTimeOffset((DateTimeOffset)value, metatype.Scale, 10, stateObj); + WriteDateTimeOffset(GenericConverter.Convert(value), metatype.Scale, 10, stateObj); break; default: @@ -11321,7 +11322,7 @@ internal bool ShouldEncryptValuesForBulkCopy() /// Encrypts a column value (for SqlBulkCopy) /// /// - internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) + internal byte[] EncryptColumnValue(T value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) { Debug.Assert(_serverSupportsColumnEncryption, "Server doesn't support encryption, yet we received encryption metadata"); Debug.Assert(ShouldEncryptValuesForBulkCopy(), "Encryption attempted when not requested"); @@ -11346,10 +11347,14 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin // when we normalize and serialize the data buffers. The serialization routine expects us // to report the size of data to be copied out (for serialization). If we underreport the // size, truncation will happen for us! - actualLengthInBytes = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; + actualLengthInBytes = (isSqlType) + ? GenericConverter.Convert(value).Length + : GenericConverter.Convert(value).Length; + if (metadata.baseTI.length > 0 && actualLengthInBytes > metadata.baseTI.length) - { // see comments agove + { + // see comments above actualLengthInBytes = metadata.baseTI.length; } break; @@ -11365,7 +11370,10 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin ThrowUnsupportedCollationEncountered(null); // stateObject only when reading } - string stringValue = (isSqlType) ? ((SqlString)value).Value : (string)value; + string stringValue = (isSqlType) + ? GenericConverter.Convert(value).Value + : GenericConverter.Convert(value); + actualLengthInBytes = _defaultEncoding.GetByteCount(stringValue); // If the string length is > max length, then use the max length (see comments above) @@ -11379,7 +11387,10 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: - actualLengthInBytes = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; + actualLengthInBytes = (isSqlType + ? GenericConverter.Convert(value).Value.Length + : GenericConverter.Convert(value).Length) + * 2; if (metadata.baseTI.length > 0 && actualLengthInBytes > metadata.baseTI.length) @@ -11424,7 +11435,7 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin _connHandler.ConnectionOptions.DataSource); } - internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) + internal Task WriteBulkCopyValue(T value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) { Debug.Assert(!isSqlType || value is INullable, "isSqlType is true, but value can not be type cast to an INullable"); Debug.Assert(!isDataFeed ^ value is DataFeed, "Incorrect value for isDataFeed"); @@ -11489,10 +11500,12 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: - ccb = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; + ccb = (isSqlType) + ? GenericConverter.Convert(value).Length + : GenericConverter.Convert(value).Length; break; case TdsEnums.SQLUNIQUEID: - ccb = GUID_SIZE; // that's a constant for guid + ccb = GUID_SIZE; break; case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLBIGVARCHAR: @@ -11505,11 +11518,11 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars string stringValue = null; if (isSqlType) { - stringValue = ((SqlString)value).Value; + stringValue = GenericConverter.Convert(value).Value; } else { - stringValue = (string)value; + stringValue = GenericConverter.Convert(value); } ccb = stringValue.Length; @@ -11518,15 +11531,22 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: - ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; + ccb = (isSqlType + ? GenericConverter.Convert(value).Value.Length + : GenericConverter.Convert(value).Length + ) * 2; break; case TdsEnums.SQLXMLTYPE: // Value here could be string or XmlReader - if (value is XmlReader) + // the XmlReader scenario can only occur when T is object (enforced during SqlBulkCopy.ReadWriteColumnValueAsync) + if (typeof(T) == typeof(object) && value is XmlReader xr) { - value = MetaType.GetStringFromXml((XmlReader)value); + value = GenericConverter.Convert(MetaType.GetStringFromXml(xr)); } - ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; + ccb = (isSqlType + ? GenericConverter.Convert(value).Value.Length + : GenericConverter.Convert(value).Length + ) * 2; break; default: @@ -11578,7 +11598,9 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars } else if (metatype.SqlDbType != SqlDbType.Udt || metatype.IsLong) { + // we only have to consider a conversion from above in this case. internalWriteTask = WriteValue(value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed); + if ((internalWriteTask == null) && (_asyncWrite)) { internalWriteTask = stateObj.WaitForAccumulatedWrites(); @@ -11588,7 +11610,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars else { WriteShort(ccb, stateObj); - internalWriteTask = stateObj.WriteByteArray((byte[])value, ccb, 0); + internalWriteTask = stateObj.WriteByteArray(GenericConverter.Convert(value), ccb, 0); } #if DEBUG @@ -11924,7 +11946,7 @@ private bool IsBOMNeeded(MetaType type, object value) return false; } - private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaType type, int actualLength, TdsParserStateObject stateObj, bool isDataFeed) + private Task GetTerminationTask(Task unterminatedWriteTask, MetaType type, int actualLength, TdsParserStateObject stateObj, bool isDataFeed) { if (type.IsPlp && ((actualLength > 0) || isDataFeed)) { @@ -11947,16 +11969,16 @@ private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaTy } - private Task WriteSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) + private Task WriteSqlValue(T value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) { return GetTerminationTask( WriteUnterminatedSqlValue(value, type, actualLength, codePageByteSize, offset, stateObj), - value, type, actualLength, stateObj, false); + type, actualLength, stateObj, false); } // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) + private Task WriteUnterminatedSqlValue(T value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) { Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) || (value is INullable && !((INullable)value).IsNull)), @@ -11967,11 +11989,11 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat(((SqlSingle)value).Value, stateObj); + WriteFloat(GenericConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble(((SqlDouble)value).Value, stateObj); + WriteDouble(GenericConverter.Convert(value).Value, stateObj); } break; @@ -11987,18 +12009,18 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe if (value is SqlBinary) { - return stateObj.WriteByteArray(((SqlBinary)value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value).Value, actualLength, offset, canAccumulate: false); } else { Debug.Assert(value is SqlBytes); - return stateObj.WriteByteArray(((SqlBytes)value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value).Value, actualLength, offset, canAccumulate: false); } } case TdsEnums.SQLUNIQUEID: { - byte[] b = ((SqlGuid)value).ToByteArray(); + byte[] b = GenericConverter.Convert(value).ToByteArray(); Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); stateObj.WriteByteArray(b, actualLength, 0); @@ -12008,7 +12030,7 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); - if (((SqlBoolean)value).Value == true) + if (GenericConverter.Convert(value).Value == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -12018,17 +12040,17 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLINTN: if (type.FixedLength == 1) - stateObj.WriteByte(((SqlByte)value).Value); + stateObj.WriteByte(GenericConverter.Convert(value).Value); else if (type.FixedLength == 2) - WriteShort(((SqlInt16)value).Value, stateObj); + WriteShort(GenericConverter.Convert(value).Value, stateObj); else if (type.FixedLength == 4) - WriteInt(((SqlInt32)value).Value, stateObj); + WriteInt(GenericConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong(((SqlInt64)value).Value, stateObj); + WriteLong(GenericConverter.Convert(value).Value, stateObj); } break; @@ -12040,16 +12062,16 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe { WriteInt(codePageByteSize, stateObj); // chunk length } - if (value is System.Data.SqlTypes.SqlChars) + if (value is SqlChars) { - String sch = new String(((System.Data.SqlTypes.SqlChars)value).Value); + string sch = new string(GenericConverter.Convert(value).Value); return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); - return WriteEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); + return WriteEncodingChar(GenericConverter.Convert(value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } @@ -12076,27 +12098,27 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe if (actualLength != 0) actualLength >>= 1; - if (value is System.Data.SqlTypes.SqlChars) + if (value is SqlChars) { - return WriteCharArray(((System.Data.SqlTypes.SqlChars)value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteCharArray(GenericConverter.Convert(value).Value, actualLength, offset, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); - return WriteString(((SqlString)value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteString(GenericConverter.Convert(value).Value, actualLength, offset, stateObj, canAccumulate: false); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - WriteSqlDecimal((SqlDecimal)value, stateObj); + WriteSqlDecimal(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLDATETIMN: - SqlDateTime dt = (SqlDateTime)value; + SqlDateTime dt = GenericConverter.Convert(value); if (type.FixedLength == 4) { - if (0 > dt.DayTicks || dt.DayTicks > UInt16.MaxValue) + if (0 > dt.DayTicks || dt.DayTicks > ushort.MaxValue) throw SQL.SmallDateTimeOverflow(dt.ToString()); WriteShort(dt.DayTicks, stateObj); @@ -12112,12 +12134,12 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLMONEYN: { - WriteSqlMoney((SqlMoney)value, type.FixedLength, stateObj); + WriteSqlMoney(GenericConverter.Convert(value), type.FixedLength, stateObj); break; } case TdsEnums.SQLUDT: - Debug.Assert(false, "Called WriteSqlValue on UDT param.Should have already been handled"); + Debug.Fail("Called WriteSqlValue on UDT param.Should have already been handled"); throw SQL.UDTUnexpectedResult(value.GetType().AssemblyQualifiedName); default: @@ -12622,28 +12644,28 @@ private Task NullIfCompletedWriteTask(Task task) } } - private Task WriteValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) + private Task WriteValue(T value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) { return GetTerminationTask(WriteUnterminatedValue(value, type, scale, actualLength, encodingByteSize, offset, stateObj, paramSize, isDataFeed), - value, type, actualLength, stateObj, isDataFeed); + type, actualLength, stateObj, isDataFeed); } // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) + private Task WriteUnterminatedValue(T value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) { - Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object"); + Debug.Assert((null != value) && !(value is DBNull), "unexpected missing or empty object"); // parameters are always sent over as BIG or N types switch (type.NullableType) { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat((Single)value, stateObj); + WriteFloat(GenericConverter.Convert(value), stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble((Double)value, stateObj); + WriteDouble(GenericConverter.Convert(value), stateObj); } break; @@ -12660,7 +12682,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int if (isDataFeed) { Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!"); - return NullIfCompletedWriteTask(WriteStreamFeed((StreamDataFeed)value, stateObj, paramSize)); + return NullIfCompletedWriteTask(WriteStreamFeed(GenericConverter.Convert(value), stateObj, paramSize)); } else { @@ -12668,14 +12690,13 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { WriteInt(actualLength, stateObj); // chunk length } - - return stateObj.WriteByteArray((byte[])value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value), actualLength, offset, canAccumulate: false); } } case TdsEnums.SQLUNIQUEID: { - System.Guid guid = (System.Guid)value; + System.Guid guid = GenericConverter.Convert(value); byte[] b = guid.ToByteArray(); Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); @@ -12686,7 +12707,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); - if ((bool)value == true) + if (GenericConverter.Convert(value) == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); @@ -12696,15 +12717,15 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLINTN: if (type.FixedLength == 1) - stateObj.WriteByte((byte)value); + stateObj.WriteByte(GenericConverter.Convert(value)); else if (type.FixedLength == 2) - WriteShort((Int16)value, stateObj); + WriteShort(GenericConverter.Convert(value), stateObj); else if (type.FixedLength == 4) - WriteInt((Int32)value, stateObj); + WriteInt(GenericConverter.Convert(value), stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong((Int64)value, stateObj); + WriteLong(GenericConverter.Convert(value), stateObj); } break; @@ -12722,7 +12743,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { - return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); + return NullIfCompletedWriteTask(WriteXmlFeed(GenericConverter.Convert(value), stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); } else { @@ -12737,11 +12758,11 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value - return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value), actualLength, 0, canAccumulate: false); } else { - return WriteEncodingChar((string)value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); + return WriteEncodingChar(GenericConverter.Convert(value), actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } } } @@ -12759,7 +12780,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { - return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); + return NullIfCompletedWriteTask(WriteXmlFeed(GenericConverter.Convert(value), stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); } else { @@ -12782,29 +12803,29 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value - return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false); + return stateObj.WriteByteArray(GenericConverter.Convert(value), actualLength, 0, canAccumulate: false); } else { // convert to cchars instead of cbytes actualLength >>= 1; - return WriteString((string)value, actualLength, offset, stateObj, canAccumulate: false); + return WriteString(GenericConverter.Convert(value), actualLength, offset, stateObj, canAccumulate: false); } } } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - WriteDecimal((Decimal)value, stateObj); + WriteDecimal(GenericConverter.Convert(value), stateObj); break; case TdsEnums.SQLDATETIMN: Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length"); - TdsDateTime dt = MetaType.FromDateTime((DateTime)value, (byte)type.FixedLength); + TdsDateTime dt = MetaType.FromDateTime(GenericConverter.Convert(value), (byte)type.FixedLength); if (type.FixedLength == 4) { - if (0 > dt.days || dt.days > UInt16.MaxValue) + if (0 > dt.days || dt.days > ushort.MaxValue) throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture)); WriteShort(dt.days, stateObj); @@ -12820,13 +12841,13 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLMONEYN: { - WriteCurrency((Decimal)value, type.FixedLength, stateObj); + WriteCurrency(GenericConverter.Convert(value), type.FixedLength, stateObj); break; } case TdsEnums.SQLDATE: { - WriteDate((DateTime)value, stateObj); + WriteDate(GenericConverter.Convert(value), stateObj); break; } @@ -12835,7 +12856,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { throw SQL.TimeScaleValueOutOfRange(scale); } - WriteTime((TimeSpan)value, scale, actualLength, stateObj); + WriteTime(GenericConverter.Convert(value), scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIME2: @@ -12843,11 +12864,11 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { throw SQL.TimeScaleValueOutOfRange(scale); } - WriteDateTime2((DateTime)value, scale, actualLength, stateObj); + WriteDateTime2(GenericConverter.Convert(value), scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: - WriteDateTimeOffset((DateTimeOffset)value, scale, actualLength, stateObj); + WriteDateTimeOffset(GenericConverter.Convert(value), scale, actualLength, stateObj); break; default: @@ -13092,7 +13113,7 @@ private byte[] SerializeUnencryptedValue(object value, MetaType type, byte scale // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) + private byte[] SerializeUnencryptedSqlValue(T value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) { Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) || (value is INullable && !((INullable)value).IsNull)), @@ -13108,11 +13129,13 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - return SerializeFloat(((SqlSingle)value).Value); + { + return SerializeFloat(GenericConverter.Convert(value).Value); + } else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - return SerializeDouble(((SqlDouble)value).Value); + return SerializeDouble(GenericConverter.Convert(value).Value); } case TdsEnums.SQLBIGBINARY: @@ -13123,19 +13146,20 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act if (value is SqlBinary) { - Buffer.BlockCopy(((SqlBinary)value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(GenericConverter.Convert(value).Value, offset, b, 0, actualLength); } else { Debug.Assert(value is SqlBytes); - Buffer.BlockCopy(((SqlBytes)value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(GenericConverter.Convert(value).Value, offset, b, 0, actualLength); } + return b; } case TdsEnums.SQLUNIQUEID: { - byte[] b = ((SqlGuid)value).ToByteArray(); + byte[] b = GenericConverter.Convert(value).ToByteArray(); Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); return b; @@ -13146,37 +13170,37 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); // We normalize to allow conversion across data types. BIT is serialized into a BIGINT. - return SerializeLong(((SqlBoolean)value).Value == true ? 1 : 0, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value == true ? 1 : 0, stateObj); } case TdsEnums.SQLINTN: // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT. if (type.FixedLength == 1) - return SerializeLong(((SqlByte)value).Value, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value, stateObj); if (type.FixedLength == 2) - return SerializeLong(((SqlInt16)value).Value, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value, stateObj); if (type.FixedLength == 4) - return SerializeLong(((SqlInt32)value).Value, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - return SerializeLong(((SqlInt64)value).Value, stateObj); + return SerializeLong(GenericConverter.Convert(value).Value, stateObj); } case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLBIGVARCHAR: case TdsEnums.SQLTEXT: - if (value is System.Data.SqlTypes.SqlChars) + if (value is SqlChars) { - String sch = new String(((System.Data.SqlTypes.SqlChars)value).Value); + String sch = new String(GenericConverter.Convert(value).Value); return SerializeEncodingChar(sch, actualLength, offset, _defaultEncoding); } else { Debug.Assert(value is SqlString); - return SerializeEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding); + return SerializeEncodingChar(GenericConverter.Convert(value).Value, actualLength, offset, _defaultEncoding); } @@ -13189,22 +13213,22 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act if (actualLength != 0) actualLength >>= 1; - if (value is System.Data.SqlTypes.SqlChars) + if (value is SqlChars) { - return SerializeCharArray(((System.Data.SqlTypes.SqlChars)value).Value, actualLength, offset); + return SerializeCharArray(GenericConverter.Convert(value).Value, actualLength, offset); } else { Debug.Assert(value is SqlString); - return SerializeString(((SqlString)value).Value, actualLength, offset); + return SerializeString(GenericConverter.Convert(value).Value, actualLength, offset); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - return SerializeSqlDecimal((SqlDecimal)value, stateObj); + return SerializeSqlDecimal(GenericConverter.Convert(value), stateObj); case TdsEnums.SQLDATETIMN: - SqlDateTime dt = (SqlDateTime)value; + SqlDateTime dt = GenericConverter.Convert(value); if (type.FixedLength == 4) { @@ -13250,7 +13274,7 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act case TdsEnums.SQLMONEYN: { - return SerializeSqlMoney((SqlMoney)value, type.FixedLength, stateObj); + return SerializeSqlMoney(GenericConverter.Convert(value), type.FixedLength, stateObj); } default: diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index e8f102ff75..d9618adf94 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -54,7 +54,7 @@ - + Common\System\Collections\DictionaryExtensions.cs @@ -275,6 +275,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index 0632b61a0a..00edf888cf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -7,6 +7,12 @@ using System.Data; using System.Linq; using System.Linq.Expressions; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Validators; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -57,34 +63,43 @@ BoolColumn BIT NOT NULL } } + private class RunOnceConfig : ManualConfig + { + public RunOnceConfig() + { + Add(Job.InProcess.WithLaunchCount(1).WithIterationCount(1).WithWarmupCount(0)); + Add(MemoryDiagnoser.Default); + + Add(JitOptimizationsValidator.DontFailOnError); + } + } + + #if DEBUG + //return; +#endif + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] public void Should_Not_Box() - { // in debug mode, the double boxing DOES occur, which causes this test to fail (does the JIT "do" less in debug?) -#if DEBUG - return; -#endif - // cannot figure out an easy way to get this to work on all platforms + { // in debug mode, the double boxing DOES occur as the JIT optimizes less code + + //cannot figure out an easy way to get this to work on all platforms - //var config = ManualConfig.Create(DefaultConfig.Instance) - // .WithOptions(ConfigOptions.DisableOptimizationsValidator) - // .AddJob(Job.InProcess.WithLaunchCount(1).WithInvocationCount(1).WithIterationCount(1).WithWarmupCount(0).WithStrategy(RunStrategy.ColdStart)) - // .AddDiagnoser(MemoryDiagnoser.Default) - //; + var config = new RunOnceConfig(); // cannot use fluent syntax to still support net461 - //var summary = BenchmarkRunner.Run(config); + var summary = BenchmarkRunner.Run(config); - //var numValueTypeColumns = 2; - //var totalBytesWhenBoxed = IntPtr.Size * _count * numValueTypeColumns; + var numValueTypeColumns = 2; + var totalBytesWhenBoxed = IntPtr.Size * _count * numValueTypeColumns; - //var report = summary.Reports.First(); + var report = summary.Reports.First(); - //Assert.Equal(1, report.AllMeasurements.Count); - //Assert.True(report.GcStats.BytesAllocatedPerOperation < totalBytesWhenBoxed); + Assert.Equal(1, report.AllMeasurements.Count); + Assert.True(report.GcStats.BytesAllocatedPerOperation < totalBytesWhenBoxed); } public class NoBoxingValueTypesBenchmark { - // [Benchmark] + [Benchmark] public void BulkCopy() { _reader.Close(); // this resets the reader diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 044d79f9d2..e9f4b9c0b9 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -12,7 +12,7 @@ - 2.1.0 + 2.1.0 4.3.1 4.3.0 @@ -26,6 +26,7 @@ + 0.11.3 4.7.0 2.1.0 4.7.0 From 486aa5d514baf5e5c1be32bfe450ef2ec5a957e5 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Thu, 24 Sep 2020 10:07:06 -0500 Subject: [PATCH 29/31] debug --- .../SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index 00edf888cf..67b5c7c485 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -74,14 +74,14 @@ public RunOnceConfig() } } - #if DEBUG - //return; -#endif + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] public void Should_Not_Box() - { // in debug mode, the double boxing DOES occur as the JIT optimizes less code - + { // in debug mode, the double boxing DOES occur as the JIT optimizes less code, which causes the test to fail +#if DEBUG + return; +#endif //cannot figure out an easy way to get this to work on all platforms var config = new RunOnceConfig(); // cannot use fluent syntax to still support net461 From 633696e2c43f384ff45294184d1bf6483b158c53 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Thu, 24 Sep 2020 10:30:12 -0500 Subject: [PATCH 30/31] removing generic converter tests --- .../FunctionalTests/GenericConverterTests.cs | 130 ------------------ .../Microsoft.Data.SqlClient.Tests.csproj | 1 - 2 files changed, 131 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs deleted file mode 100644 index 5f98415ce9..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/GenericConverterTests.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Data.SqlTypes; -using Xunit; - -namespace Microsoft.Data.SqlClient.Tests -{ - public class DecimalTheoryData: TheoryData - { - public DecimalTheoryData() - { - Add(-100); - Add(0); - Add(.75m); - Add(525); - } - } - public class GenericConverterTests - { - // commenting out until i receive feedback for how to implement tests w/o linking file - //[Theory] - //[ClassData(typeof(DecimalTheoryData))] - //public void ObjectToDecimal_ConversionSuccessful(decimal val) - //{ - // object objVal = val; - // decimal converted = GenericConverter.Convert(objVal); - // Assert.Equal(val, converted); - //} - - //[Fact] - //public void ObjectToString_ConversionSuccessful() - //{ - // string testVal = "abcd1234"; - // string converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToInt_ConversionSuccessful() - //{ - // int testVal = 123; - // int converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToDouble_ConversionSuccessful() - //{ - // double testVal = 123d; - // double converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToFloat_ConversionSuccessful() - //{ - // float testVal = 123f; - // float converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToGuid_ConversionSuccessful() - //{ - // Guid testVal = Guid.NewGuid(); - // Guid converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToDateTime_ConversionSuccessful() - //{ - // DateTime testVal = DateTime.Now; - // DateTime converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToBool_ConversionSuccessful() - //{ - // bool testVal = false; - // bool converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToShort_ConversionSuccessful() - //{ - // short testVal = 123; - // short converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToByte_ConversionSuccessful() - //{ - // byte testVal = new byte(); - // byte converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Fact] - //public void ObjectToChar_ConversionSuccessful() - //{ - // char testVal = 'a'; - // char converted = GenericConverter.Convert(testVal); - // Assert.Equal(testVal, converted); - //} - - //[Theory] - //[ClassData(typeof(DecimalTheoryData))] - //public void SelfConversionSuccessful(decimal val) - //{ - // decimal converted = GenericConverter.Convert(val); - // Assert.Equal(val, converted); - //} - - //[Theory] - //[ClassData(typeof(DecimalTheoryData))] - //public void AssignableConversionSuccessful(decimal val) - //{ - // SqlDecimal sqlVal = new SqlDecimal(val); - // decimal converted = GenericConverter.Convert(sqlVal); - // Assert.Equal(val, converted); - //} - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index 2f99e7420a..b6b55561ea 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -50,7 +50,6 @@ - From 3cca4ca2ca7fd1e40a3ba8191da2418ba82e1736 Mon Sep 17 00:00:00 2001 From: Carl Meyertons Date: Mon, 19 Apr 2021 12:58:02 -0500 Subject: [PATCH 31/31] asserts + test --- .../netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs | 2 +- .../netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs | 2 +- .../ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index 61fd05229b..2a2fb2a1a9 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1116,7 +1116,7 @@ value is DbDataReader || } Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); - Debug.Assert(value.GetType() == currentType ^ typeChanged, "Incorrect value for typeChanged"); + Debug.Assert(objValue == null || objValue.GetType() == currentType ^ typeChanged, "Incorrect value for typeChanged"); return typeChanged; } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs index 6674316123..78ca6f2363 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1206,7 +1206,7 @@ value is DbDataReader || } Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); - Debug.Assert(value.GetType() == currentType ^ typeChanged, "Incorrect value for typeChanged"); + Debug.Assert(objValue == null || objValue.GetType() == currentType ^ typeChanged, "Incorrect value for typeChanged"); return typeChanged; } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs index 67b5c7c485..8b1f619f15 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/NoBoxingValueTypes.cs @@ -81,7 +81,7 @@ public void Should_Not_Box() { // in debug mode, the double boxing DOES occur as the JIT optimizes less code, which causes the test to fail #if DEBUG return; -#endif +#else //cannot figure out an easy way to get this to work on all platforms var config = new RunOnceConfig(); // cannot use fluent syntax to still support net461 @@ -95,6 +95,7 @@ public void Should_Not_Box() Assert.Equal(1, report.AllMeasurements.Count); Assert.True(report.GcStats.BytesAllocatedPerOperation < totalBytesWhenBoxed); +#endif } public class NoBoxingValueTypesBenchmark