2 // Mono.Data.Tds.Protocol.Tds.cs
5 // Tim Coleman (tim@timcoleman.com)
6 // Sebastien Pouliot (spouliot@motus.com)
7 // Daniel Morgan (danielmorgan@verizon.net)
8 // Veerapuram Varadhan (vvaradhan@novell.com)
10 // Copyright (C) 2002 Tim Coleman
11 // Portions (C) 2003 Motus Technologies Inc. (http://www.motus.com)
12 // Portions (C) 2003,2005 Daniel Morgan
13 // Portions (C) 2008,2009 Novell Inc.
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using Mono.Security.Protocol.Ntlm;
39 using System.Collections;
40 using System.ComponentModel;
41 using System.Diagnostics;
42 using System.Net.Sockets;
43 using System.Globalization;
44 using System.Security;
46 using System.Runtime.InteropServices;
48 namespace Mono.Data.Tds.Protocol
50 public abstract class Tds
55 TdsVersion tdsVersion;
57 protected internal TdsConnectionParameters connectionParms;
58 protected readonly byte[] NTLMSSP_ID = new byte[] {0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00};
63 string originalDatabase = string.Empty;
64 string databaseProductName;
65 string databaseProductVersion;
66 int databaseMajorVersion;
67 CultureInfo locale = CultureInfo.InvariantCulture;
69 readonly int lifeTime;
70 readonly DateTime created = DateTime.Now;
83 TdsDataRow currentRow;
84 TdsDataColumnCollection columns;
87 ArrayList columnNames;
89 TdsMetaParameterCollection parameters = new TdsMetaParameterCollection ();
98 ArrayList outputParameters = new ArrayList ();
99 protected TdsInternalErrorCollection messages = new TdsInternalErrorCollection ();
101 int recordsAffected = -1;
105 int StreamColumnIndex;
107 bool sequentialAccess;
113 internal int poolStatus = 0;
119 protected string Charset {
120 get { return charset; }
123 protected CultureInfo Locale {
124 get { return locale; }
127 public bool DoneProc {
128 get { return doneProc; }
131 protected string Language {
132 get { return language; }
135 protected ArrayList ColumnNames {
136 get { return columnNames; }
139 public TdsDataRow ColumnValues {
140 get { return currentRow; }
143 internal TdsComm Comm {
147 public string Database {
148 get { return database; }
151 public string DataSource {
152 get { return dataSource; }
155 public virtual bool IsConnected {
156 get { return connected && comm != null && comm.IsConnected (); }
157 set { connected = value; }
160 public bool Pooling {
161 get { return pooling; }
162 set { pooling = value; }
165 public bool MoreResults {
166 get { return moreResults; }
167 set { moreResults = value; }
170 public int PacketSize {
171 get { return packetSize; }
174 public int RecordsAffected {
175 get { return recordsAffected; }
176 set { recordsAffected = value; }
179 public string ServerVersion {
180 get { return databaseProductVersion; }
183 public TdsDataColumnCollection Columns {
184 get { return columns; }
187 public TdsVersion TdsVersion {
188 get { return tdsVersion; }
191 public ArrayList OutputParameters {
192 get { return outputParameters; }
193 set { outputParameters = value; }
196 protected TdsMetaParameterCollection Parameters {
197 get { return parameters; }
198 set { parameters = value; }
201 public bool SequentialAccess {
202 get { return sequentialAccess; }
203 set { sequentialAccess = value; }
206 public byte[] Collation {
207 get {return collation; }
210 public TdsVersion ServerTdsVersion {
212 switch (databaseMajorVersion) {
213 case 4: return TdsVersion.tds42;
214 case 5: return TdsVersion.tds50;
215 case 7: return TdsVersion.tds70;
216 case 8: return TdsVersion.tds80;
217 case 9: return TdsVersion.tds90;
218 case 10: return TdsVersion.tds100;
219 default: return tdsVersion; // return client's version
224 private void SkipRow ()
226 SkipToColumnIndex (Columns.Count);
229 StreamColumnIndex = 0;
231 LoadInProgress = false;
234 private void SkipToColumnIndex (int colIndex)
239 if (colIndex < StreamColumnIndex)
240 throw new Exception ("Cannot Skip to a colindex less than the curr index");
242 while (colIndex != StreamColumnIndex) {
243 TdsColumnType? colType = Columns[StreamColumnIndex].ColumnType;
245 throw new Exception ("Column type unset.");
246 if (!(colType == TdsColumnType.Image ||
247 colType == TdsColumnType.Text ||
248 colType == TdsColumnType.NText)) {
249 GetColumnValue (colType, false, StreamColumnIndex);
250 StreamColumnIndex ++;
254 Comm.Skip (StreamLength);
261 public object GetSequentialColumnValue (int colIndex)
263 if (colIndex < StreamColumnIndex)
264 throw new InvalidOperationException ("Invalid attempt tp read from column ordinal" + colIndex);
269 if (colIndex != StreamColumnIndex)
270 SkipToColumnIndex (colIndex);
272 object o = GetColumnValue (Columns[colIndex].ColumnType, false, colIndex);
277 public long GetSequentialColumnValue (int colIndex, long fieldIndex, byte[] buffer, int bufferIndex, int size)
279 if (colIndex < StreamColumnIndex)
280 throw new InvalidOperationException ("Invalid attempt to read from column ordinal" + colIndex);
282 if (colIndex != StreamColumnIndex)
283 SkipToColumnIndex (colIndex);
285 if (!LoadInProgress) {
286 BeginLoad (Columns[colIndex].ColumnType);
291 return LoadData (fieldIndex, buffer, bufferIndex, size);
292 } catch (IOException ex) {
294 throw new TdsInternalException ("Server closed the connection.", ex);
298 private void BeginLoad (
299 TdsColumnType? colType
308 throw new ArgumentNullException ("colType");
311 case TdsColumnType.Text :
312 case TdsColumnType.NText:
313 case TdsColumnType.Image:
314 if (Comm.GetByte () != 0) {
316 StreamLength = Comm.GetTdsInt ();
318 // use -2 to indicate that we're dealing
323 case TdsColumnType.BigVarChar:
324 case TdsColumnType.BigChar:
325 case TdsColumnType.BigBinary:
326 case TdsColumnType.BigVarBinary:
328 StreamLength = Comm.GetTdsShort ();
330 case TdsColumnType.VarChar :
331 case TdsColumnType.NVarChar :
332 case TdsColumnType.Char:
333 case TdsColumnType.NChar:
334 case TdsColumnType.Binary:
335 case TdsColumnType.VarBinary:
336 StreamLength = Comm.GetTdsShort ();
344 LoadInProgress = true;
347 private void EndLoad()
349 if (StreamLength > 0)
350 Comm.Skip (StreamLength);
354 LoadInProgress = false;
357 private long LoadData (long fieldIndex, byte[] buffer, int bufferIndex, int size)
359 if (StreamLength <= 0)
362 if (fieldIndex < StreamIndex)
363 throw new InvalidOperationException (string.Format (
364 "Attempting to read at dataIndex '{0}' is " +
365 "not allowed as this is less than the " +
366 "current position. You must read from " +
367 "dataIndex '{1}' or greater.",
368 fieldIndex, StreamIndex));
370 if (fieldIndex >= (StreamLength + StreamIndex))
373 // determine number of bytes to skip
374 int skip = (int) (fieldIndex - StreamIndex);
377 // update the current position
378 StreamIndex += (fieldIndex - StreamIndex);
379 // update the remaining length
380 StreamLength -= skip;
382 // Load the reqd amt of bytes
383 int loadlen = (int) ((size > StreamLength) ? StreamLength : size);
384 byte[] arr = Comm.GetBytes (loadlen, true);
386 // update the index and stream length
387 StreamIndex += loadlen + (fieldIndex - StreamIndex);
388 StreamLength -= loadlen;
389 arr.CopyTo (buffer, bufferIndex);
394 #endregion // Properties
398 public event TdsInternalErrorMessageEventHandler TdsErrorMessage;
399 public event TdsInternalInfoMessageEventHandler TdsInfoMessage;
405 public Tds (string dataSource, int port, int packetSize, int timeout, TdsVersion tdsVersion)
406 : this (dataSource, port, packetSize, timeout, 0, tdsVersion)
410 public Tds (string dataSource, int port, int packetSize, int timeout, int lifeTime, TdsVersion tdsVersion)
412 this.tdsVersion = tdsVersion;
413 this.packetSize = packetSize;
414 this.dataSource = dataSource;
415 this.columns = new TdsDataColumnCollection ();
416 this.lifeTime = lifeTime;
418 InitComm (port, timeout);
421 protected virtual void InitComm (int port, int timeout)
423 comm = new TdsComm (dataSource, port, packetSize, timeout, tdsVersion);
426 #endregion // Constructors
428 #region Public Methods
430 internal bool Expired {
434 return DateTime.Now > (created + TimeSpan.FromSeconds (lifeTime));
438 internal protected void InitExec ()
444 // Reset "read" status variables - used in case of SequentialAccess
445 isResultRead = false;
449 StreamColumnIndex = 0;
450 LoadInProgress = false;
452 // Reset more variables
453 queryInProgress = false;
454 cancelsRequested = 0;
455 cancelsProcessed = 0;
456 recordsAffected = -1;
459 outputParameters.Clear ();
462 public void Cancel ()
464 if (queryInProgress) {
465 if (cancelsRequested == cancelsProcessed) {
466 comm.StartPacket (TdsPacketType.Cancel);
469 } catch (IOException ex) {
471 throw new TdsInternalException ("Server closed the connection.", ex);
473 cancelsRequested += 1;
478 public abstract bool Connect (TdsConnectionParameters connectionParameters);
480 public static TdsTimeoutException CreateTimeoutException (string dataSource, string method)
482 string message = "Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.";
483 return new TdsTimeoutException (0, 0, message, -2, method, dataSource, "Mono TdsClient Data Provider", 0);
486 public virtual void Disconnect ()
489 comm.StartPacket (TdsPacketType.Logoff);
490 comm.Append ((byte) 0);
493 // We're closing the socket anyway
499 public virtual bool Reset ()
501 database = originalDatabase;
505 protected virtual bool IsValidRowCount (byte status, byte op)
507 return ((status & (0x10)) != 0) ;
510 public void Execute (string sql)
512 Execute (sql, null, 0, false);
515 public void ExecProc (string sql)
517 ExecProc (sql, null, 0, false);
520 public virtual void Execute (string sql, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
522 ExecuteQuery (sql, timeout, wantResults);
525 public virtual void ExecProc (string sql, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
527 ExecuteQuery (String.Format ("exec {0}", sql), timeout, wantResults);
530 public virtual void ExecPrepared (string sql, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
532 throw new NotSupportedException ();
535 internal void ExecBulkCopyMetaData (int timeout, bool wantResults)
540 CheckForData (timeout);
543 } catch (IOException ex) {
545 throw new TdsInternalException ("Server closed the connection.", ex);
549 internal void ExecBulkCopy (int timeout, bool wantResults)
554 CheckForData (timeout);
557 } catch (IOException ex) {
559 throw new TdsInternalException ("Server closed the connection.", ex);
563 protected void ExecuteQuery (string sql, int timeout, bool wantResults)
567 Comm.StartPacket (TdsPacketType.Query);
571 CheckForData (timeout);
574 } catch (IOException ex) {
576 throw new TdsInternalException ("Server closed the connection.", ex);
580 protected virtual void ExecRPC (string rpcName, TdsMetaParameterCollection parameters,
581 int timeout, bool wantResults)
583 Comm.StartPacket (TdsPacketType.DBRPC);
585 byte [] rpcNameBytes = Comm.Encoder.GetBytes (rpcName);
586 byte rpcNameLength = (byte) rpcNameBytes.Length;
587 ushort mask = 0x0000;
588 ushort packetLength = (ushort) (sizeof (byte) + rpcNameLength +
591 Comm.Append (packetLength);
592 Comm.Append (rpcNameLength);
593 Comm.Append (rpcNameBytes);
598 CheckForData (timeout);
601 } catch (IOException ex) {
603 throw new TdsInternalException ("Server closed the connection.", ex);
607 public bool NextResult ()
609 if (SequentialAccess) {
611 while (NextRow ()) {}
613 isResultRead = false;
619 TdsPacketSubType subType;
622 bool outputParams = false;
625 subType = ProcessSubPacket ();
631 case TdsPacketSubType.ColumnInfo:
632 case TdsPacketSubType.ColumnMetadata:
633 case TdsPacketSubType.RowFormat:
634 byte peek = Comm.Peek ();
635 done = (peek != (byte) TdsPacketSubType.TableName);
636 if (done && doneProc && peek == (byte) TdsPacketSubType.Row) {
641 case TdsPacketSubType.TableName:
644 done = (peek != (byte) TdsPacketSubType.ColumnDetail);
646 case TdsPacketSubType.ColumnDetail:
658 public bool NextRow ()
660 if (SequentialAccess) {
667 TdsPacketSubType subType;
672 subType = ProcessSubPacket ();
674 case TdsPacketSubType.Row:
678 case TdsPacketSubType.Done:
679 case TdsPacketSubType.DoneProc:
680 case TdsPacketSubType.DoneInProc:
690 public virtual string Prepare (string sql, TdsMetaParameterCollection parameters)
692 throw new NotSupportedException ();
695 public void SkipToEnd ()
698 while (NextResult ()) { /* DO NOTHING */ }
699 } catch (IOException ex) {
701 throw new TdsInternalException ("Server closed the connection.", ex);
705 public virtual void Unprepare (string statementId)
707 throw new NotSupportedException ();
710 #endregion // Public Methods
712 #region // Private Methods
714 [MonoTODO ("Is cancel enough, or do we need to drop the connection?")]
715 protected void CheckForData (int timeout)
717 if (timeout > 0 && !comm.Poll (timeout, SelectMode.SelectRead)) {
719 throw CreateTimeoutException (dataSource, "CheckForData()");
723 protected TdsInternalInfoMessageEventArgs CreateTdsInfoMessageEvent (TdsInternalErrorCollection errors)
725 return new TdsInternalInfoMessageEventArgs (errors);
728 protected TdsInternalErrorMessageEventArgs CreateTdsErrorMessageEvent (byte theClass, int lineNumber, string message, int number, string procedure, string server, string source, byte state)
730 return new TdsInternalErrorMessageEventArgs (new TdsInternalError (theClass, lineNumber, message, number, procedure, server, source, state));
733 private Encoding GetEncodingFromColumnCollation (int lcid, int sortId)
736 return TdsCharset.GetEncodingFromSortOrder (sortId);
738 return TdsCharset.GetEncodingFromLCID (lcid);
741 protected object GetColumnValue (
742 TdsColumnType? colType,
745 return GetColumnValue (colType, outParam, -1);
748 private object GetColumnValue (
749 TdsColumnType? colType,
750 bool outParam, int ordinal)
753 object element = null;
755 int lcid = 0, sortId = 0;
758 throw new ArgumentNullException ("colType");
759 if (ordinal > -1 && tdsVersion > TdsVersion.tds70) {
760 lcid = (int) columns[ordinal].LCID;
761 sortId = (int) columns[ordinal].SortOrder;
765 case TdsColumnType.IntN :
768 element = GetIntValue (colType);
770 case TdsColumnType.Int1 :
771 case TdsColumnType.Int2 :
772 case TdsColumnType.Int4 :
773 case TdsColumnType.BigInt :
774 element = GetIntValue (colType);
776 case TdsColumnType.Image :
779 element = GetImageValue ();
781 case TdsColumnType.Text :
782 enc = GetEncodingFromColumnCollation (lcid, sortId);
785 element = GetTextValue (false, enc);
787 case TdsColumnType.NText :
788 enc = GetEncodingFromColumnCollation (lcid, sortId);
791 element = GetTextValue (true, enc);
793 case TdsColumnType.Char :
794 case TdsColumnType.VarChar :
795 enc = GetEncodingFromColumnCollation (lcid, sortId);
798 element = GetStringValue (colType, false, outParam, enc);
800 case TdsColumnType.BigVarBinary :
803 len = comm.GetTdsShort ();
804 element = comm.GetBytes (len, true);
807 case TdsColumnType.BigBinary :
810 len = comm.GetTdsShort ();
811 element = comm.GetBytes (len, true);
814 case TdsColumnType.BigBinary :
817 element = GetBinaryValue ();
819 case TdsColumnType.BigChar :
820 case TdsColumnType.BigVarChar :
821 enc = GetEncodingFromColumnCollation (lcid, sortId);
824 element = GetStringValue (colType, false, outParam, enc);
826 case TdsColumnType.NChar :
827 case TdsColumnType.BigNVarChar :
828 enc = GetEncodingFromColumnCollation (lcid, sortId);
831 element = GetStringValue (colType, true, outParam, enc);
833 case TdsColumnType.NVarChar :
834 enc = GetEncodingFromColumnCollation (lcid, sortId);
837 element = GetStringValue (colType, true, outParam, enc);
839 case TdsColumnType.Real :
840 case TdsColumnType.Float8 :
841 element = GetFloatValue (colType);
843 case TdsColumnType.FloatN :
846 element = GetFloatValue (colType);
848 case TdsColumnType.SmallMoney :
849 case TdsColumnType.Money :
850 element = GetMoneyValue (colType);
852 case TdsColumnType.MoneyN :
855 element = GetMoneyValue (colType);
857 case TdsColumnType.Numeric :
858 case TdsColumnType.Decimal :
863 precision = comm.GetByte ();
864 scale = comm.GetByte ();
867 precision = (byte) columns[ordinal].NumericPrecision;
868 scale = (byte) columns[ordinal].NumericScale;
871 element = GetDecimalValue (precision, scale);
873 // workaround for fact that TDS 7.0 returns
874 // bigint as decimal (19,0), and client code
875 // expects it to be returned as a long
876 if (scale == 0 && precision <= 19 && tdsVersion == TdsVersion.tds70) {
877 if (!(element is System.DBNull))
878 element = Convert.ToInt64 (element);
881 case TdsColumnType.DateTimeN :
884 element = GetDateTimeValue (colType);
886 case TdsColumnType.DateTime4 :
887 case TdsColumnType.DateTime :
888 element = GetDateTimeValue (colType);
890 case TdsColumnType.VarBinary :
891 case TdsColumnType.Binary :
894 element = GetBinaryValue ();
896 case TdsColumnType.BitN :
899 if (comm.GetByte () == 0)
900 element = DBNull.Value;
902 element = (comm.GetByte() != 0);
904 case TdsColumnType.Bit :
905 int columnSize = comm.GetByte ();
906 element = (columnSize != 0);
908 case TdsColumnType.UniqueIdentifier :
909 if (comm.Peek () != 16) { // If it's null, then what to do?
910 /*byte swallowed =*/ comm.GetByte();
911 element = DBNull.Value;
917 len = comm.GetByte () & 0xff;
919 byte[] guidBytes = comm.GetBytes (len, true);
920 if (!BitConverter.IsLittleEndian) {
921 byte[] swappedguidBytes = new byte[len];
922 for (int i = 0; i < 4; i++)
923 swappedguidBytes[i] = guidBytes[4-i-1];
924 for (int i = 4; i < 6; i++)
925 swappedguidBytes[i] = guidBytes[6-(i-4)-1];
926 for (int i = 6; i < 8; i++)
927 swappedguidBytes[i] = guidBytes[8-(i-6)-1];
928 for (int i = 8; i < 16; i++)
929 swappedguidBytes[i] = guidBytes[i];
930 Array.Copy(swappedguidBytes, 0, guidBytes, 0, len);
932 element = new Guid (guidBytes);
935 case TdsColumnType.Variant :
938 element = GetVariantValue();
946 private object GetBinaryValue ()
949 object result = DBNull.Value;
951 if (tdsVersion >= TdsVersion.tds70) {
952 len = comm.GetTdsShort ();
953 if (len != 0xffff && len >= 0)
954 result = comm.GetBytes (len, true);
956 len = (comm.GetByte () & 0xff);
958 result = comm.GetBytes (len, true);
964 private object GetVariantValue ()
966 uint len = (uint)comm.GetTdsInt ();
971 TdsColumnType colType = (TdsColumnType)comm.GetByte ();
973 byte propbytes = comm.GetByte ();
975 // VARIANT_PROPERTIES
976 comm.Skip (propbytes);
978 len -= (uint)propbytes + 2;
982 case TdsColumnType.Int1 :
983 case TdsColumnType.Int2 :
984 case TdsColumnType.Int4 :
985 case TdsColumnType.BigInt :
986 return GetIntValue (colType);
988 // The old code was ignoring variants
989 // and returning null. Should we
990 // throw an exception?
998 private object GetDateTimeValue (
1006 throw new ArgumentNullException ("type");
1008 case TdsColumnType.DateTime4:
1011 case TdsColumnType.DateTime:
1014 case TdsColumnType.DateTimeN:
1015 byte tmp = comm.Peek ();
1016 if (tmp != 0 && tmp != 4 && tmp != 8)
1018 len = comm.GetByte ();
1022 DateTime epoch = new DateTime (1900, 1, 1);
1026 result = epoch.AddDays (comm.GetTdsInt ());
1027 int seconds = comm.GetTdsInt ();
1028 long millis = (long) System.Math.Round (((((long) seconds) % 300L) * 1000L) / 300f);
1029 if (seconds != 0 || millis != 0) {
1030 result = ((DateTime) result).AddSeconds (seconds / 300);
1031 result = ((DateTime) result).AddMilliseconds (millis);
1035 // MSDN says small datetime is stored in 2 bytes as no of days
1036 // *after* 1/1/1900. so, cast to unsigned short
1037 result = epoch.AddDays ((ushort) comm.GetTdsShort ());
1038 short minutes = comm.GetTdsShort ();
1040 result = ((DateTime) result).AddMinutes ((int) minutes);
1043 result = DBNull.Value;
1050 private object GetDecimalValue (byte precision, byte scale)
1052 if (tdsVersion < TdsVersion.tds70)
1053 return GetDecimalValueTds50 (precision, scale);
1055 return GetDecimalValueTds70 (precision, scale);
1058 private object GetDecimalValueTds70 (byte precision, byte scale)
1060 int[] bits = new int[4] {0,0,0,0};
1062 int len = (comm.GetByte() & 0xff) - 1;
1064 return DBNull.Value;
1066 bool positive = (comm.GetByte () == 1);
1068 throw new OverflowException ();
1070 for (int i = 0, index = 0; i < len && i < 16; i += 4, index += 1)
1071 bits[index] = comm.GetTdsInt ();
1074 return new TdsBigDecimal (precision, scale, !positive, bits);
1076 return new Decimal (bits[0], bits[1], bits[2], !positive, scale);
1079 private object GetDecimalValueTds50 (byte precision, byte scale)
1081 int[] bits = new int[4] {0,0,0,0};
1083 int len = (comm.GetByte() & 0xff);
1085 return DBNull.Value;
1087 byte[] dec_bytes=comm.GetBytes(len,false);
1089 byte[] easy=new byte[4];
1091 bool positive = dec_bytes[0]==1;
1094 throw new OverflowException ();
1096 for (int i = 1, index = 0; i < len && i < 16; i +=
1098 for(int j=0; j<4; j++)
1100 easy[j]=dec_bytes[len-
1104 if(!BitConverter.IsLittleEndian)
1105 easy=comm.Swap(easy);
1106 bits[index] = BitConverter.ToInt32(easy,0);
1109 return new TdsBigDecimal (precision,
1110 scale, positive, bits);
1112 return new Decimal(bits[0], bits[1], bits
1113 [2], positive, scale);
1117 private object GetFloatValue (
1118 TdsColumnType? columnType
1121 if (columnType == null)
1122 throw new ArgumentNullException ("columnType");
1125 switch (columnType) {
1126 case TdsColumnType.Real:
1129 case TdsColumnType.Float8:
1132 case TdsColumnType.FloatN:
1133 columnSize = comm.GetByte ();
1137 switch (columnSize) {
1139 return BitConverter.Int64BitsToDouble (comm.GetTdsInt64 ());
1141 return BitConverter.ToSingle (BitConverter.GetBytes (comm.GetTdsInt ()), 0);
1143 return DBNull.Value;
1147 private object GetImageValue ()
1149 byte hasValue = comm.GetByte ();
1152 return DBNull.Value;
1155 int len = comm.GetTdsInt ();
1158 return DBNull.Value;
1160 return (comm.GetBytes (len, true));
1163 private object GetIntValue (
1170 throw new ArgumentNullException ("type");
1172 case TdsColumnType.BigInt :
1175 case TdsColumnType.IntN :
1176 len = comm.GetByte ();
1178 case TdsColumnType.Int4 :
1181 case TdsColumnType.Int2 :
1184 case TdsColumnType.Int1 :
1188 return DBNull.Value;
1193 return (comm.GetTdsInt64 ());
1195 return (comm.GetTdsInt ());
1197 return (comm.GetTdsShort ());
1199 return (comm.GetByte ());
1201 return DBNull.Value;
1205 private object GetMoneyValue (
1212 throw new ArgumentNullException ("type");
1214 case TdsColumnType.SmallMoney :
1215 case TdsColumnType.Money4 :
1218 case TdsColumnType.Money :
1221 case TdsColumnType.MoneyN :
1222 len = comm.GetByte ();
1225 return DBNull.Value;
1230 int val = Comm.GetTdsInt ();
1231 bool negative = val < 0;
1234 return new Decimal (val, 0, 0, negative, 4);
1237 int hi = Comm.GetTdsInt ();
1238 int lo = Comm.GetTdsInt ();
1239 bool negative = hi < 0;
1245 return new Decimal (lo, hi, 0, negative, 4);
1248 return DBNull.Value;
1252 protected object GetStringValue (
1253 TdsColumnType? colType,
1254 bool wideChars, bool outputParam, Encoding encoder)
1256 bool shortLen = false;
1257 Encoding enc = encoder;
1259 if (tdsVersion > TdsVersion.tds70 && outputParam &&
1260 (colType == TdsColumnType.BigChar || colType == TdsColumnType.BigNVarChar ||
1261 colType == TdsColumnType.BigVarChar || colType == TdsColumnType.NChar ||
1262 colType == TdsColumnType.NVarChar)) {
1263 // Read collation for SqlServer 2000 and beyond
1265 collation = Comm.GetBytes (5, true);
1266 enc = TdsCharset.GetEncoding (collation);
1269 shortLen = (tdsVersion >= TdsVersion.tds70) && (wideChars || !outputParam);
1272 int len = shortLen ? comm.GetTdsShort () : (comm.GetByte () & 0xff);
1273 return GetStringValue (wideChars, len, enc);
1276 protected object GetStringValue (bool wideChars, int len, Encoding enc)
1278 if (tdsVersion < TdsVersion.tds70 && len == 0)
1279 return DBNull.Value;
1281 else if (len >= 0) {
1284 result = comm.GetString (len / 2, enc);
1286 result = comm.GetString (len, false, enc);
1287 if (tdsVersion < TdsVersion.tds70 && ((string) result).Equals (" "))
1288 result = string.Empty;
1292 return DBNull.Value;
1295 protected int GetSubPacketLength ()
1297 return comm.GetTdsShort ();
1300 private object GetTextValue (bool wideChars, Encoding encoder)
1302 string result = null;
1303 byte hasValue = comm.GetByte ();
1306 return DBNull.Value;
1308 // 16 Byte TEXTPTR, 8 Byte TIMESTAMP
1311 int len = comm.GetTdsInt ();
1313 //if the len is 0 , then the string can be a '' string
1314 // this method is called only for Text and NText. Hence will
1315 // return a empty string
1317 return string.Empty;
1320 result = comm.GetString (len / 2, encoder);
1322 result = comm.GetString (len, false, encoder);
1325 if ((byte) tdsVersion < (byte) TdsVersion.tds70 && result == " ")
1326 result = string.Empty;
1331 internal bool IsBlobType (TdsColumnType columnType)
1333 return (columnType == TdsColumnType.Text || columnType == TdsColumnType.Image || columnType == TdsColumnType.NText || columnType == TdsColumnType.Variant);
1336 internal bool IsLargeType (TdsColumnType columnType)
1338 return ((byte) columnType > 128);
1341 protected bool IsWideType (TdsColumnType columnType)
1343 switch (columnType) {
1344 case TdsColumnType.NChar:
1345 case TdsColumnType.NText:
1346 case TdsColumnType.NVarChar:
1353 internal static bool IsFixedSizeColumn (TdsColumnType columnType)
1355 switch (columnType) {
1356 case TdsColumnType.Int1 :
1357 case TdsColumnType.Int2 :
1358 case TdsColumnType.Int4 :
1359 case TdsColumnType.BigInt :
1360 case TdsColumnType.Float8 :
1361 case TdsColumnType.DateTime :
1362 case TdsColumnType.Bit :
1363 case TdsColumnType.Money :
1364 case TdsColumnType.Money4 :
1365 case TdsColumnType.SmallMoney :
1366 case TdsColumnType.Real :
1367 case TdsColumnType.DateTime4 :
1369 case TdsColumnType.Decimal:
1370 case TdsColumnType.Numeric:
1378 protected void LoadRow ()
1380 if (SequentialAccess) {
1384 isResultRead = true;
1388 currentRow = new TdsDataRow ();
1391 foreach (TdsDataColumn column in columns) {
1392 object o = GetColumnValue (column.ColumnType, false, i);
1395 outputParameters.Add (o);
1397 if (o is TdsBigDecimal && currentRow.BigDecimalIndex < 0)
1398 currentRow.BigDecimalIndex = i;
1403 internal static int LookupBufferSize (TdsColumnType columnType)
1405 switch (columnType) {
1406 case TdsColumnType.Int1 :
1407 case TdsColumnType.Bit :
1409 case TdsColumnType.Int2 :
1411 case TdsColumnType.Int4 :
1412 case TdsColumnType.Real :
1413 case TdsColumnType.DateTime4 :
1414 case TdsColumnType.Money4 :
1415 case TdsColumnType.SmallMoney :
1417 case TdsColumnType.Float8 :
1418 case TdsColumnType.DateTime :
1419 case TdsColumnType.Money :
1420 case TdsColumnType.BigInt :
1427 protected internal int ProcessAuthentication ()
1429 int pdu_size = Comm.GetTdsShort ();
1430 byte[] msg2 = Comm.GetBytes (pdu_size, true);
1432 Type2Message t2 = new Type2Message (msg2);
1433 // 0x0001 Negotiate Unicode
1434 // 0x0200 Negotiate NTLM
1435 // 0x8000 Negotiate Always Sign
1437 Type3Message t3 = new Type3Message (t2);
1439 t3.Domain = this.connectionParms.DefaultDomain;
1440 t3.Host = this.connectionParms.Hostname;
1441 t3.Username = this.connectionParms.User;
1442 t3.Password = GetPlainPassword(this.connectionParms.Password);
1444 Comm.StartPacket (TdsPacketType.SspAuth); // 0x11
1445 Comm.Append (t3.GetBytes ());
1448 } catch (IOException ex) {
1450 throw new TdsInternalException ("Server closed the connection.", ex);
1452 return 1; // TDS_SUCCEED
1455 protected void ProcessColumnDetail ()
1457 int len = GetSubPacketLength ();
1458 byte[] values = new byte[3];
1459 int columnNameLength;
1460 string baseColumnName = String.Empty;
1463 while (position < len) {
1464 for (int j = 0; j < 3; j += 1)
1465 values[j] = comm.GetByte ();
1468 bool isAlias = ((values[2] & (byte) TdsColumnStatus.Rename) != 0);
1470 if (tdsVersion >= TdsVersion.tds70) {
1471 columnNameLength = comm.GetByte ();
1472 position += 2 * columnNameLength + 1;
1475 columnNameLength = comm.GetByte ();
1476 position += columnNameLength + 1;
1478 baseColumnName = comm.GetString (columnNameLength);
1481 byte index = (byte) (values[0] - (byte) 1);
1482 byte tableIndex = (byte) (values[1] - (byte) 1);
1483 bool isExpression = ((values[2] & (byte) TdsColumnStatus.IsExpression) != 0);
1485 TdsDataColumn column = columns [index];
1486 column.IsHidden = ((values[2] & (byte) TdsColumnStatus.Hidden) != 0);
1487 column.IsExpression = isExpression;
1488 column.IsKey = ((values[2] & (byte) TdsColumnStatus.IsKey) != 0);
1489 column.IsAliased = isAlias;
1490 column.BaseColumnName = ((isAlias) ? baseColumnName : null);
1491 column.BaseTableName = ((!isExpression) ? (string) tableNames [tableIndex] : null);
1495 protected abstract void ProcessColumnInfo ();
1497 protected void ProcessColumnNames ()
1499 columnNames = new ArrayList ();
1501 int totalLength = comm.GetTdsShort ();
1505 while (bytesRead < totalLength) {
1506 int columnNameLength = comm.GetByte ();
1507 string columnName = comm.GetString (columnNameLength);
1508 bytesRead = bytesRead + 1 + columnNameLength;
1509 columnNames.Add (columnName);
1514 [MonoTODO ("Make sure counting works right, especially with multiple resultsets.")]
1515 protected void ProcessEndToken (TdsPacketSubType type)
1517 byte status = Comm.GetByte ();
1519 byte op = comm.GetByte ();
1522 int rowCount = comm.GetTdsInt ();
1523 bool validRowCount = IsValidRowCount (status,op);
1524 moreResults = ((status & 0x01) != 0);
1525 bool cancelled = ((status & 0x20) != 0);
1528 case TdsPacketSubType.DoneProc:
1530 goto case TdsPacketSubType.Done;
1531 case TdsPacketSubType.Done:
1532 case TdsPacketSubType.DoneInProc:
1533 if (validRowCount) {
1534 if (recordsAffected == -1)
1535 recordsAffected = rowCount;
1537 recordsAffected += rowCount;
1543 queryInProgress = false;
1545 cancelsProcessed += 1;
1546 if (messages.Count > 0 && !moreResults)
1547 OnTdsInfoMessage (CreateTdsInfoMessageEvent (messages));
1550 protected void ProcessEnvironmentChange ()
1552 // VARADHAN: TDS 8 Debugging
1553 //Console.WriteLine ("In ProcessEnvironmentChange... entry");
1554 int len = GetSubPacketLength ();
1555 TdsEnvPacketSubType type = (TdsEnvPacketSubType) comm.GetByte ();
1559 case TdsEnvPacketSubType.BlockSize :
1561 cLen = comm.GetByte ();
1562 blockSize = comm.GetString (cLen);
1564 if (tdsVersion >= TdsVersion.tds70)
1565 comm.Skip (len - 2 - cLen * 2);
1567 comm.Skip (len - 2 - cLen);
1569 packetSize = Int32.Parse (blockSize);
1570 comm.ResizeOutBuf (packetSize);
1572 case TdsEnvPacketSubType.CharSet :
1573 cLen = comm.GetByte ();
1574 if (tdsVersion == TdsVersion.tds70) {
1575 SetCharset (comm.GetString (cLen));
1576 comm.Skip (len - 2 - cLen * 2);
1579 SetCharset (comm.GetString (cLen));
1580 comm.Skip (len - 2 - cLen);
1584 case TdsEnvPacketSubType.Locale :
1585 cLen = comm.GetByte ();
1587 if (tdsVersion >= TdsVersion.tds70) {
1588 lcid = (int) Convert.ChangeType (comm.GetString (cLen), typeof (int));
1589 comm.Skip (len - 2 - cLen * 2);
1592 lcid = (int) Convert.ChangeType (comm.GetString (cLen), typeof (int));
1593 comm.Skip (len - 2 - cLen);
1595 locale = new CultureInfo (lcid);
1597 case TdsEnvPacketSubType.Database :
1598 cLen = comm.GetByte ();
1599 string newDB = comm.GetString (cLen);
1600 cLen = comm.GetByte () & 0xff;
1601 comm.GetString (cLen);
1602 if (originalDatabase == string.Empty)
1603 originalDatabase = newDB;
1606 case TdsEnvPacketSubType.CollationInfo:
1607 //Console.WriteLine ("ProcessEnvironmentChange::Got collation info");
1608 cLen = comm.GetByte ();
1609 collation = comm.GetBytes (cLen, true);
1610 lcid = TdsCollation.LCID (collation);
1611 locale = new CultureInfo (lcid);
1612 SetCharset (TdsCharset.GetEncoding (collation));
1616 comm.Skip (len - 1);
1619 // VARADHAN: TDS 8 Debugging
1620 //Console.WriteLine ("In ProcessEnvironmentChange... exit");
1623 protected void ProcessLoginAck ()
1625 uint srvVersion = 0;
1626 GetSubPacketLength ();
1628 //Console.WriteLine ("ProcessLoginAck: B4 tdsVersion:{0}", tdsVersion);
1629 // Valid only for a Login7 request
1630 if (tdsVersion >= TdsVersion.tds70) {
1632 srvVersion = (uint)comm.GetTdsInt ();
1634 //Console.WriteLine ("srvVersion: {0}", srvVersion);
1635 switch (srvVersion) {
1637 tdsVersion = TdsVersion.tds70;
1640 tdsVersion = TdsVersion.tds80;
1643 tdsVersion = TdsVersion.tds81;
1646 tdsVersion = TdsVersion.tds90;
1649 //Console.WriteLine ("ProcessLoginAck: after tdsVersion:{0}", tdsVersion);
1652 if (tdsVersion >= TdsVersion.tds70) {
1653 int nameLength = comm.GetByte ();
1654 databaseProductName = comm.GetString (nameLength);
1655 databaseMajorVersion = comm.GetByte ();
1656 databaseProductVersion = String.Format ("{0}.{1}.{2}", databaseMajorVersion.ToString("00"),
1657 comm.GetByte ().ToString("00"),
1658 (256 * comm.GetByte () + comm.GetByte ()).ToString("0000"));
1661 short nameLength = comm.GetByte ();
1662 databaseProductName = comm.GetString (nameLength);
1664 databaseMajorVersion = comm.GetByte ();
1665 databaseProductVersion = String.Format ("{0}.{1}", databaseMajorVersion, comm.GetByte ());
1669 if (databaseProductName.Length > 1 && -1 != databaseProductName.IndexOf ('\0')) {
1670 int last = databaseProductName.IndexOf ('\0');
1671 databaseProductName = databaseProductName.Substring (0, last);
1675 //Console.WriteLine ("databaseProductVersion:{0}", databaseProductVersion);
1678 protected void OnTdsErrorMessage (TdsInternalErrorMessageEventArgs e)
1680 if (TdsErrorMessage != null)
1681 TdsErrorMessage (this, e);
1684 protected void OnTdsInfoMessage (TdsInternalInfoMessageEventArgs e)
1686 if (TdsInfoMessage != null)
1687 TdsInfoMessage (this, e);
1691 protected void ProcessMessage (TdsPacketSubType subType)
1693 GetSubPacketLength ();
1695 int number = comm.GetTdsInt ();
1696 byte state = comm.GetByte ();
1697 byte theClass = comm.GetByte ();
1703 bool isError = false;
1705 if (subType == TdsPacketSubType.EED) {
1706 isError = (theClass > 10);
1707 comm.Skip (comm.GetByte ()); // SQL State
1708 comm.Skip (1); // Status
1709 comm.Skip (2); // TranState
1711 isError = (subType == TdsPacketSubType.Error);
1713 message = comm.GetString (comm.GetTdsShort ());
1714 server = comm.GetString (comm.GetByte ());
1715 procedure = comm.GetString (comm.GetByte ());
1716 lineNumber = comm.GetByte ();
1718 source = String.Empty; // FIXME
1721 OnTdsErrorMessage (CreateTdsErrorMessageEvent (theClass, lineNumber, message, number, procedure, server, source, state));
1723 messages.Add (new TdsInternalError (theClass, lineNumber, message, number, procedure, server, source, state));
1726 protected virtual void ProcessOutputParam ()
1728 GetSubPacketLength ();
1729 /*string paramName = */comm.GetString (comm.GetByte () & 0xff);
1732 TdsColumnType colType = (TdsColumnType) comm.GetByte ();
1733 object value = GetColumnValue (colType, true);
1734 outputParameters.Add (value);
1737 protected void ProcessDynamic ()
1740 /*byte type =*/ Comm.GetByte ();
1741 /*byte status =*/ Comm.GetByte ();
1742 /*string id =*/ Comm.GetString (Comm.GetByte ());
1745 protected virtual TdsPacketSubType ProcessSubPacket ()
1747 // VARADHAN: TDS 8 Debugging
1748 // Console.WriteLine ("In ProcessSubPacket... entry");
1750 TdsPacketSubType subType = (TdsPacketSubType) comm.GetByte ();
1752 // VARADHAN: TDS 8 Debugging
1753 //Console.WriteLine ("Subpacket type: {0}", subType);
1755 case TdsPacketSubType.Dynamic2:
1756 comm.Skip (comm.GetTdsInt ());
1758 case TdsPacketSubType.AltName:
1759 case TdsPacketSubType.AltFormat:
1760 case TdsPacketSubType.Capability:
1761 case TdsPacketSubType.ParamFormat:
1762 comm.Skip (comm.GetTdsShort ());
1764 case TdsPacketSubType.Dynamic:
1767 case TdsPacketSubType.EnvironmentChange:
1768 ProcessEnvironmentChange ();
1770 case TdsPacketSubType.Info: // TDS 4.2/7.0
1771 case TdsPacketSubType.EED: // TDS 5.0
1772 case TdsPacketSubType.Error: // TDS 4.2/7.0
1773 ProcessMessage (subType);
1775 case TdsPacketSubType.Param:
1776 ProcessOutputParam ();
1778 case TdsPacketSubType.LoginAck:
1781 case TdsPacketSubType.Authentication: // TDS 7.0
1782 ProcessAuthentication ();
1784 case TdsPacketSubType.ReturnStatus :
1785 ProcessReturnStatus ();
1787 case TdsPacketSubType.ProcId:
1790 case TdsPacketSubType.Done:
1791 case TdsPacketSubType.DoneProc:
1792 case TdsPacketSubType.DoneInProc:
1793 ProcessEndToken (subType);
1795 case TdsPacketSubType.ColumnName:
1797 ProcessColumnNames ();
1799 case TdsPacketSubType.ColumnInfo: // TDS 4.2
1800 case TdsPacketSubType.ColumnMetadata: // TDS 7.0
1801 case TdsPacketSubType.RowFormat: // TDS 5.0
1803 ProcessColumnInfo ();
1805 case TdsPacketSubType.ColumnDetail:
1806 ProcessColumnDetail ();
1808 case TdsPacketSubType.TableName:
1809 ProcessTableName ();
1811 case TdsPacketSubType.ColumnOrder:
1812 comm.Skip (comm.GetTdsShort ());
1814 case TdsPacketSubType.Control:
1815 comm.Skip (comm.GetTdsShort ());
1817 case TdsPacketSubType.Row:
1822 // VARADHAN: TDS 8 Debugging
1823 //Console.WriteLine ("In ProcessSubPacket... exit");
1827 protected void ProcessTableName ()
1829 tableNames = new ArrayList ();
1830 int totalLength = comm.GetTdsShort ();
1834 while (position < totalLength) {
1835 if (tdsVersion >= TdsVersion.tds70) {
1836 len = comm.GetTdsShort ();
1837 position += 2 * (len + 1);
1840 len = comm.GetByte ();
1841 position += len + 1;
1843 tableNames.Add (comm.GetString (len));
1847 protected void SetCharset (Encoding encoder)
1849 comm.Encoder = encoder;
1852 protected void SetCharset (string charset)
1854 if (charset == null || charset.Length > 30)
1857 if (this.charset != null && this.charset == charset)
1860 if (charset.StartsWith ("cp")) {
1861 encoder = Encoding.GetEncoding (Int32.Parse (charset.Substring (2)));
1862 this.charset = charset;
1865 encoder = Encoding.GetEncoding ("iso-8859-1");
1866 this.charset = "iso_1";
1868 SetCharset (encoder);
1871 protected void SetLanguage (string language)
1873 if (language == null || language.Length > 30)
1874 language = "us_english";
1876 this.language = language;
1879 protected virtual void ProcessReturnStatus ()
1884 public static string GetPlainPassword(SecureString secPass)
1886 IntPtr plainString = IntPtr.Zero;
1889 plainString = Marshal.SecureStringToGlobalAllocUnicode(secPass);
1890 return Marshal.PtrToStringUni(plainString);
1894 Marshal.ZeroFreeGlobalAllocUnicode(plainString);
1898 #endregion // Private Methods
1900 #region asynchronous methods
1901 protected IAsyncResult BeginExecuteQueryInternal (string sql, bool wantResults,
1902 AsyncCallback callback, object state)
1906 TdsAsyncResult ar = new TdsAsyncResult (callback, state);
1907 ar.TdsAsyncState.WantResults = wantResults;
1909 Comm.StartPacket (TdsPacketType.Query);
1913 Comm.BeginReadPacket (new AsyncCallback(OnBeginExecuteQueryCallback),
1915 } catch (IOException ex) {
1917 throw new TdsInternalException ("Server closed the connection.", ex);
1923 protected void EndExecuteQueryInternal (IAsyncResult ar)
1925 if (!ar.IsCompleted)
1926 ar.AsyncWaitHandle.WaitOne ();
1927 TdsAsyncResult result = (TdsAsyncResult) ar;
1928 if (result.IsCompletedWithException)
1929 throw result.Exception;
1932 protected void OnBeginExecuteQueryCallback (IAsyncResult ar)
1934 TdsAsyncResult result = (TdsAsyncResult) ar.AsyncState;
1935 TdsAsyncState tdsState = (TdsAsyncState) result.TdsAsyncState;
1938 Comm.EndReadPacket (ar);
1939 if (!tdsState.WantResults)
1941 } catch (Exception e) {
1942 result.MarkComplete (e);
1945 result.MarkComplete ();
1949 public virtual IAsyncResult BeginExecuteNonQuery (string sql,
1950 TdsMetaParameterCollection parameters,
1951 AsyncCallback callback,
1954 // abstract, kept to be backward compatiable.
1955 throw new NotImplementedException ("should not be called!");
1958 public virtual void EndExecuteNonQuery (IAsyncResult ar)
1961 throw new NotImplementedException ("should not be called!");
1964 public virtual IAsyncResult BeginExecuteQuery (string sql,
1965 TdsMetaParameterCollection parameters,
1966 AsyncCallback callback,
1969 // abstract, kept to be backward compatiable.
1970 throw new NotImplementedException ("should not be called!");
1973 public virtual void EndExecuteQuery (IAsyncResult ar)
1976 throw new NotImplementedException ("should not be called!");
1979 public virtual IAsyncResult BeginExecuteProcedure (string prolog,
1983 TdsMetaParameterCollection parameters,
1984 AsyncCallback callback,
1987 throw new NotImplementedException ("should not be called!");
1990 public virtual void EndExecuteProcedure (IAsyncResult ar)
1993 throw new NotImplementedException ("should not be called!");
1996 public void WaitFor (IAsyncResult ar)
1998 if (! ar.IsCompleted)
1999 ar.AsyncWaitHandle.WaitOne ();
2002 public void CheckAndThrowException (IAsyncResult ar)
2004 TdsAsyncResult result = (TdsAsyncResult) ar;
2005 if (result.IsCompleted && result.IsCompletedWithException)
2006 throw result.Exception;
2009 #endregion // asynchronous methods