From 6e7e44b7bad8a7e77fa261959760346120b195d0 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 12 Oct 2018 23:40:51 +0100 Subject: [PATCH 01/14] rpc rework --- .../src/System/Data/SqlClient/SqlCommand.cs | 134 +- .../src/System/Data/SqlClient/TdsParser.cs | 1083 +++++++++++------ .../Data/SqlClient/TdsParserHelperClasses.cs | 41 +- 3 files changed, 832 insertions(+), 426 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs index 53abc5286adb..aeb7b9e0cb6c 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -3175,10 +3175,9 @@ private SqlParameter GetParameterForOutputValueExtraction(SqlParameterCollection return null; } - private void GetRPCObject(int paramCount, ref _SqlRPC rpc) + private void GetRPCObject(int systemParamCount, int userParamCount, ref _SqlRPC rpc) { // Designed to minimize necessary allocations - int ii; if (rpc == null) { if (_rpcArrayOf1 == null) @@ -3189,42 +3188,42 @@ private void GetRPCObject(int paramCount, ref _SqlRPC rpc) rpc = _rpcArrayOf1[0]; } - rpc.ProcID = 0; - rpc.rpcName = null; + //rpc.ProcID = 0; + //rpc.rpcName = null; rpc.options = 0; + rpc.systemParamCount = systemParamCount; - + int currentCount = rpc.systemParams?.Length ?? 0; // Make sure there is enough space in the parameters and paramoptions arrays - if (rpc.parameters == null || rpc.parameters.Length < paramCount) + if (currentCount < systemParamCount) { - rpc.parameters = new SqlParameter[paramCount]; - } - else if (rpc.parameters.Length > paramCount) - { - rpc.parameters[paramCount] = null; // Terminator + Array.Resize(ref rpc.systemParams, systemParamCount); + Array.Resize(ref rpc.systemParamOptions, systemParamCount); + for (int index = currentCount; index < systemParamCount; index++) + { + rpc.systemParams[index] = new SqlParameter(); + } } - if (rpc.paramoptions == null || (rpc.paramoptions.Length < paramCount)) + for (int ii = 0; ii < systemParamCount; ii++) { - rpc.paramoptions = new byte[paramCount]; + rpc.systemParamOptions[ii] = 0; } - else + if ((rpc.userParamMap?.Length ?? 0) < userParamCount) { - for (ii = 0; ii < paramCount; ii++) - rpc.paramoptions[ii] = 0; + Array.Resize(ref rpc.userParamMap, userParamCount); } } private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlParameterCollection parameters) { - int ii; int paramCount = GetParameterCount(parameters); - int j = startCount; - TdsParser parser = _activeConnection.Parser; + //int j = startCount; + int userParamCount = 0; - for (ii = 0; ii < paramCount; ii++) + for (int index = 0; index < paramCount; index++) { - SqlParameter parameter = parameters[ii]; - parameter.Validate(ii, CommandType.StoredProcedure == CommandType); + SqlParameter parameter = parameters[index]; + parameter.Validate(index, CommandType.StoredProcedure == CommandType); // func will change type to that with a 4 byte length if the type has a two // byte length and a parameter length > than that expressible in 2 bytes @@ -3235,12 +3234,12 @@ private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlP if (ShouldSendParameter(parameter)) { - rpc.parameters[j] = parameter; + byte options = 0; // set output bit if (parameter.Direction == ParameterDirection.InputOutput || parameter.Direction == ParameterDirection.Output) - rpc.paramoptions[j] = TdsEnums.RPC_PARAM_BYREF; + options = TdsEnums.RPC_PARAM_BYREF; // set default value bit if (parameter.Direction != ParameterDirection.Output) @@ -3252,50 +3251,58 @@ private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlP // TVPs use DEFAULT and do not allow NULL, even for schema only. if (null == parameter.Value && (!inSchema || SqlDbType.Structured == parameter.SqlDbType)) { - rpc.paramoptions[j] |= TdsEnums.RPC_PARAM_DEFAULT; + options |= TdsEnums.RPC_PARAM_DEFAULT; } } + rpc.userParamMap[userParamCount] = ((((long)options) << 32) | (long)index); + userParamCount += 1; // Must set parameter option bit for LOB_COOKIE if unfilled LazyMat blob - j++; + } } + rpc.userParamCount = userParamCount; + rpc.userParams = parameters; } private _SqlRPC BuildPrepExec(CommandBehavior behavior) { Debug.Assert(System.Data.CommandType.Text == this.CommandType, "invalid use of sp_prepexec for stored proc invocation!"); SqlParameter sqlParam; - int j = 3; + const int systemParameterCount = 3; - int count = CountSendableParameters(_parameters); + int userParameterCount = CountSendableParameters(_parameters); _SqlRPC rpc = null; - GetRPCObject(count + j, ref rpc); + GetRPCObject(systemParameterCount,userParameterCount, ref rpc); rpc.ProcID = TdsEnums.RPC_PROCID_PREPEXEC; rpc.rpcName = TdsEnums.SP_PREPEXEC; //@handle - sqlParam = new SqlParameter(null, SqlDbType.Int); - sqlParam.Direction = ParameterDirection.InputOutput; + sqlParam = rpc.systemParams[0]; + sqlParam.SqlDbType = SqlDbType.Int; sqlParam.Value = _prepareHandle; - rpc.parameters[0] = sqlParam; - rpc.paramoptions[0] = TdsEnums.RPC_PARAM_BYREF; + sqlParam.Size = 4; + sqlParam.Direction = ParameterDirection.InputOutput; + rpc.systemParamOptions[0] = TdsEnums.RPC_PARAM_BYREF; //@batch_params string paramList = BuildParamList(_stateObj.Parser, _parameters); - sqlParam = new SqlParameter(null, ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, paramList.Length); + sqlParam = rpc.systemParams[1]; + sqlParam.SqlDbType = ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText; + sqlParam.Size = paramList.Length; sqlParam.Value = paramList; - rpc.parameters[1] = sqlParam; //@batch_text string text = GetCommandText(behavior); - sqlParam = new SqlParameter(null, ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, text.Length); + sqlParam = rpc.systemParams[2]; + sqlParam.SqlDbType = ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText; + sqlParam.Size = text.Length; sqlParam.Value = text; - rpc.parameters[2] = sqlParam; - SetUpRPCParameters(rpc, j, false, _parameters); + SetUpRPCParameters(rpc, systemParameterCount, false, _parameters); + return rpc; } @@ -3350,9 +3357,10 @@ private int GetParameterCount(SqlParameterCollection parameters) private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _SqlRPC rpc) { Debug.Assert(this.CommandType == System.Data.CommandType.StoredProcedure, "Command must be a stored proc to execute an RPC"); - int count = CountSendableParameters(parameters); - GetRPCObject(count, ref rpc); + int userParameterCount = CountSendableParameters(parameters); + GetRPCObject(0,userParameterCount, ref rpc); + rpc.ProcID = 0; rpc.rpcName = this.CommandText; // just get the raw command text SetUpRPCParameters(rpc, 0, inSchema, parameters); @@ -3372,24 +3380,23 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql private _SqlRPC BuildExecute(bool inSchema) { Debug.Assert(_prepareHandle != -1, "Invalid call to sp_execute without a valid handle!"); - int j = 1; + const int systemParameterCount = 1; - int count = CountSendableParameters(_parameters); + int userParameterCount = CountSendableParameters(_parameters); _SqlRPC rpc = null; - GetRPCObject(count + j, ref rpc); - - SqlParameter sqlParam; + GetRPCObject(systemParameterCount, userParameterCount, ref rpc); rpc.ProcID = TdsEnums.RPC_PROCID_EXECUTE; rpc.rpcName = TdsEnums.SP_EXECUTE; //@handle - sqlParam = new SqlParameter(null, SqlDbType.Int); + SqlParameter sqlParam = rpc.systemParams[0]; + sqlParam.SqlDbType = SqlDbType.Int; sqlParam.Value = _prepareHandle; - rpc.parameters[0] = sqlParam; + sqlParam.Direction = ParameterDirection.Input; - SetUpRPCParameters(rpc, j, inSchema, _parameters); + SetUpRPCParameters(rpc, systemParameterCount, inSchema, _parameters); return rpc; } @@ -3403,20 +3410,21 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa Debug.Assert(_prepareHandle == -1, "This command has an existing handle, use sp_execute!"); Debug.Assert(CommandType.Text == this.CommandType, "invalid use of sp_executesql for stored proc invocation!"); - int j; + int systemParamCount; SqlParameter sqlParam; - int cParams = CountSendableParameters(parameters); - if (cParams > 0) + int userParamCount = CountSendableParameters(parameters); + if (userParamCount > 0) { - j = 2; + systemParamCount = 2; } else { - j = 1; + systemParamCount = 1; } - GetRPCObject(cParams + j, ref rpc); + GetRPCObject(systemParamCount, userParamCount, ref rpc); + rpc.ProcID = TdsEnums.RPC_PROCID_EXECUTESQL; rpc.rpcName = TdsEnums.SP_EXECUTESQL; @@ -3425,19 +3433,22 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa { commandText = GetCommandText(behavior); } - sqlParam = new SqlParameter(null, ((commandText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, commandText.Length); + sqlParam = rpc.systemParams[0]; + sqlParam.SqlDbType = ((commandText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText; + sqlParam.Size = commandText.Length; sqlParam.Value = commandText; - rpc.parameters[0] = sqlParam; + sqlParam.Direction = ParameterDirection.Input; - if (cParams > 0) + if (userParamCount > 0) { string paramList = BuildParamList(_stateObj.Parser, BatchRPCMode ? parameters : _parameters); - sqlParam = new SqlParameter(null, ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, paramList.Length); + sqlParam = rpc.systemParams[1]; + sqlParam.SqlDbType = ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText; + sqlParam.Size = paramList.Length; sqlParam.Value = paramList; - rpc.parameters[1] = sqlParam; - + bool inSchema = (0 != (behavior & CommandBehavior.SchemaOnly)); - SetUpRPCParameters(rpc, j, inSchema, parameters); + SetUpRPCParameters(rpc, systemParamCount, inSchema, parameters); } } @@ -3449,6 +3460,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete int count = 0; + count = parameters.Count; for (int i = 0; i < count; i++) { diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index e7e35d351c0e..e67d10ff6798 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -7105,12 +7105,16 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN } // Stream out parameters - SqlParameter[] parameters = rpcext.parameters; + //SqlParameter[] parameters = rpcext.parameters; + int parametersLength = rpcext.userParamCount + rpcext.systemParamCount; - for (int i = (ii == startRpc) ? startParam : 0; i < parameters.Length; i++) + for (int i = (ii == startRpc) ? startParam : 0; i < parametersLength; i++) { - // parameters can be unnamed - SqlParameter param = parameters[i]; + byte options = 0; + SqlParameter param = rpcext.GetParameterByIndex(i, out options); + + + // Since we are reusing the parameters array, we cannot rely on length to indicate no of parameters. if (param == null) break; // End of parameters for this execute @@ -7118,12 +7122,14 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN // Validate parameters are not variable length without size and with null value. param.Validate(i, isCommandProc); + + // type (parameter record stores the MetaType class which is a helper that encapsulates all the type information we need here) MetaType mt = param.InternalMetaType; if (mt.IsNewKatmaiType) { - WriteSmiParameter(param, i, 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_DEFAULT), stateObj); + WriteSmiParameter(param, i, 0 != (options & TdsEnums.RPC_PARAM_DEFAULT), stateObj); continue; } @@ -7132,352 +7138,356 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN { throw ADP.VersionDoesNotSupportDataType(mt.TypeName); } - object value = null; - bool isNull = true; - bool isSqlVal = false; - bool isDataFeed = false; - // if we have an output param, set the value to null so we do not send it across to the server - if (param.Direction == ParameterDirection.Output) - { - isSqlVal = param.ParameterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the parameter we will no longer be able to distinguish what type were seeing. - param.Value = null; - param.ParameterIsSqlType = isSqlVal; - } - else - { - value = param.GetCoercedValue(); - isNull = param.IsNull; - if (!isNull) - { - isSqlVal = param.CoercedValueIsSqlType; - isDataFeed = param.CoercedValueIsDataFeed; - } - } - - WriteParameterName(param.ParameterNameFixed, stateObj); - - // Write parameter status - stateObj.WriteByte(rpcext.paramoptions[i]); - - // MaxLen field is only written out for non-fixed length data types - // use the greater of the two sizes for maxLen - int actualSize; - int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize(); - - // for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation - if (mt.TDSType != TdsEnums.SQLUDT) - // getting the actualSize is expensive, cache here and use below - actualSize = param.GetActualSize(); - else - actualSize = 0; //get this later - - byte precision = 0; - byte scale = 0; - - // scale and precision are only relevant for numeric and decimal types - // adjust the actual value scale and precision to match the user specified - if (mt.SqlDbType == SqlDbType.Decimal) - { - precision = param.GetActualPrecision(); - scale = param.GetActualScale(); - - if (precision > TdsEnums.MAX_NUMERIC_PRECISION) - { - throw SQL.PrecisionValueOutOfRange(precision); - } - - // Make sure the value matches the scale the user enters - if (!isNull) - { - if (isSqlVal) - { - value = AdjustSqlDecimalScale((SqlDecimal)value, scale); - - // If Precision is specified, verify value precision vs param precision - if (precision != 0) - { - if (precision < ((SqlDecimal)value).Precision) - { - throw ADP.ParameterValueOutOfRange((SqlDecimal)value); - } - } - } - else - { - value = AdjustDecimalScale((decimal)value, scale); - - SqlDecimal sqlValue = new SqlDecimal((decimal)value); - - // If Precision is specified, verify value precision vs param precision - if (precision != 0) - { - if (precision < sqlValue.Precision) - { - throw ADP.ParameterValueOutOfRange((decimal)value); - } - } - } - } - } - - // fixup the types by using the NullableType property of the MetaType class - // - // following rules should be followed based on feedback from the M-SQL team - // 1) always use the BIG* types (ex: instead of SQLCHAR use SQLBIGCHAR) - // 2) always use nullable types (ex: instead of SQLINT use SQLINTN) - // 3) DECIMALN should always be sent as NUMERICN - // - stateObj.WriteByte(mt.NullableType); - - // handle variants here: the SQLVariant writing routine will write the maxlen and actual len columns - if (mt.TDSType == TdsEnums.SQLVARIANT) - { - // devnote: Do we ever hit this codepath? Yes, when a null value is being written out via a sql variant - // param.GetActualSize is not used - WriteSqlVariantValue(isSqlVal ? MetaType.GetComValueFromSqlVariant(value) : value, param.GetActualSize(), param.Offset, stateObj); - continue; - } - - int codePageByteSize = 0; - int maxsize = 0; - - if (mt.IsAnsiType) - { - // Avoid the following code block if ANSI but unfilled LazyMat blob - if ((!isNull) && (!isDataFeed)) - { - string s; - - if (isSqlVal) - { - if (value is SqlString) - { - s = ((SqlString)value).Value; - } - else - { - Debug.Assert(value is SqlChars, "Unknown value for Ansi datatype"); - s = new string(((SqlChars)value).Value); - } - } - else - { - s = (string)value; - } - - codePageByteSize = GetEncodingCharLength(s, actualSize, param.Offset, _defaultEncoding); - } - - if (mt.IsPlp) - { - WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); - } - else - { - maxsize = (size > codePageByteSize) ? size : codePageByteSize; - if (maxsize == 0) - { - // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types - if (mt.IsNCharType) - maxsize = 2; - else - maxsize = 1; - } - - WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); - } - } - else - { - // If type timestamp - treat as fixed type and always send over timestamp length, which is 8. - // For fixed types, we either send null or fixed length for type length. We want to match that - // behavior for timestamps. However, in the case of null, we still must send 8 because if we - // send null we will not receive a output val. You can send null for fixed types and still - // receive a output value, but not for variable types. So, always send 8 for timestamp because - // while the user sees it as a fixed type, we are actually representing it as a bigbinary which - // is variable. - if (mt.SqlDbType == SqlDbType.Timestamp) - { - WriteParameterVarLen(mt, TdsEnums.TEXT_TIME_STAMP_LEN, false, stateObj); - } - else if (mt.SqlDbType == SqlDbType.Udt) - { - byte[] udtVal = null; - Format format = Format.Native; - - Debug.Assert(_isYukon, "Invalid DataType UDT for non-Yukon or later server!"); - - if (!isNull) - { - udtVal = _connHandler.Connection.GetBytes(value, out format, out maxsize); - - Debug.Assert(null != udtVal, "GetBytes returned null instance. Make sure that it always returns non-null value"); - size = udtVal.Length; - - //it may be legitimate, but we dont support it yet - if (size < 0 || (size >= ushort.MaxValue && maxsize != -1)) - throw new IndexOutOfRangeException(); - } - - //if this is NULL value, write special null value - byte[] lenBytes = BitConverter.GetBytes((long)size); - - if (string.IsNullOrEmpty(param.UdtTypeName)) - throw SQL.MustSetUdtTypeNameForUdtParams(); - - // Split the input name. TypeName is returned as single 3 part name during DeriveParameters. - // NOTE: ParseUdtTypeName throws if format is incorrect - string[] names = SqlParameter.ParseTypeName(param.UdtTypeName, true /* is UdtTypeName */); - if (!string.IsNullOrEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) - { - throw ADP.ArgumentOutOfRange(nameof(names)); - } - if (!string.IsNullOrEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) - { - throw ADP.ArgumentOutOfRange(nameof(names)); - } - if (TdsEnums.MAX_SERVERNAME < names[2].Length) - { - throw ADP.ArgumentOutOfRange(nameof(names)); - } - - WriteUDTMetaData(value, names[0], names[1], names[2], stateObj); - - if (!isNull) - { - WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length - if (udtVal.Length > 0) - { // Only write chunk length if its value is greater than 0 - WriteInt(udtVal.Length, stateObj); // Chunk length - stateObj.WriteByteArray(udtVal, udtVal.Length, 0); // Value - } - WriteInt(0, stateObj); // Terminator - } - else - { - WriteUnsignedLong(TdsEnums.SQL_PLP_NULL, stateObj); // PLP Null. - } - continue; // End of UDT - continue to next parameter. - } - else if (mt.IsPlp) - { - if (mt.SqlDbType != SqlDbType.Xml) - WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); - } - else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) - { // Time, Date, DateTime2, DateTimeoffset do not have the size written out - maxsize = (size > actualSize) ? size : actualSize; - if (maxsize == 0 && _isYukon) - { - // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) - if (mt.IsNCharType) - maxsize = 2; - else - maxsize = 1; - } - - WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); - } - } - - // scale and precision are only relevant for numeric and decimal types - if (mt.SqlDbType == SqlDbType.Decimal) - { - if (0 == precision) - { - stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION); - } - else - { - stateObj.WriteByte(precision); - } - - stateObj.WriteByte(scale); - } - else if (mt.IsVarTime) - { - stateObj.WriteByte(param.GetActualScale()); - } - - // write out collation or xml metadata - - if (_isYukon && (mt.SqlDbType == SqlDbType.Xml)) - { - if (((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) || - ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) || - ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty))) - { - stateObj.WriteByte(1); //Schema present flag - - if ((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) - { - tempLen = (param.XmlSchemaCollectionDatabase).Length; - stateObj.WriteByte((byte)(tempLen)); - WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj); - } - else - { - stateObj.WriteByte(0); // No dbname - } - - if ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) - { - tempLen = (param.XmlSchemaCollectionOwningSchema).Length; - stateObj.WriteByte((byte)(tempLen)); - WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj); - } - else - { - stateObj.WriteByte(0); // no xml schema name - } - - if ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty)) - { - tempLen = (param.XmlSchemaCollectionName).Length; - WriteShort((short)(tempLen), stateObj); - WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj); - } - else - { - WriteShort(0, stateObj); // No xml schema collection name - } - } - else - { - stateObj.WriteByte(0); // No schema - } - } - else if (mt.IsCharType) - { - // if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter - SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation; - Debug.Assert(_defaultCollation != null, "_defaultCollation is null!"); - - WriteUnsignedInt(outCollation.info, stateObj); - stateObj.WriteByte(outCollation.sortId); - } - - if (0 == codePageByteSize) - WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed); - else - WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed); - - Task writeParamTask = null; - // write the value now - if (!isNull) - { - if (isSqlVal) - { - 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(value, mt, param.GetActualScale(), actualSize, codePageByteSize, param.Offset, stateObj, param.Size, isDataFeed); - } - } + Task writeParamTask = TDSExecuteRPCAddParameter(stateObj,param,mt, options); + + #region moved to function + //////////object value = null; + //////////bool isNull = true; + //////////bool isSqlVal = false; + //////////bool isDataFeed = false; + //////////// if we have an output param, set the value to null so we do not send it across to the server + //////////if (param.Direction == ParameterDirection.Output) + //////////{ + ////////// isSqlVal = param.ParameterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the parameter we will no longer be able to distinguish what type were seeing. + ////////// param.Value = null; + ////////// param.ParameterIsSqlType = isSqlVal; + //////////} + //////////else + //////////{ + ////////// value = param.GetCoercedValue(); + ////////// isNull = param.IsNull; + ////////// if (!isNull) + ////////// { + ////////// isSqlVal = param.CoercedValueIsSqlType; + ////////// isDataFeed = param.CoercedValueIsDataFeed; + ////////// } + //////////} + + //////////WriteParameterName(param.ParameterNameFixed, stateObj); + + //////////// Write parameter status + //////////stateObj.WriteByte(rpcext.paramoptions[i]); + + //////////// MaxLen field is only written out for non-fixed length data types + //////////// use the greater of the two sizes for maxLen + //////////int actualSize; + //////////int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize(); + + //////////// for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation + //////////if (mt.TDSType != TdsEnums.SQLUDT) + ////////// // getting the actualSize is expensive, cache here and use below + ////////// actualSize = param.GetActualSize(); + //////////else + ////////// actualSize = 0; //get this later + + //////////byte precision = 0; + //////////byte scale = 0; + + //////////// scale and precision are only relevant for numeric and decimal types + //////////// adjust the actual value scale and precision to match the user specified + //////////if (mt.SqlDbType == SqlDbType.Decimal) + //////////{ + ////////// precision = param.GetActualPrecision(); + ////////// scale = param.GetActualScale(); + + ////////// if (precision > TdsEnums.MAX_NUMERIC_PRECISION) + ////////// { + ////////// throw SQL.PrecisionValueOutOfRange(precision); + ////////// } + + ////////// // Make sure the value matches the scale the user enters + ////////// if (!isNull) + ////////// { + ////////// if (isSqlVal) + ////////// { + ////////// value = AdjustSqlDecimalScale((SqlDecimal)value, scale); + + ////////// // If Precision is specified, verify value precision vs param precision + ////////// if (precision != 0) + ////////// { + ////////// if (precision < ((SqlDecimal)value).Precision) + ////////// { + ////////// throw ADP.ParameterValueOutOfRange((SqlDecimal)value); + ////////// } + ////////// } + ////////// } + ////////// else + ////////// { + ////////// value = AdjustDecimalScale((decimal)value, scale); + + ////////// SqlDecimal sqlValue = new SqlDecimal((decimal)value); + + ////////// // If Precision is specified, verify value precision vs param precision + ////////// if (precision != 0) + ////////// { + ////////// if (precision < sqlValue.Precision) + ////////// { + ////////// throw ADP.ParameterValueOutOfRange((decimal)value); + ////////// } + ////////// } + ////////// } + ////////// } + //////////} + + //////////// fixup the types by using the NullableType property of the MetaType class + //////////// + //////////// following rules should be followed based on feedback from the M-SQL team + //////////// 1) always use the BIG* types (ex: instead of SQLCHAR use SQLBIGCHAR) + //////////// 2) always use nullable types (ex: instead of SQLINT use SQLINTN) + //////////// 3) DECIMALN should always be sent as NUMERICN + //////////// + //////////stateObj.WriteByte(mt.NullableType); + + //////////// handle variants here: the SQLVariant writing routine will write the maxlen and actual len columns + //////////if (mt.TDSType == TdsEnums.SQLVARIANT) + //////////{ + ////////// // devnote: Do we ever hit this codepath? Yes, when a null value is being written out via a sql variant + ////////// // param.GetActualSize is not used + ////////// WriteSqlVariantValue(isSqlVal ? MetaType.GetComValueFromSqlVariant(value) : value, param.GetActualSize(), param.Offset, stateObj); + ////////// continue; + //////////} + + //////////int codePageByteSize = 0; + //////////int maxsize = 0; + + //////////if (mt.IsAnsiType) + //////////{ + ////////// // Avoid the following code block if ANSI but unfilled LazyMat blob + ////////// if ((!isNull) && (!isDataFeed)) + ////////// { + ////////// string s; + + ////////// if (isSqlVal) + ////////// { + ////////// if (value is SqlString) + ////////// { + ////////// s = ((SqlString)value).Value; + ////////// } + ////////// else + ////////// { + ////////// Debug.Assert(value is SqlChars, "Unknown value for Ansi datatype"); + ////////// s = new string(((SqlChars)value).Value); + ////////// } + ////////// } + ////////// else + ////////// { + ////////// s = (string)value; + ////////// } + + ////////// codePageByteSize = GetEncodingCharLength(s, actualSize, param.Offset, _defaultEncoding); + ////////// } + + ////////// if (mt.IsPlp) + ////////// { + ////////// WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); + ////////// } + ////////// else + ////////// { + ////////// maxsize = (size > codePageByteSize) ? size : codePageByteSize; + ////////// if (maxsize == 0) + ////////// { + ////////// // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types + ////////// if (mt.IsNCharType) + ////////// maxsize = 2; + ////////// else + ////////// maxsize = 1; + ////////// } + + ////////// WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); + ////////// } + //////////} + //////////else + //////////{ + ////////// // If type timestamp - treat as fixed type and always send over timestamp length, which is 8. + ////////// // For fixed types, we either send null or fixed length for type length. We want to match that + ////////// // behavior for timestamps. However, in the case of null, we still must send 8 because if we + ////////// // send null we will not receive a output val. You can send null for fixed types and still + ////////// // receive a output value, but not for variable types. So, always send 8 for timestamp because + ////////// // while the user sees it as a fixed type, we are actually representing it as a bigbinary which + ////////// // is variable. + ////////// if (mt.SqlDbType == SqlDbType.Timestamp) + ////////// { + ////////// WriteParameterVarLen(mt, TdsEnums.TEXT_TIME_STAMP_LEN, false, stateObj); + ////////// } + ////////// else if (mt.SqlDbType == SqlDbType.Udt) + ////////// { + ////////// byte[] udtVal = null; + ////////// Format format = Format.Native; + + ////////// Debug.Assert(_isYukon, "Invalid DataType UDT for non-Yukon or later server!"); + + ////////// if (!isNull) + ////////// { + ////////// udtVal = _connHandler.Connection.GetBytes(value, out format, out maxsize); + + ////////// Debug.Assert(null != udtVal, "GetBytes returned null instance. Make sure that it always returns non-null value"); + ////////// size = udtVal.Length; + + ////////// //it may be legitimate, but we dont support it yet + ////////// if (size < 0 || (size >= ushort.MaxValue && maxsize != -1)) + ////////// throw new IndexOutOfRangeException(); + ////////// } + + ////////// //if this is NULL value, write special null value + ////////// byte[] lenBytes = BitConverter.GetBytes((long)size); + + ////////// if (string.IsNullOrEmpty(param.UdtTypeName)) + ////////// throw SQL.MustSetUdtTypeNameForUdtParams(); + + ////////// // Split the input name. TypeName is returned as single 3 part name during DeriveParameters. + ////////// // NOTE: ParseUdtTypeName throws if format is incorrect + ////////// string[] names = SqlParameter.ParseTypeName(param.UdtTypeName, true /* is UdtTypeName */); + ////////// if (!string.IsNullOrEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) + ////////// { + ////////// throw ADP.ArgumentOutOfRange(nameof(names)); + ////////// } + ////////// if (!string.IsNullOrEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) + ////////// { + ////////// throw ADP.ArgumentOutOfRange(nameof(names)); + ////////// } + ////////// if (TdsEnums.MAX_SERVERNAME < names[2].Length) + ////////// { + ////////// throw ADP.ArgumentOutOfRange(nameof(names)); + ////////// } + + ////////// WriteUDTMetaData(value, names[0], names[1], names[2], stateObj); + + ////////// if (!isNull) + ////////// { + ////////// WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length + ////////// if (udtVal.Length > 0) + ////////// { // Only write chunk length if its value is greater than 0 + ////////// WriteInt(udtVal.Length, stateObj); // Chunk length + ////////// stateObj.WriteByteArray(udtVal, udtVal.Length, 0); // Value + ////////// } + ////////// WriteInt(0, stateObj); // Terminator + ////////// } + ////////// else + ////////// { + ////////// WriteUnsignedLong(TdsEnums.SQL_PLP_NULL, stateObj); // PLP Null. + ////////// } + ////////// continue; // End of UDT - continue to next parameter. + ////////// } + ////////// else if (mt.IsPlp) + ////////// { + ////////// if (mt.SqlDbType != SqlDbType.Xml) + ////////// WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); + ////////// } + ////////// else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) + ////////// { // Time, Date, DateTime2, DateTimeoffset do not have the size written out + ////////// maxsize = (size > actualSize) ? size : actualSize; + ////////// if (maxsize == 0 && _isYukon) + ////////// { + ////////// // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) + ////////// if (mt.IsNCharType) + ////////// maxsize = 2; + ////////// else + ////////// maxsize = 1; + ////////// } + + ////////// WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); + ////////// } + //////////} + + //////////// scale and precision are only relevant for numeric and decimal types + //////////if (mt.SqlDbType == SqlDbType.Decimal) + //////////{ + ////////// if (0 == precision) + ////////// { + ////////// stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION); + ////////// } + ////////// else + ////////// { + ////////// stateObj.WriteByte(precision); + ////////// } + + ////////// stateObj.WriteByte(scale); + //////////} + //////////else if (mt.IsVarTime) + //////////{ + ////////// stateObj.WriteByte(param.GetActualScale()); + //////////} + + //////////// write out collation or xml metadata + + //////////if (_isYukon && (mt.SqlDbType == SqlDbType.Xml)) + //////////{ + ////////// if (((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) || + ////////// ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) || + ////////// ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty))) + ////////// { + ////////// stateObj.WriteByte(1); //Schema present flag + + ////////// if ((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) + ////////// { + ////////// tempLen = (param.XmlSchemaCollectionDatabase).Length; + ////////// stateObj.WriteByte((byte)(tempLen)); + ////////// WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj); + ////////// } + ////////// else + ////////// { + ////////// stateObj.WriteByte(0); // No dbname + ////////// } + + ////////// if ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) + ////////// { + ////////// tempLen = (param.XmlSchemaCollectionOwningSchema).Length; + ////////// stateObj.WriteByte((byte)(tempLen)); + ////////// WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj); + ////////// } + ////////// else + ////////// { + ////////// stateObj.WriteByte(0); // no xml schema name + ////////// } + + ////////// if ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty)) + ////////// { + ////////// tempLen = (param.XmlSchemaCollectionName).Length; + ////////// WriteShort((short)(tempLen), stateObj); + ////////// WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj); + ////////// } + ////////// else + ////////// { + ////////// WriteShort(0, stateObj); // No xml schema collection name + ////////// } + ////////// } + ////////// else + ////////// { + ////////// stateObj.WriteByte(0); // No schema + ////////// } + //////////} + //////////else if (mt.IsCharType) + //////////{ + ////////// // if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter + ////////// SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation; + ////////// Debug.Assert(_defaultCollation != null, "_defaultCollation is null!"); + + ////////// WriteUnsignedInt(outCollation.info, stateObj); + ////////// stateObj.WriteByte(outCollation.sortId); + //////////} + + //////////if (0 == codePageByteSize) + ////////// WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed); + //////////else + ////////// WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed); + + //////////Task writeParamTask = null; + //////////// write the value now + //////////if (!isNull) + //////////{ + ////////// if (isSqlVal) + ////////// { + ////////// 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(value, mt, param.GetActualScale(), actualSize, codePageByteSize, param.Offset, stateObj, param.Size, isDataFeed); + ////////// } + //////////} + #endregion if (!sync) { if (writeParamTask == null) @@ -7494,11 +7504,7 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN task = completion.Task; } - AsyncHelper.ContinueTask(writeParamTask, completion, - () => TdsExecuteRPC(rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion, - startRpc: ii, startParam: i + 1), - connectionToDoom: _connHandler, - onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj)); + TDSExecuteRPCParameterSetupWriteCompletion(rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion, ii, i, writeParamTask); // Take care of releasing the locks if (releaseConnectionLock) @@ -7547,8 +7553,7 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN task = completion.Task; } - bool taskReleaseConnectionLock = releaseConnectionLock; - execFlushTask.ContinueWith(tsk => ExecuteFlushTaskCallback(tsk, stateObj, completion, taskReleaseConnectionLock), TaskScheduler.Default); + TDSExecuteRPCParameterSetupFlushCompletion(stateObj, completion, execFlushTask, releaseConnectionLock); // ExecuteFlushTaskCallback will take care of the locks for us releaseConnectionLock = false; @@ -7597,6 +7602,370 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN } } + // 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); + } + + // This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter + private void TDSExecuteRPCParameterSetupWriteCompletion(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync, TaskCompletionSource completion, int ii, int i, Task writeParamTask) + { + AsyncHelper.ContinueTask(writeParamTask, completion, + () => TdsExecuteRPC(rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion, + startRpc: ii, startParam: i + 1), + connectionToDoom: _connHandler, + onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj)); + } + + private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParameter param,MetaType mt,byte options) + { + int tempLen; + object value = null; + bool isNull = true; + bool isSqlVal = false; + bool isDataFeed = false; + // if we have an output param, set the value to null so we do not send it across to the server + if (param.Direction == ParameterDirection.Output) + { + isSqlVal = param.ParameterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the parameter we will no longer be able to distinguish what type were seeing. + param.Value = null; + param.ParameterIsSqlType = isSqlVal; + } + else + { + value = param.GetCoercedValue(); + isNull = param.IsNull; + if (!isNull) + { + isSqlVal = param.CoercedValueIsSqlType; + isDataFeed = param.CoercedValueIsDataFeed; + } + } + + WriteParameterName(param.ParameterNameFixed, stateObj); + + // Write parameter status + stateObj.WriteByte(options); + + // MaxLen field is only written out for non-fixed length data types + // use the greater of the two sizes for maxLen + int actualSize; + int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize(); + + // for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation + if (mt.TDSType != TdsEnums.SQLUDT) + // getting the actualSize is expensive, cache here and use below + actualSize = param.GetActualSize(); + else + actualSize = 0; //get this later + + byte precision = 0; + byte scale = 0; + + // scale and precision are only relevant for numeric and decimal types + // adjust the actual value scale and precision to match the user specified + if (mt.SqlDbType == SqlDbType.Decimal) + { + precision = param.GetActualPrecision(); + scale = param.GetActualScale(); + + if (precision > TdsEnums.MAX_NUMERIC_PRECISION) + { + throw SQL.PrecisionValueOutOfRange(precision); + } + + // Make sure the value matches the scale the user enters + if (!isNull) + { + if (isSqlVal) + { + value = AdjustSqlDecimalScale((SqlDecimal)value, scale); + + // If Precision is specified, verify value precision vs param precision + if (precision != 0) + { + if (precision < ((SqlDecimal)value).Precision) + { + throw ADP.ParameterValueOutOfRange((SqlDecimal)value); + } + } + } + else + { + value = AdjustDecimalScale((decimal)value, scale); + + SqlDecimal sqlValue = new SqlDecimal((decimal)value); + + // If Precision is specified, verify value precision vs param precision + if (precision != 0) + { + if (precision < sqlValue.Precision) + { + throw ADP.ParameterValueOutOfRange((decimal)value); + } + } + } + } + } + + // fixup the types by using the NullableType property of the MetaType class + // + // following rules should be followed based on feedback from the M-SQL team + // 1) always use the BIG* types (ex: instead of SQLCHAR use SQLBIGCHAR) + // 2) always use nullable types (ex: instead of SQLINT use SQLINTN) + // 3) DECIMALN should always be sent as NUMERICN + // + stateObj.WriteByte(mt.NullableType); + + // handle variants here: the SQLVariant writing routine will write the maxlen and actual len columns + if (mt.TDSType == TdsEnums.SQLVARIANT) + { + // devnote: Do we ever hit this codepath? Yes, when a null value is being written out via a sql variant + // param.GetActualSize is not used + WriteSqlVariantValue(isSqlVal ? MetaType.GetComValueFromSqlVariant(value) : value, param.GetActualSize(), param.Offset, stateObj); + return null; + } + + int codePageByteSize = 0; + int maxsize = 0; + + if (mt.IsAnsiType) + { + // Avoid the following code block if ANSI but unfilled LazyMat blob + if ((!isNull) && (!isDataFeed)) + { + string s; + + if (isSqlVal) + { + if (value is SqlString) + { + s = ((SqlString)value).Value; + } + else + { + Debug.Assert(value is SqlChars, "Unknown value for Ansi datatype"); + s = new string(((SqlChars)value).Value); + } + } + else + { + s = (string)value; + } + + codePageByteSize = GetEncodingCharLength(s, actualSize, param.Offset, _defaultEncoding); + } + + if (mt.IsPlp) + { + WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); + } + else + { + maxsize = (size > codePageByteSize) ? size : codePageByteSize; + if (maxsize == 0) + { + // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types + if (mt.IsNCharType) + maxsize = 2; + else + maxsize = 1; + } + + WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); + } + } + else + { + // If type timestamp - treat as fixed type and always send over timestamp length, which is 8. + // For fixed types, we either send null or fixed length for type length. We want to match that + // behavior for timestamps. However, in the case of null, we still must send 8 because if we + // send null we will not receive a output val. You can send null for fixed types and still + // receive a output value, but not for variable types. So, always send 8 for timestamp because + // while the user sees it as a fixed type, we are actually representing it as a bigbinary which + // is variable. + if (mt.SqlDbType == SqlDbType.Timestamp) + { + WriteParameterVarLen(mt, TdsEnums.TEXT_TIME_STAMP_LEN, false, stateObj); + } + else if (mt.SqlDbType == SqlDbType.Udt) + { + byte[] udtVal = null; + Format format = Format.Native; + + Debug.Assert(_isYukon, "Invalid DataType UDT for non-Yukon or later server!"); + + if (!isNull) + { + udtVal = _connHandler.Connection.GetBytes(value, out format, out maxsize); + + Debug.Assert(null != udtVal, "GetBytes returned null instance. Make sure that it always returns non-null value"); + size = udtVal.Length; + + //it may be legitimate, but we dont support it yet + if (size < 0 || (size >= ushort.MaxValue && maxsize != -1)) + throw new IndexOutOfRangeException(); + } + + if (string.IsNullOrEmpty(param.UdtTypeName)) + throw SQL.MustSetUdtTypeNameForUdtParams(); + + // Split the input name. TypeName is returned as single 3 part name during DeriveParameters. + // NOTE: ParseUdtTypeName throws if format is incorrect + string[] names = SqlParameter.ParseTypeName(param.UdtTypeName, true /* is UdtTypeName */); + if (!string.IsNullOrEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) + { + throw ADP.ArgumentOutOfRange(nameof(names)); + } + if (!string.IsNullOrEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) + { + throw ADP.ArgumentOutOfRange(nameof(names)); + } + if (TdsEnums.MAX_SERVERNAME < names[2].Length) + { + throw ADP.ArgumentOutOfRange(nameof(names)); + } + + WriteUDTMetaData(value, names[0], names[1], names[2], stateObj); + + if (!isNull) + { + WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length + if (udtVal.Length > 0) + { // Only write chunk length if its value is greater than 0 + WriteInt(udtVal.Length, stateObj); // Chunk length + stateObj.WriteByteArray(udtVal, udtVal.Length, 0); // Value + } + WriteInt(0, stateObj); // Terminator + } + else + { + WriteUnsignedLong(TdsEnums.SQL_PLP_NULL, stateObj); // PLP Null. + } + return null;//continue; // End of UDT - continue to next parameter. + } + else if (mt.IsPlp) + { + if (mt.SqlDbType != SqlDbType.Xml) + WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); + } + else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) + { // Time, Date, DateTime2, DateTimeoffset do not have the size written out + maxsize = (size > actualSize) ? size : actualSize; + if (maxsize == 0 && _isYukon) + { + // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) + if (mt.IsNCharType) + maxsize = 2; + else + maxsize = 1; + } + + WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); + } + } + + // scale and precision are only relevant for numeric and decimal types + if (mt.SqlDbType == SqlDbType.Decimal) + { + if (0 == precision) + { + stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION); + } + else + { + stateObj.WriteByte(precision); + } + + stateObj.WriteByte(scale); + } + else if (mt.IsVarTime) + { + stateObj.WriteByte(param.GetActualScale()); + } + + // write out collation or xml metadata + + if (_isYukon && (mt.SqlDbType == SqlDbType.Xml)) + { + if (((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) || + ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) || + ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty))) + { + stateObj.WriteByte(1); //Schema present flag + + if ((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) + { + tempLen = (param.XmlSchemaCollectionDatabase).Length; + stateObj.WriteByte((byte)(tempLen)); + WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj); + } + else + { + stateObj.WriteByte(0); // No dbname + } + + if ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) + { + tempLen = (param.XmlSchemaCollectionOwningSchema).Length; + stateObj.WriteByte((byte)(tempLen)); + WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj); + } + else + { + stateObj.WriteByte(0); // no xml schema name + } + + if ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty)) + { + tempLen = (param.XmlSchemaCollectionName).Length; + WriteShort((short)(tempLen), stateObj); + WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj); + } + else + { + WriteShort(0, stateObj); // No xml schema collection name + } + } + else + { + stateObj.WriteByte(0); // No schema + } + } + else if (mt.IsCharType) + { + // if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter + SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation; + Debug.Assert(_defaultCollation != null, "_defaultCollation is null!"); + + WriteUnsignedInt(outCollation.info, stateObj); + stateObj.WriteByte(outCollation.sortId); + } + + if (0 == codePageByteSize) + WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed); + else + WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed); + + Task writeParamTask = null; + // write the value now + if (!isNull) + { + if (isSqlVal) + { + 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(value, mt, param.GetActualScale(), actualSize, codePageByteSize, param.Offset, stateObj, param.Size, isDataFeed); + } + } + return writeParamTask; + } + private void FinalizeExecuteRPC(TdsParserStateObject stateObj) { stateObj.SniContext = SniContext.Snix_Read; diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs index 01923136a34d..9c248f8eba70 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs @@ -559,8 +559,14 @@ sealed internal class _SqlRPC internal string rpcName; internal ushort ProcID; // Used instead of name internal ushort options; - internal SqlParameter[] parameters; - internal byte[] paramoptions; + + internal SqlParameter[] systemParams; + internal byte[] systemParamOptions; + internal int systemParamCount; + + internal SqlParameterCollection userParams; + internal long[] userParamMap; + internal int userParamCount; internal int? recordsAffected; internal int cumulativeRecordsAffected; @@ -573,17 +579,36 @@ sealed internal class _SqlRPC internal int warningsIndexEnd; internal SqlErrorCollection warnings; - internal string GetCommandTextOrRpcName() - { - if (TdsEnums.RPC_PROCID_EXECUTESQL == ProcID) + //internal string GetCommandTextOrRpcName() + //{ + // if (TdsEnums.RPC_PROCID_EXECUTESQL == ProcID) + // { + // // Param 0 is the actual sql executing + // return (string)parameters[0].Value; + // } + // else + // { + // return rpcName; + // } + //} + + public SqlParameter GetParameterByIndex(int index, out byte options) + { + options = 0; + SqlParameter retval = null; + if (index < systemParamCount) { - // Param 0 is the actual sql executing - return (string)parameters[0].Value; + retval = systemParams[index]; + options = systemParamOptions[index]; } else { - return rpcName; + long data = userParamMap[index - systemParamCount]; + int paramIndex = (int)(data & int.MaxValue); + options = (byte)((data >> 32) & 0xFF); + retval = userParams[paramIndex]; } + return retval; } } From 63ff136cc9018cc32cbb68377defb0c139b0f4d5 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Sat, 13 Oct 2018 16:37:31 +0100 Subject: [PATCH 02/14] hoisted async lamda allocating functions to avoid allocating on synchronus paths --- .../src/System/Data/SqlClient/SqlCommand.cs | 93 ++++++++++++------- .../System/Data/SqlClient/SqlConnection.cs | 10 +- .../src/System/Data/SqlClient/TdsParser.cs | 10 +- .../SqlClient/TdsParserStateObjectNative.cs | 4 +- 4 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs index aeb7b9e0cb6c..4861dcbe5dae 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -918,7 +918,7 @@ public IAsyncResult BeginExecuteNonQuery(AsyncCallback callback, object stateObj cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteNonQuery), _activeConnection); if (execNQ != null) { - AsyncHelper.ContinueTask(execNQ, completion, () => BeginExecuteNonQueryInternalReadStage(completion)); + BeginExecuteNonQuerySetupCompletionContinuation(completion, execNQ); } else { @@ -952,6 +952,13 @@ public IAsyncResult BeginExecuteNonQuery(AsyncCallback callback, object stateObj } } + // This is in its own method to avoid always allocating the lambda in BeginExecuteNonQuery + [MethodImpl(MethodImplOptions.NoInlining)] + private void BeginExecuteNonQuerySetupCompletionContinuation(TaskCompletionSource completion, Task execNQ) + { + AsyncHelper.ContinueTask(execNQ, completion, () => BeginExecuteNonQueryInternalReadStage(completion)); + } + private void BeginExecuteNonQueryInternalReadStage(TaskCompletionSource completion) { // Read SNI does not have catches for async exceptions, handle here. @@ -1170,7 +1177,7 @@ private Task InternalExecuteNonQuery(TaskCompletionSource completion, bo { if (task != null) { - task = AsyncHelper.CreateContinuationTask(task, () => reader.Close()); + task = InternalExecuteNonQuerySetupCloseContinuation(task, reader); } else { @@ -1182,6 +1189,11 @@ private Task InternalExecuteNonQuery(TaskCompletionSource completion, bo return task; } + private static Task InternalExecuteNonQuerySetupCloseContinuation(Task task, SqlDataReader reader) + { + return AsyncHelper.CreateContinuationTask(task, () => reader.Close()); + } + public XmlReader ExecuteXmlReader() { // Reset _pendingCancel upon entry into any Execute - used to synchronize state @@ -2470,28 +2482,7 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi TaskCompletionSource completion = new TaskCompletionSource(); _activeConnection.RegisterWaitingForReconnect(completion.Task); _reconnectionCompletionSource = completion; - CancellationTokenSource timeoutCTS = new CancellationTokenSource(); - AsyncHelper.SetTimeoutException(completion, timeout, SQL.CR_ReconnectTimeout, timeoutCTS.Token); - AsyncHelper.ContinueTask(reconnectTask, completion, - () => - { - if (completion.Task.IsCompleted) - { - return; - } - Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion); - timeoutCTS.Cancel(); - Task subTask; - RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, TdsParserStaticMethods.GetRemainingTimeout(timeout, reconnectionStart), out subTask, asyncWrite, ds); - if (subTask == null) - { - completion.SetResult(null); - } - else - { - AsyncHelper.ContinueTask(subTask, completion, () => completion.SetResult(null)); - } - }, connectionToAbort: _activeConnection); + RunExecuteReaderTdsSetupReconnectContinuation(cmdBehavior, runBehavior, returnStream, async, timeout, asyncWrite, ds, reconnectTask, reconnectionStart, completion); task = completion.Task; return ds; } @@ -2627,15 +2618,7 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi decrementAsyncCountOnFailure = false; if (writeTask != null) { - task = AsyncHelper.CreateContinuationTask(writeTask, () => - { - _activeConnection.GetOpenTdsConnection(); // it will throw if connection is closed - cachedAsyncState.SetAsyncReaderState(ds, runBehavior, optionSettings); - }, - onFailure: (exc) => - { - _activeConnection.GetOpenTdsConnection().DecrementAsyncCount(); - }); + task = RunExecuteReaderTdsSetupContinuation(runBehavior, ds, optionSettings, writeTask); } else { @@ -2674,6 +2657,50 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi return ds; } + // This is in its own method to avoid always allocating the lambda in RunExecuteReaderTds + [MethodImpl(MethodImplOptions.NoInlining)] + private Task RunExecuteReaderTdsSetupContinuation(RunBehavior runBehavior, SqlDataReader ds, string optionSettings, Task writeTask) + { + Task task; + task = AsyncHelper.CreateContinuationTask(writeTask, () => + { + _activeConnection.GetOpenTdsConnection(); // it will throw if connection is closed + cachedAsyncState.SetAsyncReaderState(ds, runBehavior, optionSettings); + }, + onFailure: (exc) => + { + _activeConnection.GetOpenTdsConnection().DecrementAsyncCount(); + }); + return task; + } + + // This is in its own method to avoid always allocating the lambda in RunExecuteReaderTds + [MethodImpl(MethodImplOptions.NoInlining)] + private void RunExecuteReaderTdsSetupReconnectContinuation(CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, bool async, int timeout, bool asyncWrite, SqlDataReader ds, Task reconnectTask, long reconnectionStart, TaskCompletionSource completion) + { + CancellationTokenSource timeoutCTS = new CancellationTokenSource(); + AsyncHelper.SetTimeoutException(completion, timeout, SQL.CR_ReconnectTimeout, timeoutCTS.Token); + AsyncHelper.ContinueTask(reconnectTask, completion, + () => + { + if (completion.Task.IsCompleted) + { + return; + } + Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion); + timeoutCTS.Cancel(); + Task subTask; + RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, TdsParserStaticMethods.GetRemainingTimeout(timeout, reconnectionStart), out subTask, asyncWrite, ds); + if (subTask == null) + { + completion.SetResult(null); + } + else + { + AsyncHelper.ContinueTask(subTask, completion, () => completion.SetResult(null)); + } + }, connectionToAbort: _activeConnection); + } private SqlDataReader CompleteAsyncExecuteReader() { diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnection.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnection.cs index 28676a079b91..6eea597683ce 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnection.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnection.cs @@ -15,6 +15,7 @@ using System.IO; using System.Globalization; using System.Security; +using System.Runtime.CompilerServices; namespace System.Data.SqlClient { @@ -911,7 +912,7 @@ internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) catch (SqlException) { } - runningReconnect = Task.Run(() => ReconnectAsync(timeout)); + runningReconnect = ValidateAndReconnectSetupReconnectContinuation(timeout); // if current reconnect is not null, somebody already started reconnection task - some kind of race condition Debug.Assert(_currentReconnectionTask == null, "Duplicate reconnection tasks detected"); _currentReconnectionTask = runningReconnect; @@ -949,6 +950,13 @@ internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) return runningReconnect; } + // This is in its own method to avoid always allocating the lambda in ValidateAndReconnect + [MethodImpl(MethodImplOptions.NoInlining)] + private Task ValidateAndReconnectSetupReconnectContinuation(int timeout) + { + return Task.Run(() => ReconnectAsync(timeout)); + } + // this is straightforward, but expensive method to do connection resiliency - it take locks and all preparations as for TDS request partial void RepairInnerConnection() { diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index e67d10ff6798..8c8af8a6b1a6 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -2135,7 +2136,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead { // Dev11 #344723: SqlClient stress hang System_Data!Tcp::ReadSync via a call to SqlDataReader::Close // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent - SpinWait.SpinUntil(() => !stateObj._attentionSending); + TryRunSetupSpinWaitContinuation(stateObj); Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent"); if (stateObj._attentionSent) @@ -2159,6 +2160,13 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead return true; } + // This is in its own method to avoid always allocating the lambda in TryRun + [MethodImpl(MethodImplOptions.NoInlining)] + private static void TryRunSetupSpinWaitContinuation(TdsParserStateObject stateObj) + { + SpinWait.SpinUntil(() => !stateObj._attentionSending); + } + private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange[] sqlEnvChange) { // There could be multiple environment change messages following this token. diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectNative.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectNative.cs index c1fa34bd9c70..887eaf96a8bf 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectNative.cs @@ -18,6 +18,8 @@ internal class TdsParserStateObjectNative : TdsParserStateObject internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn private readonly WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used + private static readonly object CachedEmptyReadPacketObjectPointer = (object)IntPtr.Zero; + public TdsParserStateObjectNative(TdsParser parser) : base(parser) { } private GCHandle _gcHandle; // keeps this object alive until we're closed. @@ -35,7 +37,7 @@ internal TdsParserStateObjectNative(TdsParser parser, TdsParserStateObject physi internal override object SessionHandle => _sessionHandle; - protected override object EmptyReadPacket => IntPtr.Zero; + protected override object EmptyReadPacket => CachedEmptyReadPacketObjectPointer; protected override void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async) { From cb84ce00b26e7789f227a1239b782538a5ba2270 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Sat, 13 Oct 2018 22:58:16 +0100 Subject: [PATCH 03/14] spanify write single and double --- .../src/System/Data/SqlClient/TdsParser.cs | 12 +++--- .../Data/SqlClient/TdsParserStateObject.cs | 42 ++++++++++++++++--- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index 8c8af8a6b1a6..36a9ccdb953b 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -1420,9 +1420,9 @@ internal void WriteInt(int v, TdsParserStateObject stateObj) // internal void WriteFloat(float v, TdsParserStateObject stateObj) { - byte[] bytes = BitConverter.GetBytes(v); - - stateObj.WriteByteArray(bytes, bytes.Length, 0); + Span bytes = stackalloc byte[sizeof(float)]; + BitConverter.TryWriteBytes(bytes, v); + stateObj.WriteByteSpan(bytes); } // @@ -1494,9 +1494,9 @@ internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) // internal void WriteDouble(double v, TdsParserStateObject stateObj) { - byte[] bytes = BitConverter.GetBytes(v); - - stateObj.WriteByteArray(bytes, bytes.Length, 0); + Span bytes = stackalloc byte[sizeof(double)]; + BitConverter.TryWriteBytes(bytes, v); + stateObj.WriteByteSpan(bytes); } internal void PrepareResetConnection(bool preserveTransaction) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index dcc7eae1744e..a8cbc2f5d0b9 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -2998,11 +2998,25 @@ internal void WriteByte(byte b) _outBuff[_outBytesUsed++] = b; } + internal Task WriteByteSpan(ReadOnlySpan span, bool canAccumulate = true, TaskCompletionSource completion = null) + { + return WriteByteArray(span, span.Length, 0, canAccumulate, completion); + } + + internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) + { + return WriteByteArray(ReadOnlySpan.Empty, len, offsetBuffer, canAccumulate, completion, b); + } + // // Takes a byte array and writes it to the buffer. // - internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) + internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null,byte[] array = null) { + if (array != null) + { + b = new ReadOnlySpan(array, offsetBuffer, len); + } try { bool async = _parser._asyncWrite; // NOTE: We are capturing this now for the assert after the Task is returned, since WritePacket will turn off async if there is an exception @@ -3031,7 +3045,10 @@ internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumu int remainder = _outBuff.Length - _outBytesUsed; // write the remainder - Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, remainder); + //Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, remainder); + Span copyTo = _outBuff.AsSpan(_outBytesUsed, remainder); + ReadOnlySpan copyFrom = b.Slice(0, remainder); + copyFrom.CopyTo(copyTo); // handle counters offset += remainder; @@ -3049,7 +3066,18 @@ internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumu completion = new TaskCompletionSource(); task = completion.Task; // we only care about return from topmost call, so do not access Task property in other cases } - WriteByteArraySetupContinuation(b, len, completion, offset, packetTask); + + if (array == null) + { + byte[] tempArray = new byte[len]; + Span copyTempTo = tempArray.AsSpan(); + ReadOnlySpan copyTempFrom = b.Slice(remainder); + copyTempFrom.CopyTo(copyTempTo); + array = tempArray; + offset = 0; + } + + WriteByteArraySetupContinuation(array, len, completion, offset, packetTask); return task; } } @@ -3058,7 +3086,9 @@ internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumu // Else the remainder of the string will fit into the buffer, so copy it into the // buffer and then break out of the loop. - Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, len); + //Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, len); + Span copyTo = _outBuff.AsSpan(_outBytesUsed, len); + b.CopyTo(copyTo); // handle out buffer bytes used counter _outBytesUsed += len; @@ -3087,10 +3117,10 @@ internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumu } // This is in its own method to avoid always allocating the lambda in WriteByteArray - private void WriteByteArraySetupContinuation(byte[] b, int len, TaskCompletionSource completion, int offset, Task packetTask) + private void WriteByteArraySetupContinuation(byte[] array, int len, TaskCompletionSource completion, int offset, Task packetTask) { AsyncHelper.ContinueTask(packetTask, completion, - () => WriteByteArray(b, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion), + () => WriteByteArray(ReadOnlySpan.Empty, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion,array), connectionToDoom: _parser.Connection); } From 8af254f902c2ce74539305b0255146dc84bb20a1 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Sun, 14 Oct 2018 14:46:57 +0100 Subject: [PATCH 04/14] change prepare dhandle storage to use box and cache statis prepared invalid handle move some more async continuations o separate functions --- .../src/System/Data/SqlClient/SqlCommand.cs | 75 +++++++++++-------- .../src/System/Data/SqlClient/TdsParser.cs | 53 +++++++------ .../Data/SqlClient/TdsParserStateObject.cs | 37 +++------ .../SqlClient/TdsParserStateObjectNative.cs | 4 +- 4 files changed, 86 insertions(+), 83 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs index 4861dcbe5dae..d10a6ec622d2 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -52,8 +52,9 @@ private enum EXECTYPE // // _prepareHandle - the handle of a prepared command. Apparently there can be multiple prepared commands at a time - a feature that we do not support yet. + private static readonly object s_cachedInvalidPrepareHandle = (object)-1; private bool _inPrepare = false; - private int _prepareHandle = -1; + private object _prepareHandle = s_cachedInvalidPrepareHandle; // this is an int which is used in the object typed SqlParameter.Value field, avoid repeated boxing by storing in a box private bool _hiddenPrepare = false; private int _preparedConnectionCloseCount = -1; private int _preparedConnectionReconnectCount = -1; @@ -292,7 +293,7 @@ private SqlCommand(SqlCommand from) : this() finally { // clean prepare status (even successful Unprepare does not do that) - _prepareHandle = -1; + _prepareHandle = s_cachedInvalidPrepareHandle; _execType = EXECTYPE.UNPREPARED; } } @@ -672,7 +673,7 @@ internal void Unprepare() if ((_activeConnection.CloseCount != _preparedConnectionCloseCount) || (_activeConnection.ReconnectCount != _preparedConnectionReconnectCount)) { // reset our handle - _prepareHandle = -1; + _prepareHandle = s_cachedInvalidPrepareHandle; } _cachedMetaData = null; @@ -2340,27 +2341,7 @@ private Task RunExecuteNonQueryTds(string methodName, bool async, int timeout, b TaskCompletionSource completion = new TaskCompletionSource(); _activeConnection.RegisterWaitingForReconnect(completion.Task); _reconnectionCompletionSource = completion; - CancellationTokenSource timeoutCTS = new CancellationTokenSource(); - AsyncHelper.SetTimeoutException(completion, timeout, SQL.CR_ReconnectTimeout, timeoutCTS.Token); - AsyncHelper.ContinueTask(reconnectTask, completion, - () => - { - if (completion.Task.IsCompleted) - { - return; - } - Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion); - timeoutCTS.Cancel(); - Task subTask = RunExecuteNonQueryTds(methodName, async, TdsParserStaticMethods.GetRemainingTimeout(timeout, reconnectionStart), asyncWrite); - if (subTask == null) - { - completion.SetResult(null); - } - else - { - AsyncHelper.ContinueTask(subTask, completion, () => completion.SetResult(null)); - } - }, connectionToAbort: _activeConnection); + RunExecuteNonQueryTdsSetupReconnnectContinuation(methodName, async, timeout, asyncWrite, reconnectTask, reconnectionStart, completion); return completion.Task; } else @@ -2413,6 +2394,32 @@ private Task RunExecuteNonQueryTds(string methodName, bool async, int timeout, b return null; } + // This is in its own method to avoid always allocating the lambda in RunExecuteNonQueryTds + [MethodImpl(MethodImplOptions.NoInlining)] + private void RunExecuteNonQueryTdsSetupReconnnectContinuation(string methodName, bool async, int timeout, bool asyncWrite, Task reconnectTask, long reconnectionStart, TaskCompletionSource completion) + { + CancellationTokenSource timeoutCTS = new CancellationTokenSource(); + AsyncHelper.SetTimeoutException(completion, timeout, SQL.CR_ReconnectTimeout, timeoutCTS.Token); + AsyncHelper.ContinueTask(reconnectTask, completion, + () => + { + if (completion.Task.IsCompleted) + { + return; + } + Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion); + timeoutCTS.Cancel(); + Task subTask = RunExecuteNonQueryTds(methodName, async, TdsParserStaticMethods.GetRemainingTimeout(timeout, reconnectionStart), asyncWrite); + if (subTask == null) + { + completion.SetResult(null); + } + else + { + AsyncHelper.ContinueTask(subTask, completion, () => completion.SetResult(null)); + } + }, connectionToAbort: _activeConnection); + } internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, [CallerMemberName] string method = "") { @@ -2559,7 +2566,7 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi if (_execType == EXECTYPE.PREPARED) { - Debug.Assert(this.IsPrepared && (_prepareHandle != -1), "invalid attempt to call sp_execute without a handle!"); + Debug.Assert(this.IsPrepared && ((int)_prepareHandle != -1), "invalid attempt to call sp_execute without a handle!"); rpc = BuildExecute(inSchema); } else if (_execType == EXECTYPE.PREPAREPENDING) @@ -3406,7 +3413,7 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql private _SqlRPC BuildExecute(bool inSchema) { - Debug.Assert(_prepareHandle != -1, "Invalid call to sp_execute without a valid handle!"); + Debug.Assert((int)_prepareHandle != -1, "Invalid call to sp_execute without a valid handle!"); const int systemParameterCount = 1; int userParameterCount = CountSendableParameters(_parameters); @@ -3435,7 +3442,7 @@ private _SqlRPC BuildExecute(bool inSchema) private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlParameterCollection parameters, ref _SqlRPC rpc) { - Debug.Assert(_prepareHandle == -1, "This command has an existing handle, use sp_execute!"); + Debug.Assert((int)_prepareHandle == -1, "This command has an existing handle, use sp_execute!"); Debug.Assert(CommandType.Text == this.CommandType, "invalid use of sp_executesql for stored proc invocation!"); int systemParamCount; SqlParameter sqlParam; @@ -3484,22 +3491,23 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete { StringBuilder paramList = new StringBuilder(); bool fAddSeperator = false; + int count = parameters.Count; - int count = 0; - - - count = parameters.Count; for (int i = 0; i < count; i++) { SqlParameter sqlParam = parameters[i]; sqlParam.Validate(i, CommandType.StoredProcedure == CommandType); // skip ReturnValue parameters; we never send them to the server if (!ShouldSendParameter(sqlParam)) + { continue; + } // add our separator for the ith parameter if (fAddSeperator) + { paramList.Append(','); + } paramList.Append(sqlParam.ParameterNameFixed); @@ -3620,7 +3628,10 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete // set the output bit for Output or InputOutput parameters if (sqlParam.Direction != ParameterDirection.Input) - paramList.Append(" " + TdsEnums.PARAM_OUTPUT); + { + paramList.Append(" "); + paramList.Append(TdsEnums.PARAM_OUTPUT); + } } return paramList.ToString(); diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index 36a9ccdb953b..7868295b4b41 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -6980,29 +6980,7 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques // Need to wait for flush - continuation will unlock the connection bool taskReleaseConnectionLock = releaseConnectionLock; releaseConnectionLock = false; - return executeTask.ContinueWith(t => - { - Debug.Assert(!t.IsCanceled, "Task should not be canceled"); - try - { - if (t.IsFaulted) - { - FailureCleanup(stateObj, t.Exception.InnerException); - throw t.Exception.InnerException; - } - else - { - stateObj.SniContext = SniContext.Snix_Read; - } - } - finally - { - if (taskReleaseConnectionLock) - { - _connHandler._parserLock.Release(); - } - } - }, TaskScheduler.Default); + return TDSExecuteSqlBatchSetupReleaseContinuation(stateObj, executeTask, taskReleaseConnectionLock); } // Finished sync @@ -7028,6 +7006,35 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques } } + // This is in its own method to avoid always allocating the lambda in TDSExecuteSqlBatch + [MethodImpl(MethodImplOptions.NoInlining)] + private Task TDSExecuteSqlBatchSetupReleaseContinuation(TdsParserStateObject stateObj, Task executeTask, bool taskReleaseConnectionLock) + { + return executeTask.ContinueWith(t => + { + Debug.Assert(!t.IsCanceled, "Task should not be canceled"); + try + { + if (t.IsFaulted) + { + FailureCleanup(stateObj, t.Exception.InnerException); + throw t.Exception.InnerException; + } + else + { + stateObj.SniContext = SniContext.Snix_Read; + } + } + finally + { + if (taskReleaseConnectionLock) + { + _connHandler._parserLock.Release(); + } + } + }, TaskScheduler.Default); + } + internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync = true, TaskCompletionSource completion = null, int startRpc = 0, int startParam = 0) { diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index a8cbc2f5d0b9..0d3e99765d56 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -2417,38 +2417,23 @@ internal bool IsConnectionAlive(bool throwOnException) } else { - uint error; + SniContext = SniContext.Snix_Connect; + uint error = CheckConnection(); - object readPacket = EmptyReadPacket; - - try + if ((error != TdsEnums.SNI_SUCCESS) && (error != TdsEnums.SNI_WAIT_TIMEOUT)) { - SniContext = SniContext.Snix_Connect; - - error = CheckConnection(); - - if ((error != TdsEnums.SNI_SUCCESS) && (error != TdsEnums.SNI_WAIT_TIMEOUT)) - { - // Connection is dead - isAlive = false; - if (throwOnException) - { - // Get the error from SNI so that we can throw the correct exception - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(); - } - } - else + // Connection is dead + isAlive = false; + if (throwOnException) { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; + // Get the error from SNI so that we can throw the correct exception + AddError(_parser.ProcessSNIError(this)); + ThrowExceptionAndWarning(); } } - finally + else { - if (!IsPacketEmpty(readPacket)) - { - ReleasePacket(readPacket); - } + _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; } } } diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectNative.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectNative.cs index 887eaf96a8bf..13b42fe6f40f 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectNative.cs @@ -18,7 +18,7 @@ internal class TdsParserStateObjectNative : TdsParserStateObject internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn private readonly WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used - private static readonly object CachedEmptyReadPacketObjectPointer = (object)IntPtr.Zero; + private static readonly object s_cachedEmptyReadPacketObjectPointer = (object)IntPtr.Zero; public TdsParserStateObjectNative(TdsParser parser) : base(parser) { } @@ -37,7 +37,7 @@ internal TdsParserStateObjectNative(TdsParser parser, TdsParserStateObject physi internal override object SessionHandle => _sessionHandle; - protected override object EmptyReadPacket => CachedEmptyReadPacketObjectPointer; + protected override object EmptyReadPacket => s_cachedEmptyReadPacketObjectPointer; protected override void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async) { From b4e92b06e974c036e632795762801d21720ed2f7 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Wed, 17 Oct 2018 18:47:53 +0100 Subject: [PATCH 05/14] comment cleanup and formatting fixes --- .../src/System/Data/SqlClient/TdsParser.cs | 380 ++---------------- 1 file changed, 24 insertions(+), 356 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index 7868295b4b41..cfd8a3b97fbb 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -7128,17 +7128,15 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN byte options = 0; SqlParameter param = rpcext.GetParameterByIndex(i, out options); - - // Since we are reusing the parameters array, we cannot rely on length to indicate no of parameters. if (param == null) + { break; // End of parameters for this execute + } // Validate parameters are not variable length without size and with null value. param.Validate(i, isCommandProc); - - // type (parameter record stores the MetaType class which is a helper that encapsulates all the type information we need here) MetaType mt = param.InternalMetaType; @@ -7156,353 +7154,6 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN Task writeParamTask = TDSExecuteRPCAddParameter(stateObj,param,mt, options); - #region moved to function - //////////object value = null; - //////////bool isNull = true; - //////////bool isSqlVal = false; - //////////bool isDataFeed = false; - //////////// if we have an output param, set the value to null so we do not send it across to the server - //////////if (param.Direction == ParameterDirection.Output) - //////////{ - ////////// isSqlVal = param.ParameterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the parameter we will no longer be able to distinguish what type were seeing. - ////////// param.Value = null; - ////////// param.ParameterIsSqlType = isSqlVal; - //////////} - //////////else - //////////{ - ////////// value = param.GetCoercedValue(); - ////////// isNull = param.IsNull; - ////////// if (!isNull) - ////////// { - ////////// isSqlVal = param.CoercedValueIsSqlType; - ////////// isDataFeed = param.CoercedValueIsDataFeed; - ////////// } - //////////} - - //////////WriteParameterName(param.ParameterNameFixed, stateObj); - - //////////// Write parameter status - //////////stateObj.WriteByte(rpcext.paramoptions[i]); - - //////////// MaxLen field is only written out for non-fixed length data types - //////////// use the greater of the two sizes for maxLen - //////////int actualSize; - //////////int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize(); - - //////////// for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation - //////////if (mt.TDSType != TdsEnums.SQLUDT) - ////////// // getting the actualSize is expensive, cache here and use below - ////////// actualSize = param.GetActualSize(); - //////////else - ////////// actualSize = 0; //get this later - - //////////byte precision = 0; - //////////byte scale = 0; - - //////////// scale and precision are only relevant for numeric and decimal types - //////////// adjust the actual value scale and precision to match the user specified - //////////if (mt.SqlDbType == SqlDbType.Decimal) - //////////{ - ////////// precision = param.GetActualPrecision(); - ////////// scale = param.GetActualScale(); - - ////////// if (precision > TdsEnums.MAX_NUMERIC_PRECISION) - ////////// { - ////////// throw SQL.PrecisionValueOutOfRange(precision); - ////////// } - - ////////// // Make sure the value matches the scale the user enters - ////////// if (!isNull) - ////////// { - ////////// if (isSqlVal) - ////////// { - ////////// value = AdjustSqlDecimalScale((SqlDecimal)value, scale); - - ////////// // If Precision is specified, verify value precision vs param precision - ////////// if (precision != 0) - ////////// { - ////////// if (precision < ((SqlDecimal)value).Precision) - ////////// { - ////////// throw ADP.ParameterValueOutOfRange((SqlDecimal)value); - ////////// } - ////////// } - ////////// } - ////////// else - ////////// { - ////////// value = AdjustDecimalScale((decimal)value, scale); - - ////////// SqlDecimal sqlValue = new SqlDecimal((decimal)value); - - ////////// // If Precision is specified, verify value precision vs param precision - ////////// if (precision != 0) - ////////// { - ////////// if (precision < sqlValue.Precision) - ////////// { - ////////// throw ADP.ParameterValueOutOfRange((decimal)value); - ////////// } - ////////// } - ////////// } - ////////// } - //////////} - - //////////// fixup the types by using the NullableType property of the MetaType class - //////////// - //////////// following rules should be followed based on feedback from the M-SQL team - //////////// 1) always use the BIG* types (ex: instead of SQLCHAR use SQLBIGCHAR) - //////////// 2) always use nullable types (ex: instead of SQLINT use SQLINTN) - //////////// 3) DECIMALN should always be sent as NUMERICN - //////////// - //////////stateObj.WriteByte(mt.NullableType); - - //////////// handle variants here: the SQLVariant writing routine will write the maxlen and actual len columns - //////////if (mt.TDSType == TdsEnums.SQLVARIANT) - //////////{ - ////////// // devnote: Do we ever hit this codepath? Yes, when a null value is being written out via a sql variant - ////////// // param.GetActualSize is not used - ////////// WriteSqlVariantValue(isSqlVal ? MetaType.GetComValueFromSqlVariant(value) : value, param.GetActualSize(), param.Offset, stateObj); - ////////// continue; - //////////} - - //////////int codePageByteSize = 0; - //////////int maxsize = 0; - - //////////if (mt.IsAnsiType) - //////////{ - ////////// // Avoid the following code block if ANSI but unfilled LazyMat blob - ////////// if ((!isNull) && (!isDataFeed)) - ////////// { - ////////// string s; - - ////////// if (isSqlVal) - ////////// { - ////////// if (value is SqlString) - ////////// { - ////////// s = ((SqlString)value).Value; - ////////// } - ////////// else - ////////// { - ////////// Debug.Assert(value is SqlChars, "Unknown value for Ansi datatype"); - ////////// s = new string(((SqlChars)value).Value); - ////////// } - ////////// } - ////////// else - ////////// { - ////////// s = (string)value; - ////////// } - - ////////// codePageByteSize = GetEncodingCharLength(s, actualSize, param.Offset, _defaultEncoding); - ////////// } - - ////////// if (mt.IsPlp) - ////////// { - ////////// WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); - ////////// } - ////////// else - ////////// { - ////////// maxsize = (size > codePageByteSize) ? size : codePageByteSize; - ////////// if (maxsize == 0) - ////////// { - ////////// // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types - ////////// if (mt.IsNCharType) - ////////// maxsize = 2; - ////////// else - ////////// maxsize = 1; - ////////// } - - ////////// WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); - ////////// } - //////////} - //////////else - //////////{ - ////////// // If type timestamp - treat as fixed type and always send over timestamp length, which is 8. - ////////// // For fixed types, we either send null or fixed length for type length. We want to match that - ////////// // behavior for timestamps. However, in the case of null, we still must send 8 because if we - ////////// // send null we will not receive a output val. You can send null for fixed types and still - ////////// // receive a output value, but not for variable types. So, always send 8 for timestamp because - ////////// // while the user sees it as a fixed type, we are actually representing it as a bigbinary which - ////////// // is variable. - ////////// if (mt.SqlDbType == SqlDbType.Timestamp) - ////////// { - ////////// WriteParameterVarLen(mt, TdsEnums.TEXT_TIME_STAMP_LEN, false, stateObj); - ////////// } - ////////// else if (mt.SqlDbType == SqlDbType.Udt) - ////////// { - ////////// byte[] udtVal = null; - ////////// Format format = Format.Native; - - ////////// Debug.Assert(_isYukon, "Invalid DataType UDT for non-Yukon or later server!"); - - ////////// if (!isNull) - ////////// { - ////////// udtVal = _connHandler.Connection.GetBytes(value, out format, out maxsize); - - ////////// Debug.Assert(null != udtVal, "GetBytes returned null instance. Make sure that it always returns non-null value"); - ////////// size = udtVal.Length; - - ////////// //it may be legitimate, but we dont support it yet - ////////// if (size < 0 || (size >= ushort.MaxValue && maxsize != -1)) - ////////// throw new IndexOutOfRangeException(); - ////////// } - - ////////// //if this is NULL value, write special null value - ////////// byte[] lenBytes = BitConverter.GetBytes((long)size); - - ////////// if (string.IsNullOrEmpty(param.UdtTypeName)) - ////////// throw SQL.MustSetUdtTypeNameForUdtParams(); - - ////////// // Split the input name. TypeName is returned as single 3 part name during DeriveParameters. - ////////// // NOTE: ParseUdtTypeName throws if format is incorrect - ////////// string[] names = SqlParameter.ParseTypeName(param.UdtTypeName, true /* is UdtTypeName */); - ////////// if (!string.IsNullOrEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) - ////////// { - ////////// throw ADP.ArgumentOutOfRange(nameof(names)); - ////////// } - ////////// if (!string.IsNullOrEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) - ////////// { - ////////// throw ADP.ArgumentOutOfRange(nameof(names)); - ////////// } - ////////// if (TdsEnums.MAX_SERVERNAME < names[2].Length) - ////////// { - ////////// throw ADP.ArgumentOutOfRange(nameof(names)); - ////////// } - - ////////// WriteUDTMetaData(value, names[0], names[1], names[2], stateObj); - - ////////// if (!isNull) - ////////// { - ////////// WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length - ////////// if (udtVal.Length > 0) - ////////// { // Only write chunk length if its value is greater than 0 - ////////// WriteInt(udtVal.Length, stateObj); // Chunk length - ////////// stateObj.WriteByteArray(udtVal, udtVal.Length, 0); // Value - ////////// } - ////////// WriteInt(0, stateObj); // Terminator - ////////// } - ////////// else - ////////// { - ////////// WriteUnsignedLong(TdsEnums.SQL_PLP_NULL, stateObj); // PLP Null. - ////////// } - ////////// continue; // End of UDT - continue to next parameter. - ////////// } - ////////// else if (mt.IsPlp) - ////////// { - ////////// if (mt.SqlDbType != SqlDbType.Xml) - ////////// WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); - ////////// } - ////////// else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) - ////////// { // Time, Date, DateTime2, DateTimeoffset do not have the size written out - ////////// maxsize = (size > actualSize) ? size : actualSize; - ////////// if (maxsize == 0 && _isYukon) - ////////// { - ////////// // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) - ////////// if (mt.IsNCharType) - ////////// maxsize = 2; - ////////// else - ////////// maxsize = 1; - ////////// } - - ////////// WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); - ////////// } - //////////} - - //////////// scale and precision are only relevant for numeric and decimal types - //////////if (mt.SqlDbType == SqlDbType.Decimal) - //////////{ - ////////// if (0 == precision) - ////////// { - ////////// stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION); - ////////// } - ////////// else - ////////// { - ////////// stateObj.WriteByte(precision); - ////////// } - - ////////// stateObj.WriteByte(scale); - //////////} - //////////else if (mt.IsVarTime) - //////////{ - ////////// stateObj.WriteByte(param.GetActualScale()); - //////////} - - //////////// write out collation or xml metadata - - //////////if (_isYukon && (mt.SqlDbType == SqlDbType.Xml)) - //////////{ - ////////// if (((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) || - ////////// ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) || - ////////// ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty))) - ////////// { - ////////// stateObj.WriteByte(1); //Schema present flag - - ////////// if ((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) - ////////// { - ////////// tempLen = (param.XmlSchemaCollectionDatabase).Length; - ////////// stateObj.WriteByte((byte)(tempLen)); - ////////// WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj); - ////////// } - ////////// else - ////////// { - ////////// stateObj.WriteByte(0); // No dbname - ////////// } - - ////////// if ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) - ////////// { - ////////// tempLen = (param.XmlSchemaCollectionOwningSchema).Length; - ////////// stateObj.WriteByte((byte)(tempLen)); - ////////// WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj); - ////////// } - ////////// else - ////////// { - ////////// stateObj.WriteByte(0); // no xml schema name - ////////// } - - ////////// if ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty)) - ////////// { - ////////// tempLen = (param.XmlSchemaCollectionName).Length; - ////////// WriteShort((short)(tempLen), stateObj); - ////////// WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj); - ////////// } - ////////// else - ////////// { - ////////// WriteShort(0, stateObj); // No xml schema collection name - ////////// } - ////////// } - ////////// else - ////////// { - ////////// stateObj.WriteByte(0); // No schema - ////////// } - //////////} - //////////else if (mt.IsCharType) - //////////{ - ////////// // if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter - ////////// SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation; - ////////// Debug.Assert(_defaultCollation != null, "_defaultCollation is null!"); - - ////////// WriteUnsignedInt(outCollation.info, stateObj); - ////////// stateObj.WriteByte(outCollation.sortId); - //////////} - - //////////if (0 == codePageByteSize) - ////////// WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed); - //////////else - ////////// WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed); - - //////////Task writeParamTask = null; - //////////// write the value now - //////////if (!isNull) - //////////{ - ////////// if (isSqlVal) - ////////// { - ////////// 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(value, mt, param.GetActualScale(), actualSize, codePageByteSize, param.Offset, stateObj, param.Size, isDataFeed); - ////////// } - //////////} - #endregion if (!sync) { if (writeParamTask == null) @@ -7626,11 +7277,24 @@ private void TDSExecuteRPCParameterSetupFlushCompletion(TdsParserStateObject sta // This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter private void TDSExecuteRPCParameterSetupWriteCompletion(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync, TaskCompletionSource completion, int ii, int i, Task writeParamTask) { - AsyncHelper.ContinueTask(writeParamTask, completion, - () => TdsExecuteRPC(rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion, - startRpc: ii, startParam: i + 1), - connectionToDoom: _connHandler, - onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj)); + AsyncHelper.ContinueTask( + writeParamTask, + completion, + () => TdsExecuteRPC( + rpcArray, + timeout, + inSchema, + notificationRequest, + stateObj, + isCommandProc, + sync, + completion, + startRpc: ii, + startParam: i + 1 + ), + connectionToDoom: _connHandler, + onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj) + ); } private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParameter param,MetaType mt,byte options) @@ -7959,9 +7623,13 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet } if (0 == codePageByteSize) + { WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed); + } else + { WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed); + } Task writeParamTask = null; // write the value now From 130491d46d404d6f05faeaac97dbabf95da3423f Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 18 Oct 2018 20:11:27 +0100 Subject: [PATCH 06/14] ifdefed float and double span writes to netcoreapp only --- .../src/System/Data/SqlClient/TdsParser.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index cfd8a3b97fbb..177528b70492 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -1420,9 +1420,14 @@ internal void WriteInt(int v, TdsParserStateObject stateObj) // internal void WriteFloat(float v, TdsParserStateObject stateObj) { +#if netcoreapp // BitConverter.TryGetBytes is only availble in netcoreapp 2.1> at this time, review support when changing Span bytes = stackalloc byte[sizeof(float)]; BitConverter.TryWriteBytes(bytes, v); stateObj.WriteByteSpan(bytes); +#else + byte[] bytes = BitConverter.GetBytes(v); + stateObj.WriteByteArray(bytes, bytes.Length, 0); +#endif } // @@ -1494,9 +1499,14 @@ internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) // internal void WriteDouble(double v, TdsParserStateObject stateObj) { +#if netcoreapp // BitConverter.TryGetBytes is only availble in netcoreapp 2.1> at this time, review support when changing Span bytes = stackalloc byte[sizeof(double)]; BitConverter.TryWriteBytes(bytes, v); stateObj.WriteByteSpan(bytes); +#else + byte[] bytes = BitConverter.GetBytes(v); + stateObj.WriteByteArray(bytes, bytes.Length, 0); +#endif } internal void PrepareResetConnection(bool preserveTransaction) From ec4bc009d94386a9e073f5f4127e2736877aa053 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 19 Oct 2018 22:43:18 +0100 Subject: [PATCH 07/14] limit copy source span to len in case b.Length>len --- .../src/System/Data/SqlClient/TdsParserStateObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index 0d3e99765d56..815bc0fdc76c 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -3056,7 +3056,7 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo { byte[] tempArray = new byte[len]; Span copyTempTo = tempArray.AsSpan(); - ReadOnlySpan copyTempFrom = b.Slice(remainder); + ReadOnlySpan copyTempFrom = b.Slice(remainder,len); copyTempFrom.CopyTo(copyTempTo); array = tempArray; offset = 0; From ff7f1560bfb87ce670cec91802d426fdc173c89d Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Wed, 24 Oct 2018 12:44:17 +0100 Subject: [PATCH 08/14] clarify copy source length and add length check assertions --- .../System/Data/SqlClient/TdsParserStateObject.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index 815bc0fdc76c..c348b846edca 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -3033,6 +3033,9 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo //Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, remainder); Span copyTo = _outBuff.AsSpan(_outBytesUsed, remainder); ReadOnlySpan copyFrom = b.Slice(0, remainder); + + Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length{copyFrom.Length:D} should be the same"); + copyFrom.CopyTo(copyTo); // handle counters @@ -3057,6 +3060,9 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo byte[] tempArray = new byte[len]; Span copyTempTo = tempArray.AsSpan(); ReadOnlySpan copyTempFrom = b.Slice(remainder,len); + + Debug.Assert(copyTempTo.Length == copyTempFrom.Length, $"copyTempTo.Length:{copyTempTo.Length} and copyTempFrom.Length{copyTempFrom.Length:D} should be the same"); + copyTempFrom.CopyTo(copyTempTo); array = tempArray; offset = 0; @@ -3073,7 +3079,11 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo //Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, len); Span copyTo = _outBuff.AsSpan(_outBytesUsed, len); - b.CopyTo(copyTo); + ReadOnlySpan copyFrom = b.Slice(0,len); + + Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length{copyFrom.Length:D} should be the same"); + + copyFrom.CopyTo(copyTo); // handle out buffer bytes used counter _outBytesUsed += len; From 3374f5624545b05cbb639599ee12127516a53520 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 25 Oct 2018 11:10:33 +0100 Subject: [PATCH 09/14] seal async state and try to avoid creating it unless it is needed --- .../src/System/Data/SqlClient/SqlCommand.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs index d10a6ec622d2..c68a6329a6a2 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -84,7 +84,7 @@ internal bool InPrepare } // Cached info for async executions - private class CachedAsyncState + private sealed class CachedAsyncState { private int _cachedAsyncCloseCount = -1; // value of the connection's CloseCount property when the asyncResult was set; tracks when connections are closed after an async operation private TaskCompletionSource _cachedAsyncResult = null; @@ -262,7 +262,7 @@ private SqlCommand(SqlCommand from) : this() // Don't allow the connection to be changed while in an async operation. if (_activeConnection != value && _activeConnection != null) { // If new value... - if (cachedAsyncState.PendingAsyncOperation) + if (_cachedAsyncState!=null && _cachedAsyncState.PendingAsyncOperation) { // If in pending async state, throw. throw SQL.CannotModifyPropertyAsyncOperationInProgress(); } @@ -2897,16 +2897,16 @@ private void ValidateCommand(bool async, [CallerMemberName] string method = "") private void ValidateAsyncCommand() { - if (cachedAsyncState.PendingAsyncOperation) + if (_cachedAsyncState!=null && _cachedAsyncState.PendingAsyncOperation) { // Enforce only one pending async execute at a time. - if (cachedAsyncState.IsActiveConnectionValid(_activeConnection)) + if (_cachedAsyncState.IsActiveConnectionValid(_activeConnection)) { throw SQL.PendingBeginXXXExists(); } else { _stateObj = null; // Session was re-claimed by session pool upon connection close. - cachedAsyncState.ResetAsyncState(); + _cachedAsyncState.ResetAsyncState(); } } } From 8af8d3a903cc05e5b6e88358a371d35f3641bfa0 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 25 Oct 2018 12:45:20 +0100 Subject: [PATCH 10/14] remove unused code comment --- .../System/Data/SqlClient/TdsParserHelperClasses.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs index 9c248f8eba70..5b62ff8a5144 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs @@ -579,19 +579,6 @@ sealed internal class _SqlRPC internal int warningsIndexEnd; internal SqlErrorCollection warnings; - //internal string GetCommandTextOrRpcName() - //{ - // if (TdsEnums.RPC_PROCID_EXECUTESQL == ProcID) - // { - // // Param 0 is the actual sql executing - // return (string)parameters[0].Value; - // } - // else - // { - // return rpcName; - // } - //} - public SqlParameter GetParameterByIndex(int index, out byte options) { options = 0; From 8744a67518381bcfb52a21ff3d3b7869855a40e3 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 25 Oct 2018 23:44:03 +0100 Subject: [PATCH 11/14] correct multipacket slice in loop remove nolinling on hoisted continuations --- .../src/System/Data/SqlClient/SqlCommand.cs | 4 ---- .../src/System/Data/SqlClient/TdsParser.cs | 2 -- .../src/System/Data/SqlClient/TdsParserStateObject.cs | 6 +++--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs index c68a6329a6a2..86c851e1ef9a 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -954,7 +954,6 @@ public IAsyncResult BeginExecuteNonQuery(AsyncCallback callback, object stateObj } // This is in its own method to avoid always allocating the lambda in BeginExecuteNonQuery - [MethodImpl(MethodImplOptions.NoInlining)] private void BeginExecuteNonQuerySetupCompletionContinuation(TaskCompletionSource completion, Task execNQ) { AsyncHelper.ContinueTask(execNQ, completion, () => BeginExecuteNonQueryInternalReadStage(completion)); @@ -2395,7 +2394,6 @@ private Task RunExecuteNonQueryTds(string methodName, bool async, int timeout, b } // This is in its own method to avoid always allocating the lambda in RunExecuteNonQueryTds - [MethodImpl(MethodImplOptions.NoInlining)] private void RunExecuteNonQueryTdsSetupReconnnectContinuation(string methodName, bool async, int timeout, bool asyncWrite, Task reconnectTask, long reconnectionStart, TaskCompletionSource completion) { CancellationTokenSource timeoutCTS = new CancellationTokenSource(); @@ -2665,7 +2663,6 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi } // This is in its own method to avoid always allocating the lambda in RunExecuteReaderTds - [MethodImpl(MethodImplOptions.NoInlining)] private Task RunExecuteReaderTdsSetupContinuation(RunBehavior runBehavior, SqlDataReader ds, string optionSettings, Task writeTask) { Task task; @@ -2682,7 +2679,6 @@ private Task RunExecuteReaderTdsSetupContinuation(RunBehavior runBehavior, SqlDa } // This is in its own method to avoid always allocating the lambda in RunExecuteReaderTds - [MethodImpl(MethodImplOptions.NoInlining)] private void RunExecuteReaderTdsSetupReconnectContinuation(CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, bool async, int timeout, bool asyncWrite, SqlDataReader ds, Task reconnectTask, long reconnectionStart, TaskCompletionSource completion) { CancellationTokenSource timeoutCTS = new CancellationTokenSource(); diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index 177528b70492..d997a0b892e1 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -2171,7 +2171,6 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead } // This is in its own method to avoid always allocating the lambda in TryRun - [MethodImpl(MethodImplOptions.NoInlining)] private static void TryRunSetupSpinWaitContinuation(TdsParserStateObject stateObj) { SpinWait.SpinUntil(() => !stateObj._attentionSending); @@ -7017,7 +7016,6 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques } // This is in its own method to avoid always allocating the lambda in TDSExecuteSqlBatch - [MethodImpl(MethodImplOptions.NoInlining)] private Task TDSExecuteSqlBatchSetupReleaseContinuation(TdsParserStateObject stateObj, Task executeTask, bool taskReleaseConnectionLock) { return executeTask.ContinueWith(t => diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index c348b846edca..4ec334aa5933 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -3030,7 +3030,6 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo int remainder = _outBuff.Length - _outBytesUsed; // write the remainder - //Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, remainder); Span copyTo = _outBuff.AsSpan(_outBytesUsed, remainder); ReadOnlySpan copyFrom = b.Slice(0, remainder); @@ -3038,10 +3037,10 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo copyFrom.CopyTo(copyTo); - // handle counters offset += remainder; _outBytesUsed += remainder; len -= remainder; + b = b.Slice(remainder); Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); @@ -3073,7 +3072,8 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo } } else - { //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) + { + //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) // Else the remainder of the string will fit into the buffer, so copy it into the // buffer and then break out of the loop. From 04cf2583801802ca74048e02882f1f058ea3459b Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 26 Oct 2018 01:51:08 +0100 Subject: [PATCH 12/14] address feedback --- .../src/System/Data/SqlClient/SqlCommand.cs | 7 ++----- .../src/System/Data/SqlClient/TdsParserStateObject.cs | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs index 86c851e1ef9a..270fe2df3d6a 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -3218,8 +3218,6 @@ private void GetRPCObject(int systemParamCount, int userParamCount, ref _SqlRPC rpc = _rpcArrayOf1[0]; } - //rpc.ProcID = 0; - //rpc.rpcName = null; rpc.options = 0; rpc.systemParamCount = systemParamCount; @@ -3247,7 +3245,6 @@ private void GetRPCObject(int systemParamCount, int userParamCount, ref _SqlRPC private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlParameterCollection parameters) { int paramCount = GetParameterCount(parameters); - //int j = startCount; int userParamCount = 0; for (int index = 0; index < paramCount; index++) @@ -3514,7 +3511,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete // paragraph above doesn't seem to be correct. Server won't find the type // if we don't provide a fully qualified name - paramList.Append(" "); + paramList.Append(' '); if (mt.SqlDbType == SqlDbType.Udt) { string fullTypeName = sqlParam.UdtTypeName; @@ -3625,7 +3622,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete // set the output bit for Output or InputOutput parameters if (sqlParam.Direction != ParameterDirection.Input) { - paramList.Append(" "); + paramList.Append(' '); paramList.Append(TdsEnums.PARAM_OUTPUT); } } diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index 4ec334aa5933..0c513f7f697f 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -3077,7 +3077,6 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo // Else the remainder of the string will fit into the buffer, so copy it into the // buffer and then break out of the loop. - //Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, len); Span copyTo = _outBuff.AsSpan(_outBytesUsed, len); ReadOnlySpan copyFrom = b.Slice(0,len); From 2c459d2a98d7ef5ff3a1a70b89b6bd304806cae1 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Wed, 31 Oct 2018 23:29:43 +0000 Subject: [PATCH 13/14] address feedback to cleanup spacing --- .../src/System/Data/SqlClient/SqlCommand.cs | 7 +++---- .../src/System/Data/SqlClient/TdsParser.cs | 2 +- .../System/Data/SqlClient/TdsParserStateObject.cs | 15 ++++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs index 270fe2df3d6a..491b0e5c2843 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -2665,8 +2665,7 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi // This is in its own method to avoid always allocating the lambda in RunExecuteReaderTds private Task RunExecuteReaderTdsSetupContinuation(RunBehavior runBehavior, SqlDataReader ds, string optionSettings, Task writeTask) { - Task task; - task = AsyncHelper.CreateContinuationTask(writeTask, () => + Task task = AsyncHelper.CreateContinuationTask(writeTask, () => { _activeConnection.GetOpenTdsConnection(); // it will throw if connection is closed cachedAsyncState.SetAsyncReaderState(ds, runBehavior, optionSettings); @@ -3301,7 +3300,7 @@ private _SqlRPC BuildPrepExec(CommandBehavior behavior) int userParameterCount = CountSendableParameters(_parameters); _SqlRPC rpc = null; - GetRPCObject(systemParameterCount,userParameterCount, ref rpc); + GetRPCObject(systemParameterCount, userParameterCount, ref rpc); rpc.ProcID = TdsEnums.RPC_PROCID_PREPEXEC; rpc.rpcName = TdsEnums.SP_PREPEXEC; @@ -3385,7 +3384,7 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql { Debug.Assert(this.CommandType == System.Data.CommandType.StoredProcedure, "Command must be a stored proc to execute an RPC"); int userParameterCount = CountSendableParameters(parameters); - GetRPCObject(0,userParameterCount, ref rpc); + GetRPCObject(0, userParameterCount, ref rpc); rpc.ProcID = 0; rpc.rpcName = this.CommandText; // just get the raw command text diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index d997a0b892e1..e981537da5cb 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -7160,7 +7160,7 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN throw ADP.VersionDoesNotSupportDataType(mt.TypeName); } - Task writeParamTask = TDSExecuteRPCAddParameter(stateObj,param,mt, options); + Task writeParamTask = TDSExecuteRPCAddParameter(stateObj, param, mt, options); if (!sync) { diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index 0c513f7f697f..b727412fba2c 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -2996,7 +2996,7 @@ internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumu // // Takes a byte array and writes it to the buffer. // - internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null,byte[] array = null) + internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null, byte[] array = null) { if (array != null) { @@ -3058,9 +3058,9 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo { byte[] tempArray = new byte[len]; Span copyTempTo = tempArray.AsSpan(); - ReadOnlySpan copyTempFrom = b.Slice(remainder,len); + ReadOnlySpan copyTempFrom = b.Slice(remainder, len); - Debug.Assert(copyTempTo.Length == copyTempFrom.Length, $"copyTempTo.Length:{copyTempTo.Length} and copyTempFrom.Length{copyTempFrom.Length:D} should be the same"); + Debug.Assert(copyTempTo.Length == copyTempFrom.Length, $"copyTempTo.Length:{copyTempTo.Length} and copyTempFrom.Length:{copyTempFrom.Length:D} should be the same"); copyTempFrom.CopyTo(copyTempTo); array = tempArray; @@ -3078,9 +3078,9 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo // buffer and then break out of the loop. Span copyTo = _outBuff.AsSpan(_outBytesUsed, len); - ReadOnlySpan copyFrom = b.Slice(0,len); + ReadOnlySpan copyFrom = b.Slice(0, len); - Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length{copyFrom.Length:D} should be the same"); + Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length:{copyFrom.Length:D} should be the same"); copyFrom.CopyTo(copyTo); @@ -3114,8 +3114,9 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo private void WriteByteArraySetupContinuation(byte[] array, int len, TaskCompletionSource completion, int offset, Task packetTask) { AsyncHelper.ContinueTask(packetTask, completion, - () => WriteByteArray(ReadOnlySpan.Empty, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion,array), - connectionToDoom: _parser.Connection); + () => WriteByteArray(ReadOnlySpan.Empty, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion, array), + connectionToDoom: _parser.Connection + ); } // Dumps contents of buffer to SNI for network write. From 869208dd6fcd1f7322e94e8a16432afc40e278ba Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Mon, 5 Nov 2018 21:18:07 +0000 Subject: [PATCH 14/14] rename WriteByteArray to WriteBytes, add comment explaining params, make private to prevent confused external callers --- .../Data/SqlClient/TdsParserStateObject.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index b727412fba2c..26faa7316b12 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -2985,18 +2985,21 @@ internal void WriteByte(byte b) internal Task WriteByteSpan(ReadOnlySpan span, bool canAccumulate = true, TaskCompletionSource completion = null) { - return WriteByteArray(span, span.Length, 0, canAccumulate, completion); + return WriteBytes(span, span.Length, 0, canAccumulate, completion); } internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) { - return WriteByteArray(ReadOnlySpan.Empty, len, offsetBuffer, canAccumulate, completion, b); + return WriteBytes(ReadOnlySpan.Empty, len, offsetBuffer, canAccumulate, completion, b); } // - // Takes a byte array and writes it to the buffer. - // - internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null, byte[] array = null) + // Takes a span or a byte array and writes it to the buffer + // If you pass in a span and a null array then the span wil be used. + // If you pass in a non-null array then the array will be used and the span is ignored. + // if the span cannot be written into the current packet then the remaining contents of the span are copied to a + // new heap allocated array that will used to callback into the method to continue the write operation. + private Task WriteBytes(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null, byte[] array = null) { if (array != null) { @@ -3016,14 +3019,14 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo int offset = offsetBuffer; - Debug.Assert(b.Length >= len, "Invalid length sent to WriteByteArray()!"); + Debug.Assert(b.Length >= len, "Invalid length sent to WriteBytes()!"); // loop through and write the entire array do { if ((_outBytesUsed + len) > _outBuff.Length) { - // If the remainder of the string won't fit into the buffer, then we have to put + // If the remainder of the data won't fit into the buffer, then we have to put // whatever we can into the buffer, and flush that so we can then put more into // the buffer on the next loop of the while. @@ -3067,7 +3070,7 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo offset = 0; } - WriteByteArraySetupContinuation(array, len, completion, offset, packetTask); + WriteBytesSetupContinuation(array, len, completion, offset, packetTask); return task; } } @@ -3110,11 +3113,11 @@ internal Task WriteByteArray(ReadOnlySpan b, int len, int offsetBuffer, bo } } - // This is in its own method to avoid always allocating the lambda in WriteByteArray - private void WriteByteArraySetupContinuation(byte[] array, int len, TaskCompletionSource completion, int offset, Task packetTask) + // This is in its own method to avoid always allocating the lambda in WriteBytes + private void WriteBytesSetupContinuation(byte[] array, int len, TaskCompletionSource completion, int offset, Task packetTask) { AsyncHelper.ContinueTask(packetTask, completion, - () => WriteByteArray(ReadOnlySpan.Empty, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion, array), + () => WriteBytes(ReadOnlySpan.Empty, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion, array), connectionToDoom: _parser.Connection ); }