1 //------------------------------------------------------------------------------
2 // <copyright file="SqlChars.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">junfang</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 // <owner current="true" primary="false">[....]</owner>
8 //------------------------------------------------------------------------------
10 //**************************************************************************
14 // Created by: JunFang
16 // Description: Class SqlChars is used to represent a char/varchar/nchar/nvarchar
17 // data from SQL Server. It contains a char array buffer, which can
18 // be refilled. For example, in data access, user could use one instance
19 // of SqlChars to bind to a binary column, and we will just keep copying
20 // the data into the same instance, and avoid allocation per row.
27 // 120214 JXF 09/23/02 SqlBytes/SqlChars class indexer
28 // 112296 AZA 07/06/02 Seal SqlAccess classes.
29 // 107151 AZA 04/18/02 Track byte array buffer as well as SqlBytes in
31 // 107216 JXF 04/17/02 Bug 514927
32 // 106854 JXF 04/15/02 Fix http suites due to SqlChars
33 // 106448 JXF 04/12/02 Bugs on sqlchars
34 // 105715 JXF 04/05/02 Handle NULL properly in SqlBytes.SetLength
35 // 91128 JXF 10/17/01 Make SqlBytes not unsafe
37 // 04/20/01 JunFang Created.
40 //**************************************************************************
43 namespace System.Data.SqlTypes {
46 using System.Runtime.InteropServices;
47 using System.Diagnostics;
48 using System.Data.Common;
49 using System.Data.Sql;
50 using System.Data.SqlClient;
51 using System.Data.SqlTypes;
53 using System.Xml.Schema;
54 using System.Xml.Serialization;
55 using System.Runtime.Serialization;
56 using System.Security.Permissions;
58 [Serializable,XmlSchemaProvider("GetXsdType")]
59 public sealed class SqlChars : System.Data.SqlTypes.INullable, IXmlSerializable, ISerializable {
60 // --------------------------------------------------------------
62 // --------------------------------------------------------------
64 // SqlChars has five possible states
65 // 1) SqlChars is Null
66 // - m_stream must be null, m_lCuLen must be x_lNull
67 // 2) SqlChars contains a valid buffer,
68 // - m_rgchBuf must not be null, and m_stream must be null
69 // 3) SqlChars contains a valid pointer
70 // - m_rgchBuf could be null or not,
71 // if not null, content is garbage, should never look into it.
72 // - m_stream must be null.
73 // 4) SqlChars contains a SqlStreamChars
74 // - m_stream must not be null
75 // - m_rgchBuf could be null or not. if not null, content is garbage, should never look into it.
76 // - m_lCurLen must be x_lNull.
77 // 5) SqlChars contains a Lazy Materialized Blob (ie, StorageState.Delayed)
79 internal char[] m_rgchBuf; // Data buffer
80 private long m_lCurLen; // Current data length
81 internal SqlStreamChars m_stream;
82 private SqlBytesCharsState m_state;
84 private char[] m_rgchWorkBuf; // A 1-char work buffer.
86 // The max data length that we support at this time.
87 private const long x_lMaxLen = (long)System.Int32.MaxValue;
89 private const long x_lNull = -1L;
91 // --------------------------------------------------------------
93 // --------------------------------------------------------------
95 // Public default constructor used for XML serialization
100 // Create a SqlChars with an in-memory buffer
101 public SqlChars(char[] buffer) {
104 if (m_rgchBuf == null) {
105 m_state = SqlBytesCharsState.Null;
109 m_state = SqlBytesCharsState.Buffer;
110 m_lCurLen = (long)m_rgchBuf.Length;
113 m_rgchWorkBuf = null;
118 // Create a SqlChars from a SqlString
119 public SqlChars(SqlString value) : this (value.IsNull ? (char[])null : value.Value.ToCharArray()) {
122 // Create a SqlChars from a SqlStreamChars
123 internal SqlChars(SqlStreamChars s) {
127 m_state = (s == null) ? SqlBytesCharsState.Null : SqlBytesCharsState.Stream;
129 m_rgchWorkBuf = null;
134 // Constructor required for serialization. Deserializes as a Buffer. If the bits have been tampered with
135 // then this will throw a SerializationException or a InvalidCastException.
136 private SqlChars(SerializationInfo info, StreamingContext context)
139 m_rgchWorkBuf = null;
141 if (info.GetBoolean("IsNull"))
143 m_state = SqlBytesCharsState.Null;
148 m_state = SqlBytesCharsState.Buffer;
149 m_rgchBuf = (char[]) info.GetValue("data", typeof(char[]));
150 m_lCurLen = m_rgchBuf.Length;
156 // --------------------------------------------------------------
158 // --------------------------------------------------------------
163 return m_state == SqlBytesCharsState.Null;
167 // Property: the in-memory buffer of SqlChars
168 // Return Buffer even if SqlChars is Null.
170 public char[] Buffer {
173 CopyStreamToBuffer();
179 // Property: the actual length of the data
183 case SqlBytesCharsState.Null:
184 throw new SqlNullValueException();
186 case SqlBytesCharsState.Stream:
187 return m_stream.Length;
195 // Property: the max length of the data
196 // Return MaxLength even if SqlChars is Null.
197 // When the buffer is also null, return -1.
198 // If containing a Stream, return -1.
200 public long MaxLength {
203 case SqlBytesCharsState.Stream:
207 return (m_rgchBuf == null) ? -1L : (long)m_rgchBuf.Length;
212 // Property: get a copy of the data in a new char[] array.
214 public char[] Value {
219 case SqlBytesCharsState.Null:
220 throw new SqlNullValueException();
222 case SqlBytesCharsState.Stream:
223 if (m_stream.Length > x_lMaxLen)
224 throw new SqlTypeException(Res.GetString(Res.SqlMisc_BufferInsufficientMessage));
225 buffer = new char[m_stream.Length];
226 if (m_stream.Position != 0)
227 m_stream.Seek(0, SeekOrigin.Begin);
228 m_stream.Read(buffer, 0, checked((int)m_stream.Length));
232 buffer = new char[m_lCurLen];
233 Array.Copy(m_rgchBuf, buffer, (int)m_lCurLen);
243 public char this[long offset] {
245 if (offset < 0 || offset >= this.Length)
246 throw new ArgumentOutOfRangeException("offset");
248 if (m_rgchWorkBuf == null)
249 m_rgchWorkBuf = new char[1];
251 Read(offset, m_rgchWorkBuf, 0, 1);
252 return m_rgchWorkBuf[0];
255 if (m_rgchWorkBuf == null)
256 m_rgchWorkBuf = new char[1];
257 m_rgchWorkBuf[0] = value;
258 Write(offset, m_rgchWorkBuf, 0, 1);
262 internal SqlStreamChars Stream {
264 return FStream() ? m_stream : new StreamOnSqlChars(this);
269 m_state = (value == null) ? SqlBytesCharsState.Null : SqlBytesCharsState.Stream;
275 public StorageState Storage {
278 case SqlBytesCharsState.Null:
279 throw new SqlNullValueException();
281 case SqlBytesCharsState.Stream:
282 return StorageState.Stream;
284 case SqlBytesCharsState.Buffer:
285 return StorageState.Buffer;
288 return StorageState.UnmanagedBuffer;
293 // --------------------------------------------------------------
295 // --------------------------------------------------------------
297 public void SetNull() {
300 m_state = SqlBytesCharsState.Null;
305 // Set the current length of the data
306 // If the SqlChars is Null, setLength will make it non-Null.
307 public void SetLength(long value) {
309 throw new ArgumentOutOfRangeException("value");
312 m_stream.SetLength(value);
315 // If there is a buffer, even the value of SqlChars is Null,
316 // still allow setting length to zero, which will make it not Null.
317 // If the buffer is null, raise exception
319 if (null == m_rgchBuf)
320 throw new SqlTypeException(Res.GetString(Res.SqlMisc_NoBufferMessage));
322 if (value > (long)m_rgchBuf.Length)
323 throw new ArgumentOutOfRangeException("value");
326 // At this point we know that value is small enough
327 // Go back in buffer mode
328 m_state = SqlBytesCharsState.Buffer;
336 // Read data of specified length from specified offset into a buffer
338 public long Read(long offset, char[] buffer, int offsetInBuffer, int count) {
340 throw new SqlNullValueException();
342 // Validate the arguments
344 throw new ArgumentNullException("buffer");
346 if (offset > this.Length || offset < 0)
347 throw new ArgumentOutOfRangeException("offset");
349 if (offsetInBuffer > buffer.Length || offsetInBuffer < 0)
350 throw new ArgumentOutOfRangeException("offsetInBuffer");
352 if (count < 0 || count > buffer.Length - offsetInBuffer)
353 throw new ArgumentOutOfRangeException("count");
355 // Adjust count based on data length
356 if (count > this.Length - offset)
357 count = (int)(this.Length - offset);
361 case SqlBytesCharsState.Stream:
362 if (m_stream.Position != offset)
363 m_stream.Seek(offset, SeekOrigin.Begin);
364 m_stream.Read(buffer, offsetInBuffer, count);
368 Array.Copy(m_rgchBuf, offset, buffer, offsetInBuffer, count);
375 // Write data of specified length into the SqlChars from specified offset
377 public void Write(long offset, char[] buffer, int offsetInBuffer, int count) {
379 if (m_stream.Position != offset)
380 m_stream.Seek(offset, SeekOrigin.Begin);
381 m_stream.Write(buffer, offsetInBuffer, count);
384 // Validate the arguments
386 throw new ArgumentNullException("buffer");
388 if (m_rgchBuf == null)
389 throw new SqlTypeException(Res.GetString(Res.SqlMisc_NoBufferMessage));
392 throw new ArgumentOutOfRangeException("offset");
393 if (offset > m_rgchBuf.Length)
394 throw new SqlTypeException(Res.GetString(Res.SqlMisc_BufferInsufficientMessage));
396 if (offsetInBuffer < 0 || offsetInBuffer > buffer.Length)
397 throw new ArgumentOutOfRangeException("offsetInBuffer");
399 if (count < 0 || count > buffer.Length - offsetInBuffer)
400 throw new ArgumentOutOfRangeException("count");
402 if (count > m_rgchBuf.Length - offset)
403 throw new SqlTypeException(Res.GetString(Res.SqlMisc_BufferInsufficientMessage));
406 // If NULL and there is buffer inside, we only allow writing from
410 throw new SqlTypeException(Res.GetString(Res.SqlMisc_WriteNonZeroOffsetOnNullMessage));
412 // treat as if our current length is zero.
413 // Note this has to be done after all inputs are validated, so that
414 // we won't throw exception after this point.
417 m_state = SqlBytesCharsState.Buffer;
419 else if (offset > m_lCurLen) {
420 // Don't allow writing from an offset that this larger than current length.
421 // It would leave uninitialized data in the buffer.
423 throw new SqlTypeException(Res.GetString(Res.SqlMisc_WriteOffsetLargerThanLenMessage));
427 Array.Copy(buffer, offsetInBuffer, m_rgchBuf, offset, count);
429 // If the last position that has been written is after
430 // the current data length, reset the length
431 if (m_lCurLen < offset + count)
432 m_lCurLen = offset + count;
439 public SqlString ToSqlString() {
440 return IsNull ? SqlString.Null : new String(Value);
443 // --------------------------------------------------------------
444 // Conversion operators
445 // --------------------------------------------------------------
447 // Alternative method: ToSqlString()
448 public static explicit operator SqlString(SqlChars value) {
449 return value.ToSqlString();
452 // Alternative method: constructor SqlChars(SqlString)
453 public static explicit operator SqlChars(SqlString value) {
454 return new SqlChars(value);
457 // --------------------------------------------------------------
458 // Private utility functions
459 // --------------------------------------------------------------
461 [System.Diagnostics.Conditional("DEBUG")]
462 private void AssertValid() {
463 Debug.Assert(m_state >= SqlBytesCharsState.Null && m_state <= SqlBytesCharsState.Stream);
468 Debug.Assert((m_lCurLen >= 0 && m_lCurLen <= x_lMaxLen) || FStream());
469 Debug.Assert(FStream() || (m_rgchBuf != null && m_lCurLen <= m_rgchBuf.Length));
470 Debug.Assert(!FStream() || (m_lCurLen == x_lNull));
472 Debug.Assert(m_rgchWorkBuf == null || m_rgchWorkBuf.Length == 1);
475 // whether the SqlChars contains a Stream
476 internal bool FStream() {
477 return m_state == SqlBytesCharsState.Stream;
480 // Copy the data from the Stream to the array buffer.
481 // If the SqlChars doesn't hold a buffer or the buffer
482 // is not big enough, allocate new char array.
483 private void CopyStreamToBuffer() {
484 Debug.Assert(FStream());
486 long lStreamLen = m_stream.Length;
487 if (lStreamLen >= x_lMaxLen)
488 throw new SqlTypeException(Res.GetString(Res.SqlMisc_BufferInsufficientMessage));
490 if (m_rgchBuf == null || m_rgchBuf.Length < lStreamLen)
491 m_rgchBuf = new char[lStreamLen];
493 if (m_stream.Position != 0)
494 m_stream.Seek(0, SeekOrigin.Begin);
496 m_stream.Read(m_rgchBuf, 0, (int)lStreamLen);
498 m_lCurLen = lStreamLen;
499 m_state = SqlBytesCharsState.Buffer;
504 private void SetBuffer(char[] buffer) {
506 m_lCurLen = (m_rgchBuf == null) ? x_lNull : (long)m_rgchBuf.Length;
508 m_state = (m_rgchBuf == null) ? SqlBytesCharsState.Null : SqlBytesCharsState.Buffer;
513 // --------------------------------------------------------------
515 // --------------------------------------------------------------
518 XmlSchema IXmlSerializable.GetSchema() {
522 void IXmlSerializable.ReadXml(XmlReader r) {
525 string isNull = r.GetAttribute("nil", XmlSchema.InstanceNamespace);
527 if (isNull != null && XmlConvert.ToBoolean(isNull)) {
528 // VSTFDevDiv# 479603 - SqlTypes read null value infinitely and never read the next value. Fix - Read the next value.
529 r.ReadElementString();
533 value = r.ReadElementString().ToCharArray();
538 void IXmlSerializable.WriteXml(XmlWriter writer) {
540 writer.WriteAttributeString("xsi", "nil", XmlSchema.InstanceNamespace, "true");
543 char[] value = this.Buffer;
544 writer.WriteString(new String(value, 0, (int)(this.Length)));
548 public static XmlQualifiedName GetXsdType(XmlSchemaSet schemaSet) {
549 return new XmlQualifiedName("string", XmlSchema.Namespace);
552 // --------------------------------------------------------------
553 // Serialization using ISerializable
554 // --------------------------------------------------------------
556 // State information is not saved. The current state is converted to Buffer and only the underlying
557 // array is serialized, except for Null, in which case this state is kept.
558 [SecurityPermissionAttribute(SecurityAction.LinkDemand,SerializationFormatter=true)]
559 void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
562 case SqlBytesCharsState.Null:
563 info.AddValue("IsNull", true);
566 case SqlBytesCharsState.Buffer:
567 info.AddValue("IsNull", false);
568 info.AddValue("data", m_rgchBuf);
571 case SqlBytesCharsState.Stream:
572 CopyStreamToBuffer();
573 goto case SqlBytesCharsState.Buffer;
577 goto case SqlBytesCharsState.Null;
581 // --------------------------------------------------------------
582 // Static fields, properties
583 // --------------------------------------------------------------
585 // Get a Null instance.
586 // Since SqlChars is mutable, have to be property and create a new one each time.
587 public static SqlChars Null {
589 return new SqlChars((char[])null);
594 // StreamOnSqlChars is a stream build on top of SqlChars, and
595 // provides the Stream interface. The purpose is to help users
596 // to read/write SqlChars object.
597 internal sealed class StreamOnSqlChars : SqlStreamChars
599 // --------------------------------------------------------------
601 // --------------------------------------------------------------
603 private SqlChars m_sqlchars; // the SqlChars object
604 private long m_lPosition;
606 // --------------------------------------------------------------
608 // --------------------------------------------------------------
610 internal StreamOnSqlChars(SqlChars s) {
615 // --------------------------------------------------------------
617 // --------------------------------------------------------------
619 public override bool IsNull {
621 return m_sqlchars == null || m_sqlchars.IsNull;
625 // Always can read/write/seek, unless sb is null,
626 // which means the stream has been closed.
627 public override bool CanRead {
629 return m_sqlchars != null && !m_sqlchars.IsNull;
633 public override bool CanSeek {
635 return m_sqlchars != null;
639 public override bool CanWrite {
641 return m_sqlchars != null && (!m_sqlchars.IsNull || m_sqlchars.m_rgchBuf != null);
645 public override long Length {
647 CheckIfStreamClosed("get_Length");
648 return m_sqlchars.Length;
652 public override long Position {
654 CheckIfStreamClosed("get_Position");
658 CheckIfStreamClosed("set_Position");
659 if (value < 0 || value > m_sqlchars.Length)
660 throw new ArgumentOutOfRangeException("value");
666 // --------------------------------------------------------------
668 // --------------------------------------------------------------
670 public override long Seek(long offset, SeekOrigin origin) {
671 CheckIfStreamClosed("Seek");
676 case SeekOrigin.Begin:
677 if (offset < 0 || offset > m_sqlchars.Length)
678 throw ADP.ArgumentOutOfRange("offset");
679 m_lPosition = offset;
682 case SeekOrigin.Current:
683 lPosition = m_lPosition + offset;
684 if (lPosition < 0 || lPosition > m_sqlchars.Length)
685 throw ADP.ArgumentOutOfRange("offset");
686 m_lPosition = lPosition;
690 lPosition = m_sqlchars.Length + offset;
691 if (lPosition < 0 || lPosition > m_sqlchars.Length)
692 throw ADP.ArgumentOutOfRange("offset");
693 m_lPosition = lPosition;
697 throw ADP.ArgumentOutOfRange("offset");;
703 // The Read/Write/Readchar/Writechar simply delegates to SqlChars
704 public override int Read(char[] buffer, int offset, int count) {
705 CheckIfStreamClosed("Read");
708 throw new ArgumentNullException("buffer");
709 if (offset < 0 || offset > buffer.Length)
710 throw new ArgumentOutOfRangeException("offset");
711 if (count < 0 || count > buffer.Length - offset)
712 throw new ArgumentOutOfRangeException("count");
714 int icharsRead = (int)m_sqlchars.Read(m_lPosition, buffer, offset, count);
715 m_lPosition += icharsRead;
720 public override void Write(char[] buffer, int offset, int count) {
721 CheckIfStreamClosed("Write");
724 throw new ArgumentNullException("buffer");
725 if (offset < 0 || offset > buffer.Length)
726 throw new ArgumentOutOfRangeException("offset");
727 if (count < 0 || count > buffer.Length - offset)
728 throw new ArgumentOutOfRangeException("count");
730 m_sqlchars.Write(m_lPosition, buffer, offset, count);
731 m_lPosition += count;
734 public override int ReadChar() {
735 CheckIfStreamClosed("ReadChar");
737 // If at the end of stream, return -1, rather than call SqlChars.Readchar,
738 // which will throw exception. This is the behavior for Stream.
740 if (m_lPosition >= m_sqlchars.Length)
743 int ret = m_sqlchars[m_lPosition];
748 public override void WriteChar(char value) {
749 CheckIfStreamClosed("WriteChar");
751 m_sqlchars[m_lPosition] = value;
755 public override void SetLength(long value) {
756 CheckIfStreamClosed("SetLength");
758 m_sqlchars.SetLength(value);
759 if (m_lPosition > value)
763 // Flush is a no-op if underlying SqlChars is not a stream on SqlChars
764 public override void Flush() {
765 if (m_sqlchars.FStream())
766 m_sqlchars.m_stream.Flush();
769 protected override void Dispose(bool disposing) {
770 // When m_sqlchars is null, it means the stream has been closed, and
771 // any opearation in the future should fail.
772 // This is the only case that m_sqlchars is null.
776 // --------------------------------------------------------------
777 // Private utility functions
778 // --------------------------------------------------------------
780 private bool FClosed() {
781 return m_sqlchars == null;
784 private void CheckIfStreamClosed(string methodname) {
786 throw ADP.StreamClosed(methodname);
788 } // class StreamOnSqlChars
789 } // namespace System.Data.SqlTypes