From af478881dd4ec1edee4bae2d13d85fcb15e6675c Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Wed, 12 Dec 2018 01:05:43 +0000 Subject: [PATCH 1/4] change rpc mechanism to re-use parameters where possible and avoid re-allocating where possible --- .../src/System/Data/SqlClient/TdsParser.cs | 803 +++++++++--------- .../Data/SqlClient/TdsParserHelperClasses.cs | 26 +- 2 files changed, 442 insertions(+), 387 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 e7e35d351c0e..b012656a2b1f 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -6972,29 +6972,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 @@ -7020,8 +6998,36 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques } } + // This is in its own method to avoid always allocating the lambda in TDSExecuteSqlBatch + 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) + TaskCompletionSource completion = null, int startRpc = 0, int startParam = 0) { bool firstCall = (completion == null); bool releaseConnectionLock = false; @@ -7105,15 +7111,19 @@ 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 + } // Validate parameters are not variable length without size and with null value. param.Validate(i, isCommandProc); @@ -7123,7 +7133,7 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN 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,351 +7142,8 @@ 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); if (!sync) { @@ -7494,11 +7161,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 +7210,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 +7259,387 @@ 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..5b62ff8a5144 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,23 @@ sealed internal class _SqlRPC internal int warningsIndexEnd; internal SqlErrorCollection warnings; - internal string GetCommandTextOrRpcName() + public SqlParameter GetParameterByIndex(int index, out byte options) { - if (TdsEnums.RPC_PROCID_EXECUTESQL == ProcID) + 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 75430cf432653524927b1e9436715c85afd87630 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Wed, 12 Dec 2018 01:14:48 +0000 Subject: [PATCH 2/4] change sqlcommand to use new rpc user params format --- .../src/System/Data/SqlClient/SqlCommand.cs | 132 ++++++++++-------- 1 file changed, 70 insertions(+), 62 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..fca99fe81fdb 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,39 @@ private void GetRPCObject(int paramCount, ref _SqlRPC rpc) rpc = _rpcArrayOf1[0]; } - 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) - { - rpc.parameters = new SqlParameter[paramCount]; - } - else if (rpc.parameters.Length > paramCount) + if (currentCount < systemParamCount) { - 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 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 +3231,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 +3248,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 +3354,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); @@ -3371,25 +3376,24 @@ 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; + Debug.Assert((int)_prepareHandle != -1, "Invalid call to sp_execute without a valid handle!"); + 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; } @@ -3401,22 +3405,23 @@ 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 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 +3430,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); } } From aaf97d30aeca4b84c5fb93defdc6543fb8cac1f7 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 14 Dec 2018 22:15:10 +0000 Subject: [PATCH 3/4] fixed merge conflicts --- .../src/System/Data/SqlClient/SqlCommand.cs | 129 ++-- .../src/System/Data/SqlClient/TdsParser.cs | 714 +++++++++--------- 2 files changed, 432 insertions(+), 411 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 6416f49e4589..379d703f8dda 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -3217,10 +3217,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) @@ -3231,42 +3230,40 @@ private void GetRPCObject(int paramCount, ref _SqlRPC rpc) rpc = _rpcArrayOf1[0]; } - 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) - { - rpc.parameters = new SqlParameter[paramCount]; - } - else if (rpc.parameters.Length > paramCount) + if (currentCount < systemParamCount) { - 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 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 @@ -3277,12 +3274,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) @@ -3294,50 +3291,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; } @@ -3392,9 +3397,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); @@ -3414,24 +3420,23 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql private _SqlRPC BuildExecute(bool inSchema) { Debug.Assert((int)_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; } @@ -3445,20 +3450,21 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa 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 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; @@ -3467,19 +3473,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); } } 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 14e997883644..498d08a591a4 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -7113,15 +7113,19 @@ 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 + } // Validate parameters are not variable length without size and with null value. param.Validate(i, isCommandProc); @@ -7131,7 +7135,7 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN 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; } @@ -7140,351 +7144,8 @@ 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); if (!sync) { @@ -7512,9 +7173,9 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN sync, completion, ii, - i+1, + i + 1, writeParamTask - ); + ); // Take care of releasing the locks if (releaseConnectionLock) @@ -7612,6 +7273,357 @@ internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlN } } + 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; + } // 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 startRpc, int startParam, Task writeParamTask) From 8f051b2116bfda3dee73eddfcf96b0424f4836ab Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Mon, 25 Mar 2019 19:45:57 +0000 Subject: [PATCH 4/4] address feedback --- .../src/System/Data/SqlClient/SqlCommand.cs | 11 +++++------ .../System/Data/SqlClient/TdsParserHelperClasses.cs | 2 +- 2 files changed, 6 insertions(+), 7 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 20c29ce8145f..6919b35d3e5e 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -3295,8 +3295,7 @@ private void GetRPCObject(int systemParamCount, int userParamCount, ref _SqlRPC } } - - private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlParameterCollection parameters) + private void SetUpRPCParameters(_SqlRPC rpc, bool inSchema, SqlParameterCollection parameters) { int paramCount = GetParameterCount(parameters); int userParamCount = 0; @@ -3399,7 +3398,7 @@ private _SqlRPC BuildPrepExec(CommandBehavior behavior) sqlParam.Size = text.Length; sqlParam.Value = text; - SetUpRPCParameters(rpc, systemParameterCount, false, _parameters); + SetUpRPCParameters(rpc, false, _parameters); return rpc; } @@ -3461,7 +3460,7 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql rpc.ProcID = 0; rpc.rpcName = this.CommandText; // just get the raw command text - SetUpRPCParameters(rpc, 0, inSchema, parameters); + SetUpRPCParameters(rpc, inSchema, parameters); } // @@ -3494,7 +3493,7 @@ private _SqlRPC BuildExecute(bool inSchema) sqlParam.Value = _prepareHandle; sqlParam.Direction = ParameterDirection.Input; - SetUpRPCParameters(rpc, systemParameterCount, inSchema, _parameters); + SetUpRPCParameters(rpc, inSchema, _parameters); return rpc; } @@ -3546,7 +3545,7 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa sqlParam.Value = paramList; bool inSchema = (0 != (behavior & CommandBehavior.SchemaOnly)); - SetUpRPCParameters(rpc, systemParamCount, inSchema, parameters); + SetUpRPCParameters(rpc, inSchema, parameters); } } 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 dd7c263cb3fe..428416699fa5 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs @@ -678,7 +678,7 @@ sealed internal class _SqlRPC internal int warningsIndexEnd; internal SqlErrorCollection warnings; - public SqlParameter GetParameterByIndex(int index, out byte options) + internal SqlParameter GetParameterByIndex(int index, out byte options) { options = 0; SqlParameter retval = null;