2009-01-14 Gonzalo Paniagua Javier <gonzalo@novell.com>
[mono.git] / mcs / class / Mono.Data.Tds / Mono.Data.Tds.Protocol / Tds70.cs
1 //
2 // Mono.Data.Tds.Protocol.Tds70.cs
3 //
4 // Author:
5 //   Tim Coleman (tim@timcoleman.com)
6 //   Diego Caravana (diego@toth.it)
7 //   Sebastien Pouliot (sebastien@ximian.com)
8 //   Daniel Morgan (danielmorgan@verizon.net)
9 //   Gert Driesen (drieseng@users.sourceforge.net)
10 //
11 // Copyright (C) 2002 Tim Coleman
12 // Portions (C) 2003 Motus Technologies Inc. (http://www.motus.com)
13 // Portions (C) 2003 Daniel Morgan
14 //
15 //
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:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
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.
34 //
35
36 using System;
37 using System.Globalization;
38 using System.Text;
39
40 using Mono.Security.Protocol.Ntlm;
41
42 namespace Mono.Data.Tds.Protocol
43 {
44         public sealed class Tds70 : Tds
45         {
46                 #region Fields
47
48                 public readonly static TdsVersion Version = TdsVersion.tds70;
49                 static readonly decimal SMALLMONEY_MIN = -214748.3648m;
50                 static readonly decimal SMALLMONEY_MAX = 214748.3647m;
51
52                 #endregion // Fields
53
54                 #region Constructors
55
56                 public Tds70 (string server, int port)
57                         : this (server, port, 512, 15)
58                 {
59                 }
60
61                 public Tds70 (string server, int port, int packetSize, int timeout)
62                         : base (server, port, packetSize, timeout, Version)
63                 {
64                 }
65
66                 #endregion // Constructors
67
68                 #region Methods
69
70                 private string BuildExec (string sql)
71                 {
72                         string esql = sql.Replace ("'", "''"); // escape single quote
73                         if (Parameters != null && Parameters.Count > 0)
74                                 return BuildProcedureCall (String.Format ("sp_executesql N'{0}', N'{1}', ", esql, BuildPreparedParameters ()));
75                         else
76                                 return BuildProcedureCall (String.Format ("sp_executesql N'{0}'", esql));
77                 }
78
79                 private string BuildParameters ()
80                 {
81                         if (Parameters == null || Parameters.Count == 0)
82                                 return String.Empty;
83
84                         StringBuilder result = new StringBuilder ();
85                         foreach (TdsMetaParameter p in Parameters) {
86                                 string parameterName = p.ParameterName;
87                                 if (parameterName [0] == '@') {
88                                         parameterName = parameterName.Substring (1);
89                                 }
90                                 if (p.Direction != TdsParameterDirection.ReturnValue) {
91                                         if (result.Length > 0)
92                                                 result.Append (", ");
93                                         if (p.Direction == TdsParameterDirection.InputOutput)
94                                                 result.AppendFormat ("@{0}={0} output", parameterName);
95                                         else
96                                                 result.Append (FormatParameter (p));
97                                 }
98                         }
99                         return result.ToString ();
100                 }
101
102                 private string BuildPreparedParameters ()
103                 {
104                         StringBuilder parms = new StringBuilder ();
105                         foreach (TdsMetaParameter p in Parameters) {
106                                 if (parms.Length > 0)
107                                         parms.Append (", ");
108                                 parms.Append (p.Prepare ());
109                                 if (p.Direction == TdsParameterDirection.Output)
110                                         parms.Append (" output");
111                         }
112                         return parms.ToString ();
113                 }
114
115                 private string BuildPreparedQuery (string id)
116                 {
117                         return BuildProcedureCall (String.Format ("sp_execute {0},", id));
118                 }
119
120                 private string BuildProcedureCall (string procedure)
121                 {
122                         string exec = String.Empty;
123
124                         StringBuilder declare = new StringBuilder ();
125                         StringBuilder select = new StringBuilder ();
126                         StringBuilder set = new StringBuilder ();
127                         
128                         int count = 0;
129                         if (Parameters != null) {
130                                 foreach (TdsMetaParameter p in Parameters) {
131                                         string parameterName = p.ParameterName;
132                                         if (parameterName [0] == '@') {
133                                                 parameterName = parameterName.Substring (1);
134                                         }
135
136                                         if (p.Direction != TdsParameterDirection.Input) {
137                                                 if (count == 0)
138                                                         select.Append ("select ");
139                                                 else
140                                                         select.Append (", ");
141                                                 select.Append ("@" + parameterName);
142                                                         
143                                                 declare.Append (String.Format ("declare {0}\n", p.Prepare ()));
144
145                                                 if (p.Direction != TdsParameterDirection.ReturnValue) {
146                                                         if (p.Direction == TdsParameterDirection.InputOutput)
147                                                                 set.Append (String.Format ("set {0}\n", FormatParameter(p)));
148                                                         else
149                                                                 set.Append (String.Format ("set @{0}=NULL\n", parameterName));
150                                                 }
151                                         
152                                                 count++;
153                                         }
154                                         if (p.Direction == TdsParameterDirection.ReturnValue)
155                                                 exec = "@" + parameterName + "=";
156                                 }
157                         }
158                         exec = "exec " + exec;
159
160                         return String.Format ("{0}{1}{2}{3} {4}\n{5}",
161                                 declare.ToString (), set.ToString (), exec,
162                                 procedure, BuildParameters (), select.ToString ());
163                 }
164
165                 public override bool Connect (TdsConnectionParameters connectionParameters)
166                 {
167                         if (IsConnected)
168                                 throw new InvalidOperationException ("The connection is already open.");
169         
170                         connectionParms = connectionParameters;
171
172                         SetLanguage (connectionParameters.Language);
173                         SetCharset ("utf-8");
174                 
175                         byte[] empty = new byte[0];
176                         short authLen = 0;
177                         byte pad = (byte) 0;
178                         
179                         byte[] domainMagic = { 6, 0x7d, 0x0f, 0xfd, 0xff, 0x0, 0x0, 0x0,
180                                                                         0x0, 0xe0, 0x83, 0x0, 0x0,
181                                                                         0x68, 0x01, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00 };
182                         byte[] sqlserverMagic = { 6, 0x0, 0x0, 0x0,
183                                                                                 0x0, 0x0, 0x0, 0x0,
184                                                                                 0x0, 0xe0, 0x03, 0x0,
185                                                                                 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
186                                                                                 0x0, 0x0, 0x0 };
187                         byte[] magic = null;
188                         
189                         if (connectionParameters.DomainLogin)
190                                 magic = domainMagic;
191                         else
192                                 magic = sqlserverMagic;
193                         
194                         string username = connectionParameters.User;
195                         string domain = null;
196
197                         int idx = username.IndexOf ("\\");
198                         if (idx != -1) {
199                                 domain = username.Substring (0, idx);
200                                 username = username.Substring (idx + 1);
201
202                                 connectionParameters.DefaultDomain = domain;
203                                 connectionParameters.User = username;
204                         } else {
205                                 domain = Environment.UserDomainName;
206                                 connectionParameters.DefaultDomain = domain;
207                         }
208
209                         short partialPacketSize = (short) (86 + (
210                                 connectionParameters.Hostname.Length +
211                                 connectionParameters.ApplicationName.Length +
212                                 DataSource.Length +
213                                 connectionParameters.LibraryName.Length +
214                                 Language.Length +
215                                 connectionParameters.Database.Length +
216                                 connectionParameters.AttachDBFileName.Length) * 2);
217
218                         if (connectionParameters.DomainLogin) {
219                                 authLen = ((short) (32 + (connectionParameters.Hostname.Length +
220                                         domain.Length)));
221                                 partialPacketSize += authLen;
222                         } else
223                                 partialPacketSize += ((short) ((username.Length + connectionParameters.Password.Length) * 2));
224                         
225                         int totalPacketSize = partialPacketSize;
226                         
227                         Comm.StartPacket (TdsPacketType.Logon70);
228                         
229                         Comm.Append (totalPacketSize);
230
231                         //Comm.Append (empty, 3, pad);
232                         byte[] version = {0x00, 0x0, 0x0, 0x70};
233                         Comm.Append (version); // TDS Version 7
234                         Comm.Append ((int)this.PacketSize); // Set the Block Size
235                         Comm.Append (empty, 3, pad);
236                         Comm.Append (magic);
237
238                         short curPos = 86;
239
240                         // Hostname
241                         Comm.Append (curPos);
242                         Comm.Append ((short) connectionParameters.Hostname.Length);
243                         curPos += (short) (connectionParameters.Hostname.Length * 2);
244
245                         if (connectionParameters.DomainLogin) {
246                                 Comm.Append((short)0);
247                                 Comm.Append((short)0);
248                                 Comm.Append((short)0);
249                                 Comm.Append((short)0);
250                         } else {
251                                 // Username
252                                 Comm.Append (curPos);
253                                 Comm.Append ((short) username.Length);
254                                 curPos += ((short) (username.Length * 2));
255
256                                 // Password
257                                 Comm.Append (curPos);
258                                 Comm.Append ((short) connectionParameters.Password.Length);
259                                 curPos += (short) (connectionParameters.Password.Length * 2);
260                         }
261
262                         // AppName
263                         Comm.Append (curPos);
264                         Comm.Append ((short) connectionParameters.ApplicationName.Length);
265                         curPos += (short) (connectionParameters.ApplicationName.Length * 2);
266
267                         // Server Name
268                         Comm.Append (curPos);
269                         Comm.Append ((short) DataSource.Length);
270                         curPos += (short) (DataSource.Length * 2);
271
272                         // Unknown
273                         Comm.Append ((short) curPos);
274                         Comm.Append ((short) 0);
275
276                         // Library Name
277                         Comm.Append (curPos);
278                         Comm.Append ((short) connectionParameters.LibraryName.Length);
279                         curPos += (short) (connectionParameters.LibraryName.Length * 2);
280
281                         // Language
282                         Comm.Append (curPos);
283                         Comm.Append ((short) Language.Length);
284                         curPos += (short) (Language.Length * 2);
285
286                         // Database
287                         Comm.Append (curPos);
288                         Comm.Append ((short) connectionParameters.Database.Length);
289                         curPos += (short) (connectionParameters.Database.Length * 2);
290
291                         // MAC Address
292                         Comm.Append((byte) 0);
293                         Comm.Append((byte) 0);
294                         Comm.Append((byte) 0);
295                         Comm.Append((byte) 0);
296                         Comm.Append((byte) 0);
297                         Comm.Append((byte) 0);
298
299                         // Authentication Stuff
300                         Comm.Append ((short) curPos);
301                         if (connectionParameters.DomainLogin) {
302                                 Comm.Append ((short) authLen);
303                                 curPos += (short) authLen;
304                         } else
305                                 Comm.Append ((short) 0);
306                         
307                         // Unknown
308                         Comm.Append (curPos);
309                         Comm.Append ((short)( connectionParameters.AttachDBFileName.Length));
310                         curPos += (short)(connectionParameters.AttachDBFileName.Length*2);
311                         
312                         // Connection Parameters
313                         Comm.Append (connectionParameters.Hostname);
314                         if (!connectionParameters.DomainLogin) {
315                                 // SQL Server Authentication
316                                 Comm.Append (connectionParameters.User);
317                                 string scrambledPwd = EncryptPassword (connectionParameters.Password);
318                                 Comm.Append (scrambledPwd);
319                         }
320                         Comm.Append (connectionParameters.ApplicationName);
321                         Comm.Append (DataSource);
322                         Comm.Append (connectionParameters.LibraryName);
323                         Comm.Append (Language);
324                         Comm.Append (connectionParameters.Database);
325
326                         if (connectionParameters.DomainLogin) {
327                                 // the rest of the packet is NTLMSSP authentication
328                                 Type1Message msg = new Type1Message ();
329                                 msg.Domain = domain;
330                                 msg.Host = connectionParameters.Hostname;
331                                 msg.Flags = NtlmFlags.NegotiateUnicode |
332                                         NtlmFlags.NegotiateNtlm |
333                                         NtlmFlags.NegotiateDomainSupplied |
334                                         NtlmFlags.NegotiateWorkstationSupplied |
335                                         NtlmFlags.NegotiateAlwaysSign; // 0xb201
336                                 Comm.Append (msg.GetBytes ());
337                         }
338
339                         Comm.Append (connectionParameters.AttachDBFileName);
340                         Comm.SendPacket ();
341                         MoreResults = true;
342                         SkipToEnd ();
343                         
344                         return IsConnected;
345                 }
346
347                 private static string EncryptPassword (string pass)
348                 {
349                         int xormask = 0x5a5a;
350                         int len = pass.Length;
351                         char[] chars = new char[len];
352
353                         for (int i = 0; i < len; ++i) {
354                                 int c = ((int) (pass[i])) ^ xormask;
355                                 int m1 = (c >> 4) & 0x0f0f;
356                                 int m2 = (c << 4) & 0xf0f0;
357                                 chars[i] = (char) (m1 | m2);
358                         }
359
360                         return new String (chars);
361                 }
362
363                 public override bool Reset ()
364                 {
365                         // Check validity of the connection - a false removes
366                         // the connection from the pool
367                         // NOTE: MS implementation will throw a connection-reset error as it will
368                         // try to use the same connection
369                         if (!Comm.IsConnected ())
370                                 return false;
371
372                         // Set "reset-connection" bit for the next message packet
373                         Comm.ResetConnection = true;
374
375                         return true;
376                 }
377
378                 public override void ExecPrepared (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
379                 {
380                         Parameters = parameters;
381                         ExecuteQuery (BuildPreparedQuery (commandText), timeout, wantResults);
382                 }
383                         
384                 public override void ExecProc (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
385                 {
386                         Parameters = parameters;
387                         ExecRPC (commandText, parameters, timeout, wantResults);
388                 }
389
390                 protected override void ExecRPC (string rpcName, TdsMetaParameterCollection parameters, 
391                                                  int timeout, bool wantResults)
392                 {
393                         // clean up
394                         InitExec ();
395                         Comm.StartPacket (TdsPacketType.RPC);
396
397                         Comm.Append ( (short) rpcName.Length);
398                         Comm.Append (rpcName);
399                         Comm.Append ( (short) 0); //no meta data
400                         if (parameters != null) {
401                                 foreach (TdsMetaParameter param in parameters) {
402                                         if (param.Direction == TdsParameterDirection.ReturnValue) 
403                                                 continue;
404                                         string pname = param.ParameterName;
405                                         if (pname != null && pname.Length > 0 && pname [0] == '@') {
406                                                 Comm.Append ( (byte) pname.Length);
407                                                 Comm.Append (pname);
408                                         } else {
409                                                 Comm.Append ( (byte) (pname.Length + 1));
410                                                 Comm.Append ("@" + pname);
411                                         }
412                                         short status = 0; // unused
413                                         if (param.Direction != TdsParameterDirection.Input)
414                                                 status |= 0x01; // output
415                                         Comm.Append ( (byte) status);
416                                         WriteParameterInfo (param);
417                                 }
418                         }
419                         Comm.SendPacket ();
420                         CheckForData (timeout);
421                         if (!wantResults)
422                                 SkipToEnd ();
423                 }
424
425                 private void WriteParameterInfo (TdsMetaParameter param)
426                 {
427                         /*
428                         Ms.net send non-nullable datatypes as nullable and allows setting null values
429                         to int/float etc.. So, using Nullable form of type for all data
430                         */
431                         param.IsNullable = true;
432                         TdsColumnType colType = param.GetMetaType ();
433                         param.IsNullable = false;
434
435                         Comm.Append ((byte)colType); // type
436
437                         int size = param.Size;
438                         if (size == 0)
439                                 size = param.GetActualSize ();
440
441                         /*
442                           If column type is SqlDbType.NVarChar the size of parameter is multiplied by 2
443                           FIXME: Need to check for other types
444                          */
445                         if (colType == TdsColumnType.BigNVarChar)
446                                 size <<= 1;
447                         if (IsLargeType (colType))
448                                 Comm.Append ((short)size); // Parameter size passed in SqlParameter
449                         else if (IsBlobType (colType))
450                                 Comm.Append (size); // Parameter size passed in SqlParameter
451                         else
452                                 Comm.Append ((byte)size);
453
454                         // Precision and Scale are non-zero for only decimal/numeric
455                         if ( param.TypeName == "decimal" || param.TypeName == "numeric") {
456                                 Comm.Append ((param.Precision !=0 ) ? param.Precision : (byte) 28);
457                                 Comm.Append (param.Scale);
458                         }
459
460                         size = param.GetActualSize ();
461                         if (IsLargeType (colType))
462                                 Comm.Append ((short)size);
463                         else if (IsBlobType (colType))
464                                 Comm.Append (size);
465                         else
466                                 Comm.Append ((byte)size);
467
468                         if (size > 0) {
469                                 switch (param.TypeName) {
470                                 case "money" : {
471                                         Decimal val = (decimal) param.Value;
472                                         int[] arr = Decimal.GetBits (val);
473
474                                         if (val >= 0) {
475                                                 Comm.Append (arr[1]);
476                                                 Comm.Append (arr[0]);
477                                         } else {
478                                                 Comm.Append (~arr[1]);
479                                                 Comm.Append (~arr[0] + 1);
480                                         }
481                                         break;
482                                 }
483                                 case "smallmoney": {
484                                         Decimal val = (decimal) param.Value;
485                                         if (val < SMALLMONEY_MIN || val > SMALLMONEY_MAX)
486                                                 throw new OverflowException (string.Format (
487                                                         CultureInfo.InvariantCulture,
488                                                         "Value '{0}' is not valid for SmallMoney."
489                                                         + "  Must be between {1:N4} and {2:N4}.",
490 #if NET_2_0
491                                                         val,
492 #else
493                                                         val.ToString (CultureInfo.CurrentCulture),
494 #endif
495                                                         SMALLMONEY_MIN, SMALLMONEY_MAX));
496
497                                         int[] arr = Decimal.GetBits (val);
498                                         int sign = (val>0 ? 1: -1);
499                                         Comm.Append (sign * arr[0]);
500                                         break;
501                                 }
502                                 case "datetime":
503                                         Comm.Append ((DateTime)param.Value, 8);
504                                         break;
505                                 case "smalldatetime":
506                                         Comm.Append ((DateTime)param.Value, 4);
507                                         break;
508                                 case "varchar" :
509                                 case "nvarchar" :
510                                 case "char" :
511                                 case "nchar" :
512                                 case "text" :
513                                 case "ntext" :
514                                         byte [] tmp = param.GetBytes ();
515                                         Comm.Append (tmp);
516                                         break;
517                                 case "uniqueidentifier" :
518                                         Comm.Append (((Guid)param.Value).ToByteArray());
519                                         break;
520                                 default :
521                                         Comm.Append (param.Value);
522                                         break;
523                                 }
524                         }
525                         return;
526                 }
527
528                 public override void Execute (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
529                 {
530                         Parameters = parameters;
531                         string sql = commandText;
532                         if (wantResults || (Parameters != null && Parameters.Count > 0))
533                                 sql = BuildExec (commandText);
534                         ExecuteQuery (sql, timeout, wantResults);
535                 }
536
537                 private string FormatParameter (TdsMetaParameter parameter)
538                 {
539                         string parameterName = parameter.ParameterName;
540                         if (parameterName [0] == '@') {
541                                 parameterName = parameterName.Substring (1);
542                         }
543                         if (parameter.Direction == TdsParameterDirection.Output)
544                                 return String.Format ("@{0}={0} output", parameterName);
545                         if (parameter.Value == null || parameter.Value == DBNull.Value)
546                                 return String.Format ("@{0}=NULL", parameterName);
547
548                         string value = null;
549                         switch (parameter.TypeName) {
550                         case "smalldatetime":
551                         case "datetime":
552                                 DateTime d = Convert.ToDateTime (parameter.Value);
553                                 value = String.Format (base.Locale,
554                                         "'{0:MMM dd yyyy hh:mm:ss.fff tt}'", d);
555                                 break;
556                         case "bigint":
557                         case "decimal":
558                         case "float":
559                         case "int":
560                         case "money":
561                         case "real":
562                         case "smallint":
563                         case "smallmoney":
564                         case "tinyint":
565                                 object paramValue = parameter.Value;
566                                 Type paramType = paramValue.GetType ();
567                                 if (paramType.IsEnum)
568                                         paramValue = Convert.ChangeType (paramValue,
569                                                 Type.GetTypeCode (paramType));
570                                 value = paramValue.ToString ();
571                                 break;
572                         case "nvarchar":
573                         case "nchar":
574                                 value = String.Format ("N'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
575                                 break;
576                         case "uniqueidentifier":
577                                 value = String.Format ("'{0}'", ((Guid) parameter.Value).ToString (string.Empty));
578                                 break;
579                         case "bit":
580                                 if (parameter.Value.GetType () == typeof (bool))
581                                         value = (((bool) parameter.Value) ? "0x1" : "0x0");
582                                 else
583                                         value = parameter.Value.ToString ();
584                                 break;
585                         case "image":
586                         case "binary":
587                         case "varbinary":
588                                 byte[] byteArray = (byte[]) parameter.Value;
589                                 // In 1.0 profile, BitConverter.ToString() throws ArgumentOutOfRangeException when passed a 0-length
590                                 // array, so handle that as a special case.
591                                 if (byteArray.Length == 0)
592                                         value = "0x";
593                                 else
594                                         value = String.Format ("0x{0}", BitConverter.ToString (byteArray).Replace ("-", string.Empty).ToLower ());
595                                 break;
596                         default:
597                                 value = String.Format ("'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
598                                 break;
599                         }
600
601                         return "@" + parameterName + "=" + value;
602                 }
603
604                 public override string Prepare (string commandText, TdsMetaParameterCollection parameters)
605                 {
606                         Parameters = parameters;
607
608                         TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
609                         TdsMetaParameter parm = new TdsMetaParameter ("@Handle", "int", null);
610                         parm.Direction = TdsParameterDirection.Output;
611                         parms.Add (parm);
612
613                         parms.Add (new TdsMetaParameter ("@VarDecl", "nvarchar", BuildPreparedParameters ()));
614                         parms.Add (new TdsMetaParameter ("@Query", "nvarchar", commandText));
615
616                         ExecProc ("sp_prepare", parms, 0, true);
617                         SkipToEnd ();
618                         return OutputParameters[0].ToString () ;
619                         //if (ColumnValues == null || ColumnValues [0] == null || ColumnValues [0] == DBNull.Value)
620                         //      throw new TdsInternalException ();
621                         //return string.Empty;
622                         //return ColumnValues [0].ToString ();
623                 }
624
625                 protected override TdsDataColumnCollection ProcessColumnInfo ()
626                 {
627                         TdsDataColumnCollection result = new TdsDataColumnCollection ();
628                         int numColumns = Comm.GetTdsShort ();
629                         for (int i = 0; i < numColumns; i += 1) {
630                                 byte[] flagData = new byte[4];
631                                 for (int j = 0; j < 4; j += 1) 
632                                         flagData[j] = Comm.GetByte ();
633
634                                 bool nullable = (flagData[2] & 0x01) > 0;
635                                 //bool caseSensitive = (flagData[2] & 0x02) > 0;
636                                 bool writable = (flagData[2] & 0x0c) > 0;
637                                 bool autoIncrement = (flagData[2] & 0x10) > 0;
638                                 bool isIdentity = (flagData[2] & 0x10) > 0;
639
640                                 TdsColumnType columnType = (TdsColumnType) ((Comm.GetByte () & 0xff));
641
642                                 byte xColumnType = 0;
643                                 if (IsLargeType (columnType)) {
644                                         xColumnType = (byte) columnType;
645                                         if (columnType != TdsColumnType.NChar)
646                                                 columnType -= 128;
647                                 }
648
649                                 int columnSize;
650                                 string tableName = null;
651
652                                 if (IsBlobType (columnType)) {
653                                         columnSize = Comm.GetTdsInt ();
654                                         tableName = Comm.GetString (Comm.GetTdsShort ());
655                                 } else if (IsFixedSizeColumn (columnType))
656                                         columnSize = LookupBufferSize (columnType);
657                                 else if (IsLargeType ((TdsColumnType) xColumnType))
658                                         columnSize = Comm.GetTdsShort ();
659                                 else
660                                         columnSize = Comm.GetByte () & 0xff;
661
662                                 if (IsWideType ((TdsColumnType) columnType))
663                                         columnSize /= 2;
664
665                                 byte precision = 0;
666                                 byte scale = 0;
667
668                                 if (columnType == TdsColumnType.Decimal || columnType == TdsColumnType.Numeric) {
669                                         precision = Comm.GetByte ();
670                                         scale = Comm.GetByte ();
671                                 } else {
672                                         precision = GetPrecision (columnType, columnSize);
673                                         scale = GetScale (columnType, columnSize);
674                                 }
675
676                                 string columnName = Comm.GetString (Comm.GetByte ());
677
678                                 TdsDataColumn col = new TdsDataColumn ();
679                                 result.Add (col);
680 #if NET_2_0
681                                 col.ColumnType = columnType;
682                                 col.ColumnName = columnName;
683                                 col.IsAutoIncrement = autoIncrement;
684                                 col.IsIdentity = isIdentity;
685                                 col.ColumnSize = columnSize;
686                                 col.NumericPrecision = precision;
687                                 col.NumericScale = scale;
688                                 col.IsReadOnly = !writable;
689                                 col.AllowDBNull = nullable;
690                                 col.BaseTableName = tableName;
691 #else
692                                 col ["ColumnType"] = columnType;
693                                 col ["ColumnName"] = columnName;
694                                 col ["IsAutoIncrement"] = autoIncrement;
695                                 col ["IsIdentity"] = isIdentity;
696                                 col ["ColumnSize"] = columnSize;
697                                 col ["NumericPrecision"] = precision;
698                                 col ["NumericScale"] = scale;
699                                 col ["IsReadOnly"] = !writable;
700                                 col ["AllowDBNull"] = nullable;
701                                 col ["BaseTableName"] = tableName;
702 #endif
703                         }
704                         return result;
705                 }
706
707                 public override void Unprepare (string statementId)
708                 {
709                         TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
710                         parms.Add (new TdsMetaParameter ("@P1", "int", Int32.Parse (statementId)));
711                         ExecProc ("sp_unprepare", parms, 0, false);
712                 }
713                 
714                 protected override bool IsValidRowCount (byte status, byte op)
715                 {
716                         if ((status & (byte)0x10) == 0 || op == (byte)0xc1)
717                                 return false;
718                         return true; 
719                 }
720
721                 protected override void ProcessReturnStatus ()
722                 {
723                         int result = Comm.GetTdsInt ();
724                         if (Parameters != null) {
725                                 foreach (TdsMetaParameter param in Parameters) {
726                                         if (param.Direction == TdsParameterDirection.ReturnValue) {
727                                                 param.Value = result;
728                                                 break;
729                                         }
730                                 }
731                         }
732                 }
733
734                 byte GetScale (TdsColumnType type, int columnSize)
735                 {
736                         switch (type) {
737                         case TdsColumnType.DateTime:
738                                 return 0x03;
739                         case TdsColumnType.DateTime4:
740                                 return 0x00;
741                         case TdsColumnType.DateTimeN:
742                                 switch (columnSize) {
743                                 case 4:
744                                         return 0x00;
745                                 case 8:
746                                         return 0x03;
747                                 }
748                                 break;
749                         default:
750                                 return 0xff;
751                         }
752
753                         throw new NotSupportedException (string.Format (
754                                 CultureInfo.InvariantCulture,
755                                 "Fixed scale not defined for column " +
756                                 "type '{0}' with size {1}.", type, columnSize));
757                 }
758
759                 byte GetPrecision (TdsColumnType type, int columnSize)
760                 {
761                         switch (type) {
762                         case TdsColumnType.Binary:
763                                 return 0xff;
764                         case TdsColumnType.Bit:
765                                 return 0xff;
766                         case TdsColumnType.Char:
767                                 return 0xff;
768                         case TdsColumnType.DateTime:
769                                 return 0x17;
770                         case TdsColumnType.DateTime4:
771                                 return 0x10;
772                         case TdsColumnType.DateTimeN:
773                                 switch (columnSize) {
774                                 case 4:
775                                         return 0x10;
776                                 case 8:
777                                         return 0x17;
778                                 }
779                                 break;
780                         case TdsColumnType.Real:
781                                 return 0x07;
782                         case TdsColumnType.Float8:
783                                 return 0x0f;
784                         case TdsColumnType.FloatN:
785                                 switch (columnSize) {
786                                 case 4:
787                                         return 0x07;
788                                 case 8:
789                                         return 0x0f;
790                                 }
791                                 break;
792                         case TdsColumnType.Image:
793                                 return 0xff;
794                         case TdsColumnType.Int1:
795                                 return 0x03;
796                         case TdsColumnType.Int2:
797                                 return 0x05;
798                         case TdsColumnType.Int4:
799                                 return 0x0a;
800                         case TdsColumnType.IntN:
801                                 switch (columnSize) {
802                                 case 1:
803                                         return 0x03;
804                                 case 2:
805                                         return 0x05;
806                                 case 4:
807                                         return 0x0a;
808                                 }
809                                 break;
810                         case TdsColumnType.Void:
811                                 return 0x01;
812                         case TdsColumnType.Text:
813                                 return 0xff;
814                         case TdsColumnType.UniqueIdentifier:
815                                 return 0xff;
816                         case TdsColumnType.VarBinary:
817                                 return 0xff;
818                         case TdsColumnType.VarChar:
819                                 return 0xff;
820                         case TdsColumnType.Money:
821                                 return 19;
822                         case TdsColumnType.NText:
823                                 return 0xff;
824                         case TdsColumnType.NVarChar:
825                                 return 0xff;
826                         case TdsColumnType.BitN:
827                                 return 0xff;
828                         case TdsColumnType.MoneyN:
829                                 switch (columnSize) {
830                                 case 4:
831                                         return 0x0a;
832                                 case 8:
833                                         return 0x13;
834                                 }
835                                 break;
836                         case TdsColumnType.Money4:
837                                 return 0x0a;
838                         case TdsColumnType.NChar:
839                                 return 0xff;
840                         case TdsColumnType.BigBinary:
841                                 return 0xff;
842                         case TdsColumnType.BigVarBinary:
843                                 return 0xff;
844                         case TdsColumnType.BigVarChar:
845                                 return 0xff;
846                         case TdsColumnType.BigNVarChar:
847                                 return 0xff;
848                         case TdsColumnType.BigChar:
849                                 return 0xff;
850                         case TdsColumnType.SmallMoney:
851                                 return 0x0a;
852                         case TdsColumnType.Variant:
853                                 return 0xff;
854                         case TdsColumnType.BigInt:
855                                 return 0xff;
856                         }
857
858                         throw new NotSupportedException (string.Format (
859                                 CultureInfo.InvariantCulture,
860                                 "Fixed precision not defined for column " +
861                                 "type '{0}' with size {1}.", type, columnSize));
862                 }
863
864                 #endregion // Methods
865
866 #if NET_2_0
867                 #region Asynchronous Methods
868
869                 public override IAsyncResult BeginExecuteNonQuery (string cmdText,
870                                                           TdsMetaParameterCollection parameters,
871                                                           AsyncCallback callback,
872                                                           object state)
873                 {
874                         Parameters = parameters;
875                         string sql = cmdText;
876                         if (Parameters != null && Parameters.Count > 0)
877                                 sql = BuildExec (cmdText);
878
879                         IAsyncResult ar = BeginExecuteQueryInternal (sql, false, callback, state);
880                         return ar;
881                 }
882
883                 public override void EndExecuteNonQuery (IAsyncResult ar)
884                 {
885                         EndExecuteQueryInternal (ar);
886                 }
887
888                 public override IAsyncResult BeginExecuteQuery (string cmdText,
889                                                                 TdsMetaParameterCollection parameters,
890                                                                 AsyncCallback callback,
891                                                                 object state)
892                 {
893                         Parameters = parameters;
894                         string sql = cmdText;
895                         if (Parameters != null && Parameters.Count > 0)
896                                 sql = BuildExec (cmdText);
897
898                         IAsyncResult ar = BeginExecuteQueryInternal (sql, true, callback, state);
899                         return ar;
900                 }
901
902                 public override void EndExecuteQuery (IAsyncResult ar)
903                 {
904                         EndExecuteQueryInternal (ar);
905                 }
906
907                 public override IAsyncResult BeginExecuteProcedure (string prolog,
908                                                                     string epilog,
909                                                                     string cmdText,
910                                                                     bool IsNonQuery,
911                                                                     TdsMetaParameterCollection parameters,
912                                                                     AsyncCallback callback,
913                                                                     object state)
914                 {
915                         Parameters = parameters;
916                         string pcall = BuildProcedureCall (cmdText);
917                         string sql = String.Format ("{0};{1};{2};", prolog, pcall, epilog);
918
919                         IAsyncResult ar = BeginExecuteQueryInternal (sql, !IsNonQuery, callback, state);
920                         return ar;
921                 }
922
923                 public override void EndExecuteProcedure (IAsyncResult ar)
924                 {
925                         EndExecuteQueryInternal (ar);
926                 }
927
928                 #endregion // Asynchronous Methods
929 #endif // NET_2_0
930         }
931 }