Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10537,7 +10537,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet
size = -1;
actualSize = (encryptedValue == null) ? 0 : encryptedValue.Length;

encryptedParameterInfoToWrite = new SqlColumnEncryptionInputParameterInfo(param.GetMetadataForTypeInfo(),
encryptedParameterInfoToWrite = new SqlColumnEncryptionInputParameterInfo(param.GetMetadataForTypeInfo(this),
param.CipherMetadata);

// Set the value to the encrypted value and mark isSqlVal as false for VARBINARY encrypted value.
Expand Down Expand Up @@ -11009,7 +11009,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa
// Determine Metadata
//
ParameterPeekAheadValue peekAhead;
SmiParameterMetaData metaData = param.MetaDataForSmi(out peekAhead);
SmiParameterMetaData metaData = param.MetaDataForSmi(this, out peekAhead);

//
// Determine value to send
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,12 @@ private _SqlRPC BuildPrepExec(CommandBehavior behavior)
sqlParam.Direction = ParameterDirection.Input;

SetUpRPCParameters(rpc, inSchema: false, _parameters);
_pendingParameterByteCapacities = new int[_parameters.Count];
for (int i = 0; i < _parameters.Count; i++)
{
_pendingParameterByteCapacities[i] = GetAnsiParameterByteCapacity(
_parameters[i], _stateObj.Parser);
}
return rpc;
}

Expand Down Expand Up @@ -779,6 +785,9 @@ private void FinishExecuteReader(
}
}

