// For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) { Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) || (value is INullable && !((INullable)value).IsNull)), "unexpected null SqlType!"); // parameters are always sent over as BIG or N types switch (type.NullableType) { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) WriteFloat(((SqlSingle)value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); WriteDouble(((SqlDouble)value).Value, stateObj); } break; case TdsEnums.SQLBIGBINARY: case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: { if (type.IsPlp) { WriteInt(actualLength, stateObj); // chunk length } if (value is SqlBinary) { return stateObj.WriteByteArray(((SqlBinary)value).Value, actualLength, offset, canAccumulate: false); } else { Debug.Assert(value is SqlBytes); return stateObj.WriteByteArray(((SqlBytes)value).Value, actualLength, offset, canAccumulate: false); } } case TdsEnums.SQLUNIQUEID: { byte[] b = ((SqlGuid)value).ToByteArray(); Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); stateObj.WriteByteArray(b, actualLength, 0); break; } case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); if (((SqlBoolean)value).Value == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); break; } case TdsEnums.SQLINTN: if (type.FixedLength == 1) stateObj.WriteByte(((SqlByte)value).Value); else if (type.FixedLength == 2) WriteShort(((SqlInt16)value).Value, stateObj); else if (type.FixedLength == 4) WriteInt(((SqlInt32)value).Value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); WriteLong(((SqlInt64)value).Value, stateObj); } break; case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLBIGVARCHAR: case TdsEnums.SQLTEXT: if (type.IsPlp) { WriteInt(codePageByteSize, stateObj); // chunk length } if (value is SqlChars) { String sch = new String(((SqlChars)value).Value); return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); return WriteEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: case TdsEnums.SQLXMLTYPE: if (type.IsPlp) { if (IsBOMNeeded(type, value)) { WriteInt(actualLength + 2, stateObj); // chunk length WriteShort(TdsEnums.XMLUNICODEBOM, stateObj); } else { WriteInt(actualLength, stateObj); // chunk length } } // convert to cchars instead of cbytes // Xml type is already converted to string through GetCoercedValue if (actualLength != 0) actualLength >>= 1; if (value is SqlChars) { return WriteCharArray(((SqlChars)value).Value, actualLength, offset, stateObj, canAccumulate: false); } else { Debug.Assert(value is SqlString); return WriteString(((SqlString)value).Value, actualLength, offset, stateObj, canAccumulate: false); } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); WriteSqlDecimal((SqlDecimal)value, stateObj); break; case TdsEnums.SQLDATETIMN: SqlDateTime dt = (SqlDateTime)value; if (type.FixedLength == 4) { if (0 > dt.DayTicks || dt.DayTicks > UInt16.MaxValue) throw SQL.SmallDateTimeOverflow(dt.ToString()); WriteShort(dt.DayTicks, stateObj); WriteShort(dt.TimeTicks / SqlDateTime.SQLTicksPerMinute, stateObj); } else { WriteInt(dt.DayTicks, stateObj); WriteInt(dt.TimeTicks, stateObj); } break; case TdsEnums.SQLMONEYN: { WriteSqlMoney((SqlMoney)value, type.FixedLength, stateObj); break; } case TdsEnums.SQLUDT: throw ADP.DbTypeNotSupported(SqlDbType.Udt.ToString()); default: Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null)); break; } // switch // return point for accumualated writes, note: non-accumulated writes returned from their case statements return null; }
// For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) { Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object"); // parameters are always sent over as BIG or N types switch (type.NullableType) { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) WriteFloat((Single)value, stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); WriteDouble((Double)value, stateObj); } break; case TdsEnums.SQLBIGBINARY: case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: { // An array should be in the object Debug.Assert(isDataFeed || value is byte[], "Value should be an array of bytes"); Debug.Assert(!isDataFeed || value is StreamDataFeed, "Value should be a stream"); if (isDataFeed) { Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!"); return NullIfCompletedWriteTask(WriteStreamFeed((StreamDataFeed)value, stateObj, paramSize)); } else { if (type.IsPlp) { WriteInt(actualLength, stateObj); // chunk length } return stateObj.WriteByteArray((byte[])value, actualLength, offset, canAccumulate: false); } } case TdsEnums.SQLUNIQUEID: { System.Guid guid = (System.Guid)value; byte[] b = guid.ToByteArray(); Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); stateObj.WriteByteArray(b, actualLength, 0); break; } case TdsEnums.SQLBITN: { Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); if ((bool)value == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); break; } case TdsEnums.SQLINTN: if (type.FixedLength == 1) stateObj.WriteByte((byte)value); else if (type.FixedLength == 2) WriteShort((Int16)value, stateObj); else if (type.FixedLength == 4) WriteInt((Int32)value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); WriteLong((Int64)value, stateObj); } break; case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLBIGVARCHAR: case TdsEnums.SQLTEXT: { Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader"); Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string"); if (isDataFeed) { Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!"); TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, needBom: true, encoding: _defaultEncoding, size: paramSize)); } else { return NullIfCompletedWriteTask(WriteTextFeed(tdf, _defaultEncoding, false, stateObj, paramSize)); } } else { if (type.IsPlp) { WriteInt(encodingByteSize, stateObj); // chunk length } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false); } else { return WriteEncodingChar((string)value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } } } case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: case TdsEnums.SQLXMLTYPE: { Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader"); Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string"); if (isDataFeed) { Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!"); TextDataFeed tdf = value as TextDataFeed; if (tdf == null) { return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize)); } else { return NullIfCompletedWriteTask(WriteTextFeed(tdf, null, IsBOMNeeded(type, value), stateObj, paramSize)); } } else { if (type.IsPlp) { if (IsBOMNeeded(type, value)) { WriteInt(actualLength + 2, stateObj); // chunk length WriteShort(TdsEnums.XMLUNICODEBOM, stateObj); } else { WriteInt(actualLength, stateObj); // chunk length } } if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false); } else { // convert to cchars instead of cbytes actualLength >>= 1; return WriteString((string)value, actualLength, offset, stateObj, canAccumulate: false); } } } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); WriteDecimal((Decimal)value, stateObj); break; case TdsEnums.SQLDATETIMN: Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length"); TdsDateTime dt = MetaType.FromDateTime((DateTime)value, (byte)type.FixedLength); if (type.FixedLength == 4) { if (0 > dt.days || dt.days > UInt16.MaxValue) throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture)); WriteShort(dt.days, stateObj); WriteShort(dt.time, stateObj); } else { WriteInt(dt.days, stateObj); WriteInt(dt.time, stateObj); } break; case TdsEnums.SQLMONEYN: { WriteCurrency((Decimal)value, type.FixedLength, stateObj); break; } case TdsEnums.SQLDATE: { WriteDate((DateTime)value, stateObj); break; } case TdsEnums.SQLTIME: if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) { throw SQL.TimeScaleValueOutOfRange(scale); } WriteTime((TimeSpan)value, scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIME2: if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) { throw SQL.TimeScaleValueOutOfRange(scale); } WriteDateTime2((DateTime)value, scale, actualLength, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: WriteDateTimeOffset((DateTimeOffset)value, scale, actualLength, stateObj); break; default: Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null)); break; } // switch // return point for accumualated writes, note: non-accumulated writes returned from their case statements return null; // Debug.WriteLine("value: " + value.ToString(CultureInfo.InvariantCulture)); }
internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int count, TdsParserStateObject stateObj) { if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) { throw ADP.ClosedConnectionError(); } stateObj.WriteByte(TdsEnums.SQLCOLMETADATA); WriteShort(count, stateObj); for (int i = 0; i < metadataCollection.Length; i++) { if (metadataCollection[i] != null) { _SqlMetaData md = metadataCollection[i]; // read user type - 4 bytes Yukon, 2 backwards WriteInt(0x0, stateObj); UInt16 flags; flags = (UInt16)(md.updatability << 2); flags |= (UInt16)(md.isNullable ? (UInt16)TdsEnums.Nullable : (UInt16)0); flags |= (UInt16)(md.isIdentity ? (UInt16)TdsEnums.Identity : (UInt16)0); WriteShort(flags, stateObj); // write the flags switch (md.type) { case SqlDbType.Decimal: stateObj.WriteByte(md.tdsType); WriteTokenLength(md.tdsType, md.length, stateObj); stateObj.WriteByte(md.precision); stateObj.WriteByte(md.scale); break; case SqlDbType.Xml: stateObj.WriteByteArray(s_xmlMetadataSubstituteSequence, s_xmlMetadataSubstituteSequence.Length, 0); break; case SqlDbType.Udt: throw ADP.DbTypeNotSupported(SqlDbType.Udt.ToString()); case SqlDbType.Date: stateObj.WriteByte(md.tdsType); break; case SqlDbType.Time: case SqlDbType.DateTime2: case SqlDbType.DateTimeOffset: stateObj.WriteByte(md.tdsType); stateObj.WriteByte(md.scale); break; default: stateObj.WriteByte(md.tdsType); WriteTokenLength(md.tdsType, md.length, stateObj); if (md.metaType.IsCharType) { WriteUnsignedInt(md.collation.info, stateObj); stateObj.WriteByte(md.collation.sortId); } break; } if (md.metaType.IsLong && !md.metaType.IsPlp) { WriteShort(md.tableName.Length, stateObj); WriteString(md.tableName, stateObj); } stateObj.WriteByte((byte)md.column.Length); WriteString(md.column, stateObj); } } // end for loop }
internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) { Debug.Assert(!isSqlType || value is INullable, "isSqlType is true, but value can not be type cast to an INullable"); Debug.Assert(!isDataFeed ^ value is DataFeed, "Incorrect value for isDataFeed"); Encoding saveEncoding = _defaultEncoding; SqlCollation saveCollation = _defaultCollation; int saveCodePage = _defaultCodePage; int saveLCID = _defaultLCID; Task resultTask = null; Task internalWriteTask = null; if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) { throw ADP.ClosedConnectionError(); } try { if (metadata.encoding != null) { _defaultEncoding = metadata.encoding; } if (metadata.collation != null) { _defaultCollation = metadata.collation; _defaultLCID = _defaultCollation.LCID; } _defaultCodePage = metadata.codePage; MetaType metatype = metadata.metaType; int ccb = 0; int ccbStringBytes = 0; if (isNull) { // For UDT, remember we treat as binary even though it is a PLP if (metatype.IsPlp && (metatype.NullableType != TdsEnums.SQLUDT || metatype.IsLong)) { WriteLong(unchecked((long)TdsEnums.SQL_PLP_NULL), stateObj); } else if (!metatype.IsFixed && !metatype.IsLong && !metatype.IsVarTime) { WriteShort(TdsEnums.VARNULL, stateObj); } else { stateObj.WriteByte(TdsEnums.FIXEDNULL); } return resultTask; } if (!isDataFeed) { switch (metatype.NullableType) { case TdsEnums.SQLBIGBINARY: case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: ccb = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; break; case TdsEnums.SQLUNIQUEID: ccb = GUID_SIZE; // that's a constant for guid break; case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLBIGVARCHAR: case TdsEnums.SQLTEXT: if (null == _defaultEncoding) { ThrowUnsupportedCollationEncountered(null); // stateObject only when reading } string stringValue = null; if (isSqlType) { stringValue = ((SqlString)value).Value; } else { stringValue = (string)value; } ccb = stringValue.Length; ccbStringBytes = _defaultEncoding.GetByteCount(stringValue); break; case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; break; case TdsEnums.SQLXMLTYPE: // Value here could be string or XmlReader if (value is XmlReader) { value = MetaType.GetStringFromXml((XmlReader)value); } ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; break; default: ccb = metadata.length; break; } } else { Debug.Assert(metatype.IsLong && ((metatype.SqlDbType == SqlDbType.VarBinary && value is StreamDataFeed) || ((metatype.SqlDbType == SqlDbType.VarChar || metatype.SqlDbType == SqlDbType.NVarChar) && value is TextDataFeed) || (metatype.SqlDbType == SqlDbType.Xml && value is XmlDataFeed)), "Stream data feed should only be assigned to VarBinary(max), Text data feed should only be assigned to [N]VarChar(max), Xml data feed should only be assigned to XML(max)"); } // Expected the text length in data stream for bulk copy of text, ntext, or image data. // if (metatype.IsLong) { switch (metatype.SqlDbType) { case SqlDbType.Text: case SqlDbType.NText: case SqlDbType.Image: stateObj.WriteByteArray(s_longDataHeader, s_longDataHeader.Length, 0); WriteTokenLength(metadata.tdsType, ccbStringBytes == 0 ? ccb : ccbStringBytes, stateObj); break; case SqlDbType.VarChar: case SqlDbType.NVarChar: case SqlDbType.VarBinary: case SqlDbType.Xml: case SqlDbType.Udt: // plp data WriteUnsignedLong(TdsEnums.SQL_PLP_UNKNOWNLEN, stateObj); break; } } else { WriteTokenLength(metadata.tdsType, ccbStringBytes == 0 ? ccb : ccbStringBytes, stateObj); } if (isSqlType) { internalWriteTask = WriteSqlValue(value, metatype, ccb, ccbStringBytes, 0, stateObj); } else if (metatype.SqlDbType != SqlDbType.Udt || metatype.IsLong) { internalWriteTask = WriteValue(value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed); if ((internalWriteTask == null) && (_asyncWrite)) { internalWriteTask = stateObj.WaitForAccumulatedWrites(); } Debug.Assert(_asyncWrite || stateObj.WaitForAccumulatedWrites() == null, "Should not have accumulated writes when writing sync"); } else { WriteShort(ccb, stateObj); internalWriteTask = stateObj.WriteByteArray((byte[])value, ccb, 0); } #if DEBUG //In DEBUG mode, when SetAlwaysTaskOnWrite is true, we create a task. Allows us to verify async execution paths. if (_asyncWrite && internalWriteTask == null && SqlBulkCopy.SetAlwaysTaskOnWrite == true) { internalWriteTask = Task.FromResult<object>(null); } #endif if (internalWriteTask != null) { //i.e. the write was async. resultTask = WriteBulkCopyValueSetupContinuation(internalWriteTask, saveEncoding, saveCollation, saveCodePage, saveLCID); } } finally { if (internalWriteTask == null) { _defaultEncoding = saveEncoding; _defaultCollation = saveCollation; _defaultCodePage = saveCodePage; _defaultLCID = saveLCID; } } return resultTask; }
internal Task WriteString(string s, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) { int cBytes = ADP.CharSize * length; // Perf shortcut: If it fits, write directly to the outBuff if (cBytes < (stateObj._outBuff.Length - stateObj._outBytesUsed)) { CopyStringToBytes(s, offset, stateObj._outBuff, stateObj._outBytesUsed, length); stateObj._outBytesUsed += cBytes; return null; } else { if (stateObj._bTmp == null || stateObj._bTmp.Length < cBytes) { stateObj._bTmp = new byte[cBytes]; } CopyStringToBytes(s, offset, stateObj._bTmp, 0, length); return stateObj.WriteByteArray(stateObj._bTmp, cBytes, 0, canAccumulate); } }
private Task WriteEncodingChar(string s, int numChars, int offset, Encoding encoding, TdsParserStateObject stateObj, bool canAccumulate = true) { char[] charData; byte[] byteData; // if hitting 7.0 server, encoding will be null in metadata for columns or return values since // 7.0 has no support for multiple code pages in data - single code page support only if (encoding == null) encoding = _defaultEncoding; charData = s.ToCharArray(offset, numChars); // Optimization: if the entire string fits in the current buffer, then copy it directly int bytesLeft = stateObj._outBuff.Length - stateObj._outBytesUsed; if ((numChars <= bytesLeft) && (encoding.GetMaxByteCount(charData.Length) <= bytesLeft)) { int bytesWritten = encoding.GetBytes(charData, 0, charData.Length, stateObj._outBuff, stateObj._outBytesUsed); stateObj._outBytesUsed += bytesWritten; return null; } else { byteData = encoding.GetBytes(charData, 0, numChars); Debug.Assert(byteData != null, "no data from encoding"); return stateObj.WriteByteArray(byteData, byteData.Length, 0, canAccumulate); } }
// // Translates a com+ object -> SqlVariant // when the type is ambiguous, we always convert to the bigger type // note that we also write out the maxlen and actuallen members (4 bytes each) // in addition to the SQLVariant structure // internal Task WriteSqlVariantValue(object value, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) { // handle null values if (ADP.IsNull(value)) { WriteInt(TdsEnums.FIXEDNULL, stateObj); //maxlen WriteInt(TdsEnums.FIXEDNULL, stateObj); //actuallen return null; } MetaType mt = MetaType.GetMetaTypeFromValue(value); // Special case data type correction for SqlMoney inside a SqlVariant. if ((TdsEnums.SQLNUMERICN == mt.TDSType) && (8 == length)) { // The caller will coerce all SqlTypes to native CLR types, which means SqlMoney will // coerce to decimal/SQLNUMERICN (via SqlMoney.Value call). In the case where the original // value was SqlMoney the caller will also pass in the metadata length for the SqlMoney type // which is 8 bytes. To honor the intent of the caller here we coerce this special case // input back to SqlMoney from decimal/SQLNUMERICN. mt = MetaType.GetMetaTypeFromValue(new SqlMoney((decimal)value)); } if (mt.IsAnsiType) { length = GetEncodingCharLength((string)value, length, 0, _defaultEncoding); } // max and actual len are equal to // SQLVARIANTSIZE {type (1 byte) + cbPropBytes (1 byte)} + cbPropBytes + length (actual length of data in bytes) WriteInt(TdsEnums.SQLVARIANT_SIZE + mt.PropBytes + length, stateObj); // maxLen WriteInt(TdsEnums.SQLVARIANT_SIZE + mt.PropBytes + length, stateObj); // actualLen // write the SQLVariant header (type and cbPropBytes) stateObj.WriteByte(mt.TDSType); stateObj.WriteByte(mt.PropBytes); // now write the actual PropBytes and data switch (mt.TDSType) { case TdsEnums.SQLFLT4: WriteFloat((Single)value, stateObj); break; case TdsEnums.SQLFLT8: WriteDouble((Double)value, stateObj); break; case TdsEnums.SQLINT8: WriteLong((Int64)value, stateObj); break; case TdsEnums.SQLINT4: WriteInt((Int32)value, stateObj); break; case TdsEnums.SQLINT2: WriteShort((Int16)value, stateObj); break; case TdsEnums.SQLINT1: stateObj.WriteByte((byte)value); break; case TdsEnums.SQLBIT: if ((bool)value == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); break; case TdsEnums.SQLBIGVARBINARY: { byte[] b = (byte[])value; WriteShort(length, stateObj); // propbytes: varlen return stateObj.WriteByteArray(b, length, offset, canAccumulate); } case TdsEnums.SQLBIGVARCHAR: { string s = (string)value; WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId WriteShort(length, stateObj); // propbyte: varlen return WriteEncodingChar(s, _defaultEncoding, stateObj, canAccumulate); } case TdsEnums.SQLUNIQUEID: { System.Guid guid = (System.Guid)value; byte[] b = guid.ToByteArray(); Debug.Assert((length == b.Length) && (length == 16), "Invalid length for guid type in com+ object"); stateObj.WriteByteArray(b, length, 0); break; } case TdsEnums.SQLNVARCHAR: { string s = (string)value; WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId WriteShort(length, stateObj); // propbyte: varlen // string takes cchar, not cbyte so convert length >>= 1; return WriteString(s, length, offset, stateObj, canAccumulate); } case TdsEnums.SQLDATETIME: { TdsDateTime dt = MetaType.FromDateTime((DateTime)value, 8); WriteInt(dt.days, stateObj); WriteInt(dt.time, stateObj); break; } case TdsEnums.SQLMONEY: { WriteCurrency((Decimal)value, 8, stateObj); break; } case TdsEnums.SQLNUMERICN: { stateObj.WriteByte(mt.Precision); //propbytes: precision stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale WriteDecimal((Decimal)value, stateObj); break; } case TdsEnums.SQLTIME: stateObj.WriteByte(mt.Scale); //propbytes: scale WriteTime((TimeSpan)value, mt.Scale, length, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: stateObj.WriteByte(mt.Scale); //propbytes: scale WriteDateTimeOffset((DateTimeOffset)value, mt.Scale, length, stateObj); break; default: Debug.Assert(false, "unknown tds type for sqlvariant!"); break; } // switch // return point for accumulated writes, note: non-accumulated writes returned from their case statements return null; }
// todo: since we now know the difference between SqlWriteVariantValue and SqlWriteRowDataVariant we should consider // combining these tow methods. // // Translates a com+ object -> SqlVariant // when the type is ambiguous, we always convert to the bigger type // note that we also write out the maxlen and actuallen members (4 bytes each) // in addition to the SQLVariant structure // // Devnote: DataRows are preceeded by Metadata. The Metadata includes the MaxLen value. // Therefore the sql_variant value must not include the MaxLength. This is the major difference // between this method and WriteSqlVariantValue above. // internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObj, bool canAccumulate = true) { // handle null values if ((null == value) || (DBNull.Value == value)) { WriteInt(TdsEnums.FIXEDNULL, stateObj); return null; } MetaType metatype = MetaType.GetMetaTypeFromValue(value); int length = 0; if (metatype.IsAnsiType) { length = GetEncodingCharLength((string)value, length, 0, _defaultEncoding); } switch (metatype.TDSType) { case TdsEnums.SQLFLT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); WriteFloat((Single)value, stateObj); break; case TdsEnums.SQLFLT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); WriteDouble((Double)value, stateObj); break; case TdsEnums.SQLINT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); WriteLong((Int64)value, stateObj); break; case TdsEnums.SQLINT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); WriteInt((Int32)value, stateObj); break; case TdsEnums.SQLINT2: WriteSqlVariantHeader(4, metatype.TDSType, metatype.PropBytes, stateObj); WriteShort((Int16)value, stateObj); break; case TdsEnums.SQLINT1: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte((byte)value); break; case TdsEnums.SQLBIT: WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj); if ((bool)value == true) stateObj.WriteByte(1); else stateObj.WriteByte(0); break; case TdsEnums.SQLBIGVARBINARY: { byte[] b = (byte[])value; length = b.Length; WriteSqlVariantHeader(4 + length, metatype.TDSType, metatype.PropBytes, stateObj); WriteShort(length, stateObj); // propbytes: varlen return stateObj.WriteByteArray(b, length, 0, canAccumulate); } case TdsEnums.SQLBIGVARCHAR: { string s = (string)value; length = s.Length; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId WriteShort(length, stateObj); return WriteEncodingChar(s, _defaultEncoding, stateObj, canAccumulate); } case TdsEnums.SQLUNIQUEID: { System.Guid guid = (System.Guid)value; byte[] b = guid.ToByteArray(); length = b.Length; Debug.Assert(length == 16, "Invalid length for guid type in com+ object"); WriteSqlVariantHeader(18, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByteArray(b, length, 0); break; } case TdsEnums.SQLNVARCHAR: { string s = (string)value; length = s.Length * 2; WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj); WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId WriteShort(length, stateObj); // propbyte: varlen // string takes cchar, not cbyte so convert length >>= 1; return WriteString(s, length, 0, stateObj, canAccumulate); } case TdsEnums.SQLDATETIME: { TdsDateTime dt = MetaType.FromDateTime((DateTime)value, 8); WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); WriteInt(dt.days, stateObj); WriteInt(dt.time, stateObj); break; } case TdsEnums.SQLMONEY: { WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); WriteCurrency((Decimal)value, 8, stateObj); break; } case TdsEnums.SQLNUMERICN: { WriteSqlVariantHeader(21, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Precision); //propbytes: precision stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale WriteDecimal((Decimal)value, stateObj); break; } case TdsEnums.SQLTIME: WriteSqlVariantHeader(8, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale WriteTime((TimeSpan)value, metatype.Scale, 5, stateObj); break; case TdsEnums.SQLDATETIMEOFFSET: WriteSqlVariantHeader(13, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Scale); //propbytes: scale WriteDateTimeOffset((DateTimeOffset)value, metatype.Scale, 10, stateObj); break; default: Debug.Assert(false, "unknown tds type for sqlvariant!"); break; } // switch // return point for accumualated writes, note: non-accumulated writes returned from their case statements return null; }
// Write the trace header data, not including the trace header length private void WriteTraceHeaderData(TdsParserStateObject stateObj) { Debug.Assert(this.IncludeTraceHeader, "WriteTraceHeaderData can only be called on a Denali or higher version server and bid trace with the control bit are on"); // We may need to update the trace header length if trace header is changed in the future ActivityCorrelator.ActivityId actId = ActivityCorrelator.Current; WriteShort(TdsEnums.HEADERTYPE_TRACE, stateObj); // Trace Header Type stateObj.WriteByteArray(actId.Id.ToByteArray(), GUID_SIZE, 0); // Id (Guid) WriteUnsignedInt(actId.Sequence, stateObj); // sequence number Bid.Trace("<sc.TdsParser.WriteTraceHeaderData|INFO> ActivityID %ls\n", actId.ToString()); }
// // Takes a double and writes it as a 64 bit double. // internal void WriteDouble(double v, TdsParserStateObject stateObj) { byte[] bytes = BitConverter.GetBytes(v); stateObj.WriteByteArray(bytes, bytes.Length, 0); }
internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int count, TdsParserStateObject stateObj) { if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) { throw ADP.ClosedConnectionError(); } stateObj.WriteByte(TdsEnums.SQLCOLMETADATA); WriteShort(count, stateObj); // Write CEK table - 0 count WriteCekTable(metadataCollection, stateObj); for (int i = 0; i < metadataCollection.Length; i++) { if (metadataCollection[i] != null) { _SqlMetaData md = metadataCollection[i]; // read user type - 4 bytes Yukon, 2 backwards if (IsYukonOrNewer) { WriteInt(0x0, stateObj); } else { WriteShort(0x0000, stateObj); } // Write the flags UInt16 flags; flags = (UInt16)(md.updatability << 2); flags |= (UInt16)(md.isNullable ? (UInt16)TdsEnums.Nullable : (UInt16)0); flags |= (UInt16)(md.isIdentity ? (UInt16)TdsEnums.Identity : (UInt16)0); // Write the next byte of flags if (_serverSupportsColumnEncryption) { // TCE Supported if (ShouldEncryptValuesForBulkCopy()) { // TCE enabled on connection options flags |= (UInt16)(md.isEncrypted ? (UInt16)(TdsEnums.IsEncrypted << 8) : (UInt16)0); } } WriteShort(flags, stateObj);// write the flags // todo: // for xml WriteTokenLength results in a no-op // discuss this with blaine ... // ([....]) xml datatype does not have token length in its metadata. So it should be a noop. switch (md.type) { case SqlDbType.Decimal: stateObj.WriteByte(md.tdsType); WriteTokenLength(md.tdsType, md.length, stateObj); stateObj.WriteByte(md.precision); stateObj.WriteByte(md.scale); break; case SqlDbType.Xml: // stateObj.WriteByteArray(s_xmlMetadataSubstituteSequence, s_xmlMetadataSubstituteSequence.Length, 0); break; case SqlDbType.Udt: stateObj.WriteByte(TdsEnums.SQLBIGVARBINARY); WriteTokenLength(TdsEnums.SQLBIGVARBINARY, md.length, stateObj); break; case SqlDbType.Date: stateObj.WriteByte(md.tdsType); break; case SqlDbType.Time: case SqlDbType.DateTime2: case SqlDbType.DateTimeOffset: stateObj.WriteByte(md.tdsType); stateObj.WriteByte(md.scale); break; default: stateObj.WriteByte(md.tdsType); WriteTokenLength(md.tdsType, md.length, stateObj); if (md.metaType.IsCharType && _isShiloh) { WriteUnsignedInt(md.collation.info, stateObj); stateObj.WriteByte(md.collation.sortId); } break; } if (md.metaType.IsLong && !md.metaType.IsPlp) { WriteShort(md.tableName.Length, stateObj); WriteString(md.tableName, stateObj); } WriteCryptoMetadata(md, stateObj); stateObj.WriteByte((byte)md.column.Length); WriteString(md.column, stateObj); } } // end for loop }
/// <summary> /// Writes a single entry of CEK Table into TDS Stream (for bulk copy). /// </summary> /// <returns></returns> internal void WriteEncryptionEntries (ref SqlTceCipherInfoTable cekTable, TdsParserStateObject stateObj) { for (int i =0; i < cekTable.Size; i++) { // Write Db ID WriteInt(cekTable[i].DatabaseId, stateObj); // Write Key ID WriteInt(cekTable[i].CekId, stateObj); // Write Key Version WriteInt(cekTable[i].CekVersion, stateObj); // Write 8 bytes of key MD Version Debug.Assert (8 == cekTable[i].CekMdVersion.Length); stateObj.WriteByteArray (cekTable[i].CekMdVersion, 8, 0); // We don't really need to send the keys stateObj.WriteByte(0x00); } }
internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync = true, TaskCompletionSource<object> completion = null, int startRpc = 0, int startParam = 0) { bool firstCall = (completion == null); bool releaseConnectionLock = false; Debug.Assert(cmd != null, @"cmd cannot be null inside TdsExecuteRPC"); Debug.Assert(!firstCall || startRpc == 0, "startRpc is not 0 on first call"); Debug.Assert(!firstCall || startParam == 0, "startParam is not 0 on first call"); Debug.Assert(!firstCall || !_connHandler.ThreadHasParserLockForClose, "Thread should not already have connection lock"); Debug.Assert(firstCall || _connHandler._parserLock.ThreadMayHaveLock(), "Connection lock not taken after the first call"); try { _SqlRPC rpcext = null; int tempLen; // SQLBUDT #20010853 - Promote, Commit and Rollback requests for // delegated transactions often happen while there is an open result // set, so we need to handle them by using a different MARS session, // otherwise we'll write on the physical state objects while someone // else is using it. When we don't have MARS enabled, we need to // lock the physical state object to syncronize it's use at least // until we increment the open results count. Once it's been // incremented the delegated transaction requests will fail, so they // won't stomp on anything. if (firstCall) { _connHandler._parserLock.Wait(canReleaseFromAnyThread:!sync); releaseConnectionLock = true; } try { // Ensure that connection is alive if ((TdsParserState.Broken == State) || (TdsParserState.Closed == State)) { throw ADP.ClosedConnectionError(); } // This validation step MUST be done after locking the connection to guarantee we don't // accidentally execute after the transaction has completed on a different thread. if (firstCall) { _asyncWrite = !sync; _connHandler.CheckEnlistedTransactionBinding(); stateObj.SetTimeoutSeconds(timeout); if ((!_fMARS) && (_physicalStateObj.HasOpenResult)) { Bid.Trace("<sc.TdsParser.TdsExecuteRPC|ERR> Potential multi-threaded misuse of connection, non-MARs connection with an open result %d#\n", ObjectID); } stateObj.SniContext = SniContext.Snix_Execute; if (_isYukon) { WriteRPCBatchHeaders(stateObj, notificationRequest); } stateObj._outputMessageType = TdsEnums.MT_RPC; } for (int ii = startRpc; ii < rpcArray.Length; ii++) { rpcext = rpcArray[ii]; if (startParam == 0 || ii > startRpc) { if (rpcext.ProcID != 0 && _isShiloh) { // Perf optimization for Shiloh and later, Debug.Assert(rpcext.ProcID < 255, "rpcExec:ProcID can't be larger than 255"); WriteShort(0xffff, stateObj); WriteShort((short)(rpcext.ProcID), stateObj); } else { Debug.Assert(!ADP.IsEmpty(rpcext.rpcName), "must have an RPC name"); tempLen = rpcext.rpcName.Length; WriteShort(tempLen, stateObj); WriteString(rpcext.rpcName, tempLen, 0, stateObj); } // Options WriteShort((short)rpcext.options, stateObj); } // Stream out parameters SqlParameter[] parameters = rpcext.parameters; for (int i = (ii == startRpc) ? startParam : 0; i < parameters.Length; i++) { // Debug.WriteLine("i: " + i.ToString(CultureInfo.InvariantCulture)); // parameters can be unnamed SqlParameter param = parameters[i]; // 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 // Throw an exception if ForceColumnEncryption is set on a parameter and the ColumnEncryption is not enabled on SqlConnection or SqlCommand if (param.ForceColumnEncryption && !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled))) { throw SQL.ParamInvalidForceColumnEncryptionSetting(param.ParameterName, rpcext.GetCommandTextOrRpcName()); } // Check if the applications wants to force column encryption to avoid sending sensitive data to server if (param.ForceColumnEncryption && param.CipherMetadata == null && (param.Direction == ParameterDirection.Input || param.Direction == ParameterDirection.InputOutput)) { // Application wants a parameter to be encrypted before sending it to server, however server doesnt think this parameter needs encryption. throw SQL.ParamUnExpectedEncryptionMetadata(param.ParameterName, rpcext.GetCommandTextOrRpcName()); } // Validate parameters are not variable length without size and with null value. MDAC 66522 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); continue; } if ((!_isShiloh && !mt.Is70Supported) || (!_isYukon && !mt.Is80Supported) || (!_isKatmai && !mt.Is90Supported)) { 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.ParamaterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the paramater we will no longer be able to distinguish what type were seeing. param.Value = null; param.ParamaterIsSqlType = 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); } // bug 49512, 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); } } } } } bool isParameterEncrypted = 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_ENCRYPTED); // Additional information we need to send over wire to the server when writing encrypted parameters. SqlColumnEncryptionInputParameterInfo encryptedParameterInfoToWrite = null; // If the parameter is encrypted, we need to encrypt the value. if (isParameterEncrypted) { Debug.Assert(mt.TDSType != TdsEnums.SQLVARIANT && mt.TDSType != TdsEnums.SQLUDT && mt.TDSType != TdsEnums.SQLXMLTYPE && mt.TDSType != TdsEnums.SQLIMAGE && mt.TDSType != TdsEnums.SQLTEXT && mt.TDSType != TdsEnums.SQLNTEXT, "Type unsupported for encryption"); byte[] serializedValue = null; byte[] encryptedValue = null; if (!isNull) { try { if (isSqlVal) { serializedValue = SerializeUnencryptedSqlValue(value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj); } else { // for codePageEncoded types, WriteValue simply expects the number of characters // For plp types, we also need the encoded byte size serializedValue = SerializeUnencryptedValue(value, mt, param.GetActualScale(), actualSize, param.Offset, isDataFeed, param.NormalizationRuleVersion, stateObj); } Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC."); encryptedValue = SqlSecurityUtility.EncryptWithKey(serializedValue, param.CipherMetadata, _connHandler.ConnectionOptions.DataSource); } catch (Exception e) { throw SQL.ParamEncryptionFailed(param.ParameterName, null, e); } Debug.Assert(encryptedValue != null && encryptedValue.Length > 0, "encryptedValue should not be null or empty in TdsExecuteRPC."); } else { encryptedValue = null; } // Change the datatype to varbinary(max). // Since we don't know the size of the encrypted parameter on the server side, always set to (max). // mt = MetaType.MetaMaxVarBinary; size = -1; actualSize = (encryptedValue == null) ? 0 : encryptedValue.Length; encryptedParameterInfoToWrite = new SqlColumnEncryptionInputParameterInfo(param.GetMetadataForTypeInfo(), param.CipherMetadata); // Set the value to the encrypted value and mark isSqlVal as false for VARBINARY encrypted value. value = encryptedValue; isSqlVal = false; } Debug.Assert(isParameterEncrypted == (encryptedParameterInfoToWrite != null), "encryptedParameterInfoToWrite can be not null if and only if isParameterEncrypted is true."); Debug.Assert(!isSqlVal || !isParameterEncrypted, "isParameterEncrypted can be true only if isSqlVal is false."); // // 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 writen 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 (SQL9 - 682322) 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; Microsoft.SqlServer.Server.Format format = Microsoft.SqlServer.Server.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 >= UInt16.MaxValue && maxsize != -1)) throw new IndexOutOfRangeException(); } //if this is NULL value, write special null value byte[] lenBytes = BitConverter.GetBytes((Int64)size); if (ADP.IsEmpty(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 (!ADP.IsEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) { throw ADP.ArgumentOutOfRange("names"); } if (!ADP.IsEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) { throw ADP.ArgumentOutOfRange("names"); } if (TdsEnums.MAX_SERVERNAME < names[2].Length) { throw ADP.ArgumentOutOfRange("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 && IsYukonOrNewer) { // 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) { if (_isShiloh) stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION); else stateObj.WriteByte(TdsEnums.SPHINX_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 (_isShiloh && 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, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); } } // Send encryption metadata for encrypted parameters. if (isParameterEncrypted) { writeParamTask = WriteEncryptionMetadata(writeParamTask, encryptedParameterInfoToWrite, stateObj); } if (!sync) { if (writeParamTask == null) { writeParamTask = stateObj.WaitForAccumulatedWrites(); } if (writeParamTask != null) { Task task = null; if (completion == null) { completion = new TaskCompletionSource<object>(); task = completion.Task; } AsyncHelper.ContinueTask(writeParamTask, completion, () => TdsExecuteRPC(cmd, rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion, startRpc: ii, startParam: i + 1), connectionToDoom: _connHandler, onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj)); // Take care of releasing the locks if (releaseConnectionLock) { task.ContinueWith(_ => { _connHandler._parserLock.Release(); }, TaskScheduler.Default); releaseConnectionLock = false; } return task; } } #if DEBUG else { Debug.Assert(writeParamTask == null, "Should not have a task when executing sync"); } #endif } // parameter for loop // If this is not the last RPC we are sending, add the batch flag if (ii < (rpcArray.Length - 1)) { if (_isYukon) { stateObj.WriteByte(TdsEnums.YUKON_RPCBATCHFLAG); } else { stateObj.WriteByte(TdsEnums.SHILOH_RPCBATCHFLAG); } } } // rpc for loop Task execFlushTask = stateObj.ExecuteFlush(); Debug.Assert(!sync || execFlushTask == null, "Should not get a task when executing sync"); if (execFlushTask != null) { Task task = null; if (completion == null) { completion = new TaskCompletionSource<object>(); task = completion.Task; } bool taskReleaseConnectionLock = releaseConnectionLock; execFlushTask.ContinueWith(tsk => ExecuteFlushTaskCallback(tsk, stateObj, completion, taskReleaseConnectionLock), TaskScheduler.Default); // ExecuteFlushTaskCallback will take care of the locks for us releaseConnectionLock = false; return task; } } catch (Exception e) { // if (!ADP.IsCatchableExceptionType(e)) { throw; } FailureCleanup(stateObj, e); throw; } FinalizeExecuteRPC(stateObj); if (completion != null) { completion.SetResult(null); } return null; } catch (Exception e) { FinalizeExecuteRPC(stateObj); if (completion != null) { completion.SetException(e); return null; } else { throw e; } } finally { Debug.Assert(firstCall || !releaseConnectionLock, "Shouldn't be releasing locks synchronously after the first call"); if (releaseConnectionLock) { _connHandler._parserLock.Release(); } } }
internal SqlDataReader TdsExecuteTransactionManagerRequest( byte[] buffer, TdsEnums.TransactionManagerRequestType request, string transactionName, TdsEnums.TransactionManagerIsolationLevel isoLevel, int timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, bool isDelegateControlRequest) { Debug.Assert(this == stateObj.Parser, "different parsers"); if (TdsParserState.Broken == State || TdsParserState.Closed == State) { return null; } // SQLBUDT #20010853 - Promote, Commit and Rollback requests for // delegated transactions often happen while there is an open result // set, so we need to handle them by using a different MARS session, // otherwise we'll write on the physical state objects while someone // else is using it. When we don't have MARS enabled, we need to // lock the physical state object to syncronize it's use at least // until we increment the open results count. Once it's been // incremented the delegated transaction requests will fail, so they // won't stomp on anything. Debug.Assert(!_connHandler.ThreadHasParserLockForClose || _connHandler._parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken"); bool callerHasConnectionLock = _connHandler.ThreadHasParserLockForClose; // If the thread already claims to have the parser lock, then we will let the caller handle releasing it if (!callerHasConnectionLock) { _connHandler._parserLock.Wait(canReleaseFromAnyThread:false); _connHandler.ThreadHasParserLockForClose = true; } // Capture _asyncWrite (after taking lock) to restore it afterwards bool hadAsyncWrites = _asyncWrite; try { // Temprarily disable async writes _asyncWrite = false; // This validation step MUST be done after locking the connection to guarantee we don't // accidentally execute after the transaction has completed on a different thread. if (!isDelegateControlRequest) { _connHandler.CheckEnlistedTransactionBinding(); } stateObj._outputMessageType = TdsEnums.MT_TRANS; // set message type stateObj.SetTimeoutSeconds(timeout); stateObj.SniContext = SniContext.Snix_Execute; if (_isYukon) { const int marsHeaderSize = 18; // 4 + 2 + 8 + 4 const int totalHeaderLength = 22; // 4 + 4 + 2 + 8 + 4 Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length"); // Write total header length WriteInt(totalHeaderLength, stateObj); // Write mars header length WriteInt(marsHeaderSize, stateObj); WriteMarsHeaderData(stateObj, _currentTransaction); } WriteShort((short)request, stateObj); // write TransactionManager Request type bool returnReader = false; switch (request) { case TdsEnums.TransactionManagerRequestType.GetDTCAddress: WriteShort(0, stateObj); returnReader = true; break; case TdsEnums.TransactionManagerRequestType.Propagate: if (null != buffer) { WriteShort(buffer.Length, stateObj); stateObj.WriteByteArray(buffer, buffer.Length, 0); } else { WriteShort(0, stateObj); } break; case TdsEnums.TransactionManagerRequestType.Begin: Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for BeginTransaction!"); Debug.Assert(null != transaction, "Should have specified an internalTransaction when doing a BeginTransaction request!"); // Only assign the passed in transaction if it is not equal to the current transaction. // And, if it is not equal, the current actually should be null. Anything else // is a unexpected state. The concern here is mainly for the mixed use of // T-SQL and API transactions. See SQL BU DT 345300 for full details and repro. // Expected states: // 1) _pendingTransaction = null, _currentTransaction = null, non null transaction // passed in on BeginTransaction API call. // 2) _currentTransaction != null, _pendingTransaction = null, non null transaction // passed in but equivalent to _currentTransaction. // #1 will occur on standard BeginTransactionAPI call. #2 should only occur if // t-sql transaction started followed by a call to SqlConnection.BeginTransaction. // Any other state is unknown. if (_currentTransaction != transaction) { Debug.Assert(_currentTransaction == null || true == _fResetConnection, "We should not have a current Tx at this point"); PendingTransaction = transaction; } stateObj.WriteByte((byte)isoLevel); stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string). WriteString(transactionName, stateObj); break; case TdsEnums.TransactionManagerRequestType.Promote: Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for PromoteTransaction!"); // No payload - except current transaction in header // Promote returns a DTC cookie. However, the transaction cookie we use for the // connection does not change after a promote. break; case TdsEnums.TransactionManagerRequestType.Commit: Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for CommitTransaction!"); Debug.Assert(transactionName.Length == 0, "Should not have a transaction name on Commit"); stateObj.WriteByte((byte)0); // No xact name stateObj.WriteByte(0); // No flags Debug.Assert(isoLevel == TdsEnums.TransactionManagerIsolationLevel.Unspecified, "Should not have isolation level other than unspecified on Commit!"); // WriteByte((byte) 0, stateObj); // IsolationLevel // WriteByte((byte) 0, stateObj); // No begin xact name break; case TdsEnums.TransactionManagerRequestType.Rollback: Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for RollbackTransaction!"); stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string). WriteString(transactionName, stateObj); stateObj.WriteByte(0); // No flags Debug.Assert(isoLevel == TdsEnums.TransactionManagerIsolationLevel.Unspecified, "Should not have isolation level other than unspecified on Commit!"); // WriteByte((byte) 0, stateObj); // IsolationLevel // WriteByte((byte) 0, stateObj); // No begin xact name break; case TdsEnums.TransactionManagerRequestType.Save: Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for SaveTransaction!"); stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string). WriteString(transactionName, stateObj); break; default: Debug.Assert(false, "Unexpected TransactionManagerRequest"); break; } Task writeTask = stateObj.WritePacket(TdsEnums.HARDFLUSH); Debug.Assert(writeTask == null, "Writes should not pend when writing sync"); stateObj._pendingData = true; stateObj._messageStatus = 0; SqlDataReader dtcReader = null; stateObj.SniContext = SniContext.Snix_Read; if (returnReader) { dtcReader = new SqlDataReader(null, CommandBehavior.Default); Debug.Assert(this == stateObj.Parser, "different parser"); #if DEBUG // Remove the current owner of stateObj - otherwise we will hit asserts stateObj.Owner = null; #endif dtcReader.Bind(stateObj); // force consumption of metadata _SqlMetaDataSet metaData = dtcReader.MetaData; } else { Run(RunBehavior.UntilDone, null, null, null, stateObj); } // If the retained ID is no longer valid (because we are enlisting in null or a new transaction) then it should be cleared if (((request == TdsEnums.TransactionManagerRequestType.Begin) || (request == TdsEnums.TransactionManagerRequestType.Propagate)) && ((transaction == null) || (transaction.TransactionId != _retainedTransactionId))) { _retainedTransactionId = SqlInternalTransaction.NullTransactionId; } return dtcReader; } catch (Exception e) { // if (!ADP.IsCatchableExceptionType(e)) { throw; } FailureCleanup(stateObj, e); throw; } finally { // SQLHotfix 50000518 // make sure we don't leave temporary fields set when leaving this function _pendingTransaction = null; _asyncWrite = hadAsyncWrites; if (!callerHasConnectionLock) { _connHandler.ThreadHasParserLockForClose = false; _connHandler._parserLock.Release(); } } }
/// <summary> /// Write parameter encryption metadata. /// </summary> private void WriteEncryptionMetadata(SqlColumnEncryptionInputParameterInfo columnEncryptionParameterInfo, TdsParserStateObject stateObj) { Debug.Assert(columnEncryptionParameterInfo != null, @"columnEncryptionParameterInfo cannot be null"); Debug.Assert(stateObj != null, @"stateObj cannot be null"); // Write the TypeInfo. WriteSmiTypeInfo(columnEncryptionParameterInfo.ParameterMetadata, stateObj); // Write the serialized array in columnEncryptionParameterInfo. stateObj.WriteByteArray(columnEncryptionParameterInfo.SerializedWireFormat, columnEncryptionParameterInfo.SerializedWireFormat.Length, offsetBuffer: 0); }