// OnReturnValue clears _inPrepare even when preparation returns a null handle.
// A committed capacity snapshot distinguishes a valid handle from failed preparation.
bool wasInPrepare = _inPrepare;
if (runBehavior is RunBehavior.UntilDone)
{
try
Expand All @@ -801,12 +810,14 @@ private void FinishExecuteReader(
{
if (ADP.IsCatchableExceptionType(e))
{
if (_inPrepare)
if (wasInPrepare && _preparedParameterByteCapacities == null)
{
// The flag is expected to be reset by OnReturnValue. We should receive
// the handle unless command execution failed. If it fails, move back
// to pending state.
// Preparation failed without returning a handle. A returned handle and
// its capacity snapshot remain reusable after an execution error.
_inPrepare = false; // reset the flag
_prepareHandle = s_cachedInvalidPrepareHandle;
Comment thread
jscarle marked this conversation as resolved.
Resolved
_preparedParameterByteCapacities = null;
_pendingParameterByteCapacities = null;
IsDirty = true; // mark command as dirty so it will be
// prepared next time we're coming through
_execType = EXECTYPE.PREPAREPENDING; // reset execution type to pending
Expand Down Expand Up @@ -868,12 +879,14 @@ private void FinishExecuteReader(
}
catch (Exception e) when (ADP.IsCatchableExceptionType(e))
{
if (_inPrepare)
if (wasInPrepare && _preparedParameterByteCapacities == null)
{
// The flag is expected to be reset by OnReturnValue. We should receive
// the handle unless command execution failed. If it fails, move back
// to pending state.
// Preparation failed without returning a handle. A returned handle and
// its capacity snapshot remain reusable after an execution error.
_inPrepare = false; // reset the flag
_prepareHandle = s_cachedInvalidPrepareHandle;
_preparedParameterByteCapacities = null;
_pendingParameterByteCapacities = null;
IsDirty = true; // mark command as dirty so it will be prepared next time we're coming through
_execType = EXECTYPE.PREPAREPENDING; // reset execution type to pending
}
Expand Down Expand Up @@ -1449,6 +1462,11 @@ private SqlDataReader RunExecuteReaderTds(
else if (CommandType is CommandType.Text)
{
// @TODO: Execute as RPC (or text with parameters)
if (_execType is EXECTYPE.PREPARED && !IsDirty &&
PreparedParametersRequireReprepare())
{
IsDirty = true;
}
if (IsDirty)
{
// Can have cached metadata if dirty because of parameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,11 @@ private static class ProcParamsColumns
/// </summary>
private object _prepareHandle = s_cachedInvalidPrepareHandle;

// These capacities describe the server declaration, not the current parameter values.
// Zero skips non-ANSI parameters; -1 denotes a MAX declaration.
private int[] _preparedParameterByteCapacities;
private int[] _pendingParameterByteCapacities;

/// <summary>
/// Last TaskCompletionSource for reconnect task - use for cancellation only.
/// </summary>
Expand Down Expand Up @@ -629,6 +634,8 @@ public override CommandType CommandType
// Clean prepare status (even successful unprepare does not do that)
// @TODO: ... but it does?
_prepareHandle = s_cachedInvalidPrepareHandle;
_preparedParameterByteCapacities = null;
_pendingParameterByteCapacities = null;
_execType = EXECTYPE.UNPREPARED;
}
}
Expand Down Expand Up @@ -1604,8 +1611,10 @@ internal void OnReturnValue(TdsReturnValueToken returnValue, TdsParserStateObjec
if (!returnValue.Value.IsNull)
{
_prepareHandle = returnValue.Value.Int32;
_preparedParameterByteCapacities = _pendingParameterByteCapacities;
}

_pendingParameterByteCapacities = null;
_inPrepare = false;
return;
}
Expand Down Expand Up @@ -2167,7 +2176,7 @@ private string BuildParamList(TdsParser parser, SqlParameterCollection parameter
// Func will change type to that with a 4 byte length if the type has a two
// byte length and a parameter length > that expressible in 2 bytes.
// @TODO: what func?
mt = sqlParam.ValidateTypeLengths();
mt = sqlParam.ValidateTypeLengths(parser);
if (!mt.IsPlp && sqlParam.Direction is not ParameterDirection.Output)
{
sqlParam.FixStreamDataForNonPLP();
Expand Down Expand Up @@ -2213,36 +2222,7 @@ and not SqlDbType.Udt
// parser, with it's associated code page.
if (mt.IsAnsiType)
{
object val = sqlParam.GetCoercedValue();
string s = null;

// Deal with the sql types
if (val is not null && val != DBNull.Value)
{
// @TODO: I swear this can be one line in the if statement...
s = val as string;
if (s is null)
{
SqlString sval = val is SqlString sqlVal ? sqlVal : SqlString.Null;
if (!sval.IsNull)
{
s = sval.Value;
}
}
}

if (s is not null)
{
int actualBytes = parser.GetEncodingCharLength(
value: s,
numChars: sqlParam.GetActualSize(),
charOffset: sqlParam.Offset,
encoding: null);
if (actualBytes > size)
{
size = actualBytes;
}
}
size = GetAnsiParameterByteCapacity(sqlParam, parser);
}

// If the user specified a 0-sized parameter for a variable length field, pass
Expand Down Expand Up @@ -2273,6 +2253,56 @@ and not SqlDbType.Udt
return paramList.ToString();
}

private static int GetAnsiParameterByteCapacity(SqlParameter parameter, TdsParser parser)
{
MetaType metaType = parameter.InternalMetaType;
if (!metaType.IsAnsiType || parameter.Direction == ParameterDirection.ReturnValue)
{
return 0;
}
if (metaType.IsPlp)
{
return -1;
}
if (metaType.IsLong)
{
return 0;
}

int capacity = Math.Max(parameter.Size, parameter.GetActualSizeInBytes(parser));
return capacity == 0 ? TdsEnums.MAXSIZE : capacity;
}

private bool PreparedParametersRequireReprepare()
{
if (_preparedParameterByteCapacities == null ||
_preparedParameterByteCapacities.Length != _parameters.Count)
{
return true;
}

TdsParser parser = _stateObj.Parser;
for (int i = 0; i < _parameters.Count; i++)
{
int preparedCapacity = _preparedParameterByteCapacities[i];
if (preparedCapacity == 0 || preparedCapacity == -1)
{
continue;
}

SqlParameter parameter = _parameters[i];
parameter.Validate(i, isCommandProc: false);
parameter.ValidateTypeLengths(parser);
int requiredCapacity = GetAnsiParameterByteCapacity(parameter, parser);
if (requiredCapacity == -1 || requiredCapacity > preparedCapacity)
{
return true;
}
}

return false;
}

/// <summary>
/// This method is used to route CancellationTokens to the Cancel method. Cancellation is a
/// suggestion, and exceptions should be ignored rather than allowed to be unhandled, as
Expand Down Expand Up @@ -2724,7 +2754,7 @@ private void SetUpRPCParameters(_SqlRPC rpc, bool inSchema, SqlParameterCollecti

// Func will change type to that with a 4 byte length if the type has a 2 byte
// length and a parameter length > than that expressible in 2 bytes.
if (!parameter.ValidateTypeLengths().IsPlp && parameter.Direction is not ParameterDirection.Output)
if (!parameter.ValidateTypeLengths(_stateObj.Parser).IsPlp && parameter.Direction is not ParameterDirection.Output)
Comment thread
jscarle marked this conversation as resolved.
Resolved
{
parameter.FixStreamDataForNonPLP();
}
Expand Down Expand Up @@ -2864,6 +2894,9 @@ private void Unprepare()

_execType = EXECTYPE.PREPAREPENDING;

_preparedParameterByteCapacities = null;
_pendingParameterByteCapacities = null;

// Don't zero out the handle because we'll pass it in to sp_prepexec on the next prepare
// Unless the close count isn't the same as when we last prepared
if (_activeConnection.CloseCount != _preparedConnectionCloseCount ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ public int Offset
throw ADP.InvalidOffsetValue(value);
}
_offset = value;
_actualSize = -1;
}
}

Expand Down Expand Up @@ -1626,6 +1627,12 @@ internal int GetActualSize()
{
// for these types, ActualSize is the num of chars, not actual bytes - since non-unicode chars are not always uniform size
coercedSize = (!HasFlag(SqlParameterFlags.IsNull) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (StringSize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0;
// ANSI variants serialize the whole string, ignoring Offset.
if (!isSqlVariant)
{
// Size limits the transmitted slice, which starts at Offset.
coercedSize = Math.Max(0, coercedSize - Offset);
}
_actualSize = (ShouldSerializeSize() ? Size : 0);
_actualSize = (ShouldSerializeSize() && (_actualSize <= coercedSize)) ? _actualSize : coercedSize;
if (_actualSize == -1)
Expand Down Expand Up @@ -1719,29 +1726,57 @@ internal int GetParameterSize()
return ShouldSerializeSize() ? Size : ValueSize(CoercedValue);
}

// GetActualSize deliberately returns characters for ANSI values. Share the byte count
// between SQL declarations, RPC promotion, and Always Encrypted type_info metadata.
internal int GetActualSizeInBytes(TdsParser parser)
{
int actualSize = GetActualSize();
if (!InternalMetaType.IsAnsiType || IsNull ||
HasFlag(SqlParameterFlags.CoercedValueIsDataFeed) ||
Direction == ParameterDirection.Output)
{
return actualSize;
}

object value = GetCoercedValue();
string text = value is SqlString sqlString ? sqlString.Value :
value is SqlChars sqlChars ? new string(sqlChars.Value) : (string)value;

return parser.GetEncodingCharLength(text, actualSize, Offset, null);
}

/// <summary>
/// Get SMI Metadata to write out type_info stream.
/// </summary>
/// <returns></returns>
internal SmiParameterMetaData GetMetadataForTypeInfo()
internal SmiParameterMetaData GetMetadataForTypeInfo(TdsParser parser)
{
if (_internalMetaType == null)
{
_internalMetaType = GetMetaTypeOnly();
}

return MetaDataForSmi(out _);
return MetaDataForSmi(parser, out _);
}

// IMPORTANT DEVNOTE: This method is being used for parameter encryption functionality, to get the type_info TDS object from SqlParameter.
// Please consider impact to that when changing this method. Refer to the callers of SqlParameter.GetMetadataForTypeInfo().
internal SmiParameterMetaData MetaDataForSmi(out ParameterPeekAheadValue peekAhead)
internal SmiParameterMetaData MetaDataForSmi(
TdsParser parser, out ParameterPeekAheadValue peekAhead)
{
peekAhead = null;
MetaType mt = ValidateTypeLengths();
long actualLen = GetActualSize();
MetaType mt = ValidateTypeLengths(parser);
long actualLen = mt.IsAnsiType && !mt.IsLong
? GetActualSizeInBytes(parser)
: GetActualSize();
long maxLen = Size;

// MAX promotion can be caused by encoded bytes even when Size is below 8000.
if (mt.IsAnsiType && mt.IsPlp)
{
maxLen = SmiMetaData.UnlimitedMaxLengthIndicator;
}

// GetActualSize returns bytes length, but smi expects char length for
// character types, so adjust
if (!mt.IsLong)
Expand Down Expand Up @@ -2091,7 +2126,7 @@ internal void Validate(int index, bool isCommandProc)

// 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
internal MetaType ValidateTypeLengths()
internal MetaType ValidateTypeLengths(TdsParser parser)
{
MetaType mt = InternalMetaType;
// Since the server will automatically reject any
Expand All @@ -2104,6 +2139,16 @@ internal MetaType ValidateTypeLengths()
long actualSizeInBytes = GetActualSize();
long sizeInCharacters = Size;

// GetActualSize returns characters for ANSI strings. Decide whether to use
// MAX before building the SQL declaration or writing the RPC metadata, using
// the same connection encoding and character slice as the serializer.
if (mt.IsAnsiType &&
actualSizeInBytes <= TdsEnums.TYPE_SIZE_LIMIT &&
sizeInCharacters <= TdsEnums.TYPE_SIZE_LIMIT && sizeInCharacters != -1)
{
actualSizeInBytes = GetActualSizeInBytes(parser);
}

// Bug: VSTFDevDiv #636867
// Notes:
// 'actualSizeInBytes' is the size of value passed;
Expand Down
Loading
Loading