instead of executing sp_reset_connection, set reset connection bit.
[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 //
10 // Copyright (C) 2002 Tim Coleman
11 // Portions (C) 2003 Motus Technologies Inc. (http://www.motus.com)
12 // Portions (C) 2003 Daniel Morgan
13 //
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 includeAt = "@";
87                                 if (p.ParameterName [0] == '@')
88                                         includeAt = string.Empty;
89                                 if (p.Direction != TdsParameterDirection.ReturnValue) {
90                                         if (result.Length > 0)
91                                                 result.Append (", ");
92                                         if (p.Direction == TdsParameterDirection.InputOutput)
93                                                 result.Append (String.Format("{0}{1}={1} output", includeAt, p.ParameterName));
94                                         else
95                                                 result.Append (FormatParameter (p));
96                                 }
97                         }
98                         return result.ToString ();
99                 }
100
101                 private string BuildPreparedParameters ()
102                 {
103                         StringBuilder parms = new StringBuilder ();
104                         foreach (TdsMetaParameter p in Parameters) {
105                                 if (parms.Length > 0)
106                                         parms.Append (", ");
107                                 parms.Append (p.Prepare ());
108                                 if (p.Direction == TdsParameterDirection.Output)
109                                         parms.Append (" output");
110                         }
111                         return parms.ToString ();
112                 }
113
114                 private string BuildPreparedQuery (string id)
115                 {
116                         return BuildProcedureCall (String.Format ("sp_execute {0},", id));
117                 }
118
119                 private string BuildProcedureCall (string procedure)
120                 {
121                         string exec = String.Empty;
122
123                         StringBuilder declare = new StringBuilder ();
124                         StringBuilder select = new StringBuilder ();
125                         StringBuilder set = new StringBuilder ();
126                         
127                         int count = 0;
128                         if (Parameters != null) {
129                                 foreach (TdsMetaParameter p in Parameters) {
130                                         if (p.Direction != TdsParameterDirection.Input) {
131                                                 if (count == 0)
132                                                         select.Append ("select ");
133                                                 else
134                                                         select.Append (", ");
135                                                 select.Append (p.ParameterName);
136                                                         
137                                                 declare.Append (String.Format ("declare {0}\n", p.Prepare ()));
138
139                                                 if (p.Direction != TdsParameterDirection.ReturnValue) {
140                                                         if (p.Direction == TdsParameterDirection.InputOutput)
141                                                                 set.Append (String.Format ("set {0}\n", FormatParameter(p)));
142                                                         else
143                                                                 set.Append (String.Format ("set {0}=NULL\n", p.ParameterName));
144                                                 }
145                                         
146                                                 count += 1;
147                                         }
148                                         
149                                         if (p.Direction == TdsParameterDirection.ReturnValue)
150                                                 exec = p.ParameterName + "=";
151                                 }
152                         }
153                         exec = "exec " + exec;
154
155                         return String.Format ("{0}{1}{2}{3} {4}\n{5}",
156                                 declare.ToString (), set.ToString (), exec,
157                                 procedure, BuildParameters (), select.ToString ());
158                 }
159
160                 public override bool Connect (TdsConnectionParameters connectionParameters)
161                 {
162                         if (IsConnected)
163                                 throw new InvalidOperationException ("The connection is already open.");
164         
165                         connectionParms = connectionParameters;
166
167                         SetLanguage (connectionParameters.Language);
168                         SetCharset ("utf-8");
169                 
170                         byte[] empty = new byte[0];
171                         short authLen = 0;
172                         byte pad = (byte) 0;
173                         
174                         byte[] domainMagic = { 6, 0x7d, 0x0f, 0xfd, 0xff, 0x0, 0x0, 0x0,
175                                                                         0x0, 0xe0, 0x83, 0x0, 0x0,
176                                                                         0x68, 0x01, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00 };
177                         byte[] sqlserverMagic = { 6, 0x0, 0x0, 0x0,
178                                                                                 0x0, 0x0, 0x0, 0x0,
179                                                                                 0x0, 0xe0, 0x03, 0x0,
180                                                                                 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
181                                                                                 0x0, 0x0, 0x0 };
182                         byte[] magic = null;
183                         
184                         if (connectionParameters.DomainLogin)
185                                 magic = domainMagic;
186                         else
187                                 magic = sqlserverMagic;
188                         
189                         string username = connectionParameters.User;
190                         string domain = null;
191
192                         int idx = username.IndexOf ("\\");
193                         if (idx != -1) {
194                                 domain = username.Substring (0, idx);
195                                 username = username.Substring (idx + 1);
196
197                                 connectionParameters.DefaultDomain = domain;
198                                 connectionParameters.User = username;
199                         } else {
200                                 domain = Environment.UserDomainName;
201                                 connectionParameters.DefaultDomain = domain;
202                         }
203
204                         short partialPacketSize = (short) (86 + (
205                                 connectionParameters.Hostname.Length +
206                                 connectionParameters.ApplicationName.Length +
207                                 DataSource.Length +
208                                 connectionParameters.LibraryName.Length +
209                                 Language.Length +
210                                 connectionParameters.Database.Length +
211                                 connectionParameters.AttachDBFileName.Length) * 2);
212
213                         if (connectionParameters.DomainLogin) {
214                                 authLen = ((short) (32 + (connectionParameters.Hostname.Length +
215                                         domain.Length)));
216                                 partialPacketSize += authLen;
217                         } else
218                                 partialPacketSize += ((short) ((username.Length + connectionParameters.Password.Length) * 2));
219                         
220                         int totalPacketSize = partialPacketSize;
221                         
222                         Comm.StartPacket (TdsPacketType.Logon70);
223                         
224                         Comm.Append (totalPacketSize);
225
226                         //Comm.Append (empty, 3, pad);
227                         byte[] version = {0x00, 0x0, 0x0, 0x70};
228                         Comm.Append (version); // TDS Version 7
229                         Comm.Append ((int)this.PacketSize); // Set the Block Size
230                         Comm.Append (empty, 3, pad);
231                         Comm.Append (magic);
232
233                         short curPos = 86;
234
235                         // Hostname
236                         Comm.Append (curPos);
237                         Comm.Append ((short) connectionParameters.Hostname.Length);
238                         curPos += (short) (connectionParameters.Hostname.Length * 2);
239
240                         if (connectionParameters.DomainLogin) {
241                                 Comm.Append((short)0);
242                                 Comm.Append((short)0);
243                                 Comm.Append((short)0);
244                                 Comm.Append((short)0);
245                         } else {
246                                 // Username
247                                 Comm.Append (curPos);
248                                 Comm.Append ((short) username.Length);
249                                 curPos += ((short) (username.Length * 2));
250
251                                 // Password
252                                 Comm.Append (curPos);
253                                 Comm.Append ((short) connectionParameters.Password.Length);
254                                 curPos += (short) (connectionParameters.Password.Length * 2);
255                         }
256
257                         // AppName
258                         Comm.Append (curPos);
259                         Comm.Append ((short) connectionParameters.ApplicationName.Length);
260                         curPos += (short) (connectionParameters.ApplicationName.Length * 2);
261
262                         // Server Name
263                         Comm.Append (curPos);
264                         Comm.Append ((short) DataSource.Length);
265                         curPos += (short) (DataSource.Length * 2);
266
267                         // Unknown
268                         Comm.Append ((short) curPos);
269                         Comm.Append ((short) 0);
270
271                         // Library Name
272                         Comm.Append (curPos);
273                         Comm.Append ((short) connectionParameters.LibraryName.Length);
274                         curPos += (short) (connectionParameters.LibraryName.Length * 2);
275
276                         // Language
277                         Comm.Append (curPos);
278                         Comm.Append ((short) Language.Length);
279                         curPos += (short) (Language.Length * 2);
280
281                         // Database
282                         Comm.Append (curPos);
283                         Comm.Append ((short) connectionParameters.Database.Length);
284                         curPos += (short) (connectionParameters.Database.Length * 2);
285
286                         // MAC Address
287                         Comm.Append((byte) 0);
288                         Comm.Append((byte) 0);
289                         Comm.Append((byte) 0);
290                         Comm.Append((byte) 0);
291                         Comm.Append((byte) 0);
292                         Comm.Append((byte) 0);
293
294                         // Authentication Stuff
295                         Comm.Append ((short) curPos);
296                         if (connectionParameters.DomainLogin) {
297                                 Comm.Append ((short) authLen);
298                                 curPos += (short) authLen;
299                         } else
300                                 Comm.Append ((short) 0);
301                         
302                         // Unknown
303                         Comm.Append (curPos);
304                         Comm.Append ((short)( connectionParameters.AttachDBFileName.Length));
305                         curPos += (short)(connectionParameters.AttachDBFileName.Length*2);
306                         
307                         // Connection Parameters
308                         Comm.Append (connectionParameters.Hostname);
309                         if (!connectionParameters.DomainLogin) {
310                                 // SQL Server Authentication
311                                 Comm.Append (connectionParameters.User);
312                                 string scrambledPwd = EncryptPassword (connectionParameters.Password);
313                                 Comm.Append (scrambledPwd);
314                         }
315                         Comm.Append (connectionParameters.ApplicationName);
316                         Comm.Append (DataSource);
317                         Comm.Append (connectionParameters.LibraryName);
318                         Comm.Append (Language);
319                         Comm.Append (connectionParameters.Database);
320
321                         if (connectionParameters.DomainLogin) {
322                                 // the rest of the packet is NTLMSSP authentication
323                                 Type1Message msg = new Type1Message ();
324                                 msg.Domain = domain;
325                                 msg.Host = connectionParameters.Hostname;
326                                 msg.Flags = NtlmFlags.NegotiateUnicode |
327                                         NtlmFlags.NegotiateNtlm |
328                                         NtlmFlags.NegotiateDomainSupplied |
329                                         NtlmFlags.NegotiateWorkstationSupplied |
330                                         NtlmFlags.NegotiateAlwaysSign; // 0xb201
331                                 Comm.Append (msg.GetBytes ());
332                         }
333
334                         Comm.Append (connectionParameters.AttachDBFileName);
335                         Comm.SendPacket ();
336                         MoreResults = true;
337                         SkipToEnd ();
338                         
339                         return IsConnected;
340                 }
341
342                 private static string EncryptPassword (string pass)
343                 {
344                         int xormask = 0x5a5a;
345                         int len = pass.Length;
346                         char[] chars = new char[len];
347
348                         for (int i = 0; i < len; ++i) {
349                                 int c = ((int) (pass[i])) ^ xormask;
350                                 int m1 = (c >> 4) & 0x0f0f;
351                                 int m2 = (c << 4) & 0xf0f0;
352                                 chars[i] = (char) (m1 | m2);
353                         }
354
355                         return new String (chars);
356                 }
357
358                 public override bool Reset ()
359                 {
360                         // Check validity of the connection - a false removes
361                         // the connection from the pool
362                         // NOTE: MS implementation will throw a connection-reset error as it will
363                         // try to use the same connection
364                         if (!Comm.IsConnected ())
365                                 return false;
366
367                         // Set "reset-connection" bit for the next message packet
368                         Comm.ResetConnection = true;
369
370                         return true;
371                 }
372
373                 public override void ExecPrepared (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
374                 {
375                         Parameters = parameters;
376                         ExecuteQuery (BuildPreparedQuery (commandText), timeout, wantResults);
377                 }
378                         
379                 public override void ExecProc (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
380                 {
381                         Parameters = parameters;
382                         ExecRPC (commandText, parameters, timeout, wantResults);
383                 }
384
385                 protected override void ExecRPC (string rpcName, TdsMetaParameterCollection parameters, 
386                                                  int timeout, bool wantResults)
387                 {
388                         // clean up
389                         InitExec ();
390                         Comm.StartPacket (TdsPacketType.RPC);
391
392                         Comm.Append ( (short) rpcName.Length);
393                         Comm.Append (rpcName);
394                         Comm.Append ( (short) 0); //no meta data
395                         if (parameters != null) {
396                                 foreach (TdsMetaParameter param in parameters) {
397                                         if (param.Direction == TdsParameterDirection.ReturnValue) 
398                                                 continue;
399                                         Comm.Append ( (byte) param.ParameterName.Length );
400                                         Comm.Append (param.ParameterName);
401                                         short status = 0; // unused
402                                         if (param.Direction != TdsParameterDirection.Input)
403                                                 status |= 0x01; // output
404                                         Comm.Append ( (byte) status);
405                                         WriteParameterInfo (param);
406                                 }
407                         }
408                         Comm.SendPacket ();
409                         CheckForData (timeout);
410                         if (!wantResults)
411                                 SkipToEnd ();
412                 }
413
414                 private void WriteParameterInfo (TdsMetaParameter param)
415                 {
416                         /*
417                         Ms.net send non-nullable datatypes as nullable and allows setting null values
418                         to int/float etc.. So, using Nullable form of type for all data
419                         */
420                         param.IsNullable = true;
421                         TdsColumnType colType = param.GetMetaType ();
422                         param.IsNullable = false;
423
424                         Comm.Append ((byte)colType); // type
425
426                         int size = param.Size;
427                         if (size == 0)
428                                 size = param.GetActualSize ();
429
430                         /*
431                           If column type is SqlDbType.NVarChar the size of parameter is multiplied by 2
432                           FIXME: Need to check for other types
433                          */
434                         if (colType == TdsColumnType.BigNVarChar)
435                                 size <<= 1;
436                         if (IsLargeType (colType))
437                                 Comm.Append ((short)size); // Parameter size passed in SqlParameter
438                         else if (IsBlobType (colType))
439                                 Comm.Append (size); // Parameter size passed in SqlParameter
440                         else
441                                 Comm.Append ((byte)size);
442
443                         // Precision and Scale are non-zero for only decimal/numeric
444                         if ( param.TypeName == "decimal" || param.TypeName == "numeric") {
445                                 Comm.Append ((param.Precision !=0 ) ? param.Precision : (byte) 28);
446                                 Comm.Append (param.Scale);
447                         }
448
449                         size = param.GetActualSize ();
450                         if (IsLargeType (colType))
451                                 Comm.Append ((short)size);
452                         else if (IsBlobType (colType))
453                                 Comm.Append (size);
454                         else
455                                 Comm.Append ((byte)size);
456
457                         if (size > 0) {
458                                 switch (param.TypeName) {
459                                 case "money" : {
460                                         Decimal val = (decimal) param.Value;
461                                         int[] arr = Decimal.GetBits (val);
462
463                                         if (val >= 0) {
464                                                 Comm.Append (arr[1]);
465                                                 Comm.Append (arr[0]);
466                                         } else {
467                                                 Comm.Append (~arr[1]);
468                                                 Comm.Append (~arr[0] + 1);
469                                         }
470                                         break;
471                                 }
472                                 case "smallmoney": {
473                                         Decimal val = (decimal) param.Value;
474                                         if (val < SMALLMONEY_MIN || val > SMALLMONEY_MAX)
475                                                 throw new OverflowException (string.Format (
476                                                         CultureInfo.InvariantCulture,
477                                                         "Value '{0}' is not valid for SmallMoney."
478                                                         + "  Must be between {1:N4} and {2:N4}.",
479 #if NET_2_0
480                                                         val,
481 #else
482                                                         val.ToString (CultureInfo.CurrentCulture),
483 #endif
484                                                         SMALLMONEY_MIN, SMALLMONEY_MAX));
485
486                                         int[] arr = Decimal.GetBits (val);
487                                         int sign = (val>0 ? 1: -1);
488                                         Comm.Append (sign * arr[0]);
489                                         break;
490                                 }
491                                 case "datetime":
492                                         Comm.Append ((DateTime)param.Value, 8);
493                                         break;
494                                 case "smalldatetime":
495                                         Comm.Append ((DateTime)param.Value, 4);
496                                         break;
497                                 case "varchar" :
498                                 case "nvarchar" :
499                                 case "char" :
500                                 case "nchar" :
501                                 case "text" :
502                                 case "ntext" :
503                                         byte [] tmp = param.GetBytes ();
504                                         Comm.Append (tmp);
505                                         break;
506                                 case "uniqueidentifier" :
507                                         Comm.Append (((Guid)param.Value).ToByteArray());
508                                         break;
509                                 default :
510                                         Comm.Append (param.Value);
511                                         break;
512                                 }
513                         }
514                         return;
515                 }
516
517                 public override void Execute (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
518                 {
519                         Parameters = parameters;
520                         string sql = commandText;
521                         if (wantResults || (Parameters != null && Parameters.Count > 0))
522                                 sql = BuildExec (commandText);
523                         ExecuteQuery (sql, timeout, wantResults);
524                 }
525
526                 private string FormatParameter (TdsMetaParameter parameter)
527                 {
528                         string includeAt = "@";
529                         if (parameter.ParameterName [0] == '@')
530                                 includeAt = string.Empty;
531                         if (parameter.Direction == TdsParameterDirection.Output)
532                                 return String.Format ("{0}{1}={1} output", includeAt, parameter.ParameterName);
533                         if (parameter.Value == null || parameter.Value == DBNull.Value)
534                                 return parameter.ParameterName + "=NULL";
535
536                         string value = null;
537                         switch (parameter.TypeName) {
538                         case "smalldatetime":
539                         case "datetime":
540                                 DateTime d = Convert.ToDateTime (parameter.Value);
541                                 value = String.Format (base.Locale,
542                                         "'{0:MMM dd yyyy hh:mm:ss.fff tt}'", d);
543                                 break;
544                         case "bigint":
545                         case "decimal":
546                         case "float":
547                         case "int":
548                         case "money":
549                         case "real":
550                         case "smallint":
551                         case "smallmoney":
552                         case "tinyint":
553                                 object paramValue = parameter.Value;
554                                 Type paramType = paramValue.GetType ();
555                                 if (paramType.IsEnum)
556                                         paramValue = Convert.ChangeType (paramValue,
557                                                 Type.GetTypeCode (paramType));
558                                 value = paramValue.ToString ();
559                                 break;
560                         case "nvarchar":
561                         case "nchar":
562                                 value = String.Format ("N'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
563                                 break;
564                         case "uniqueidentifier":
565                                 value = String.Format ("'{0}'", ((Guid) parameter.Value).ToString (string.Empty));
566                                 break;
567                         case "bit":
568                                 if (parameter.Value.GetType () == typeof (bool))
569                                         value = (((bool) parameter.Value) ? "0x1" : "0x0");
570                                 else
571                                         value = parameter.Value.ToString ();
572                                 break;
573                         case "image":
574                         case "binary":
575                         case "varbinary":
576                                 byte[] byteArray = (byte[]) parameter.Value;
577                                 // In 1.0 profile, BitConverter.ToString() throws ArgumentOutOfRangeException when passed a 0-length
578                                 // array, so handle that as a special case.
579                                 if (byteArray.Length == 0)
580                                         value = "0x";
581                                 else
582                                         value = String.Format ("0x{0}", BitConverter.ToString (byteArray).Replace ("-", string.Empty).ToLower ());
583                                 break;
584                         default:
585                                 value = String.Format ("'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
586                                 break;
587                         }
588
589                         return includeAt + parameter.ParameterName + "=" + value;
590                 }
591
592                 public override string Prepare (string commandText, TdsMetaParameterCollection parameters)
593                 {
594                         Parameters = parameters;
595
596                         TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
597                         TdsMetaParameter parm = new TdsMetaParameter ("@Handle", "int", null);
598                         parm.Direction = TdsParameterDirection.Output;
599                         parms.Add (parm);
600
601                         parms.Add (new TdsMetaParameter ("@VarDecl", "nvarchar", BuildPreparedParameters ()));
602                         parms.Add (new TdsMetaParameter ("@Query", "nvarchar", commandText));
603
604                         ExecProc ("sp_prepare", parms, 0, true);
605                         SkipToEnd ();
606                         return OutputParameters[0].ToString () ;
607                         //if (ColumnValues == null || ColumnValues [0] == null || ColumnValues [0] == DBNull.Value)
608                         //      throw new TdsInternalException ();
609                         //return string.Empty;
610                         //return ColumnValues [0].ToString ();
611                 }
612
613                 protected override TdsDataColumnCollection ProcessColumnInfo ()
614                 {
615                         TdsDataColumnCollection result = new TdsDataColumnCollection ();
616                         int numColumns = Comm.GetTdsShort ();
617                         for (int i = 0; i < numColumns; i += 1) {
618                                 byte[] flagData = new byte[4];
619                                 for (int j = 0; j < 4; j += 1) 
620                                         flagData[j] = Comm.GetByte ();
621
622                                 bool nullable = (flagData[2] & 0x01) > 0;
623                                 //bool caseSensitive = (flagData[2] & 0x02) > 0;
624                                 bool writable = (flagData[2] & 0x0c) > 0;
625                                 bool autoIncrement = (flagData[2] & 0x10) > 0;
626                                 bool isIdentity = (flagData[2] & 0x10) > 0;
627
628                                 TdsColumnType columnType = (TdsColumnType) (Comm.GetByte () & 0xff);
629                                 if ((byte) columnType == 0xef)
630                                         columnType = TdsColumnType.NChar;
631
632                                 byte xColumnType = 0;
633                                 if (IsLargeType (columnType)) {
634                                         xColumnType = (byte) columnType;
635                                         if (columnType != TdsColumnType.NChar)
636                                                 columnType -= 128;
637                                 }
638
639                                 int columnSize;
640                                 string tableName = null;
641
642                                 if (IsBlobType (columnType)) {
643                                         columnSize = Comm.GetTdsInt ();
644                                         tableName = Comm.GetString (Comm.GetTdsShort ());
645                                 } else if (IsFixedSizeColumn (columnType))
646                                         columnSize = LookupBufferSize (columnType);
647                                 else if (IsLargeType ((TdsColumnType) xColumnType))
648                                         columnSize = Comm.GetTdsShort ();
649                                 else
650                                         columnSize = Comm.GetByte () & 0xff;
651
652                                 byte precision = 0;
653                                 byte scale = 0;
654
655                                 switch (columnType) {
656                                 case TdsColumnType.NText:
657                                 case TdsColumnType.NChar:
658                                 case TdsColumnType.NVarChar:
659                                         columnSize /= 2;
660                                         break;
661                                 case TdsColumnType.Decimal:
662                                 case TdsColumnType.Numeric:
663                                   /*
664                                         Comm.Skip (1);
665                                   */
666                                         precision = Comm.GetByte ();
667                                         scale = Comm.GetByte ();
668                                         break;
669                                 }
670
671                                 string columnName = Comm.GetString (Comm.GetByte ());
672
673                                 TdsDataColumn col = new TdsDataColumn ();
674                                 result.Add (col);
675 #if NET_2_0
676                                 col.ColumnType = columnType;
677                                 col.ColumnName = columnName;
678                                 col.IsAutoIncrement = autoIncrement;
679                                 col.IsIdentity = isIdentity;
680                                 col.ColumnSize = columnSize;
681                                 col.NumericPrecision = precision;
682                                 col.NumericScale = scale;
683                                 col.IsReadOnly = !writable;
684                                 col.AllowDBNull = nullable;
685                                 col.BaseTableName = tableName;
686 #else
687                                 col ["ColumnType"] = columnType;
688                                 col ["ColumnName"] = columnName;
689                                 col ["IsAutoIncrement"] = autoIncrement;
690                                 col ["IsIdentity"] = isIdentity;
691                                 col ["ColumnSize"] = columnSize;
692                                 col ["NumericPrecision"] = precision;
693                                 col ["NumericScale"] = scale;
694                                 col ["IsReadOnly"] = !writable;
695                                 col ["AllowDBNull"] = nullable;
696                                 col ["BaseTableName"] = tableName;
697 #endif
698                         }
699                         return result;
700                 }
701
702                 public override void Unprepare (string statementId)
703                 {
704                         TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
705                         parms.Add (new TdsMetaParameter ("@P1", "int", Int32.Parse (statementId)));
706                         ExecProc ("sp_unprepare", parms, 0, false);
707                 }
708                 
709                 protected override bool IsValidRowCount (byte status, byte op)
710                 {
711                         if ((status & (byte)0x10) == 0 || op == (byte)0xc1)
712                                 return false;
713                         return true; 
714                 }
715
716                 protected override void ProcessReturnStatus ()
717                 {
718                         int result = Comm.GetTdsInt ();
719                         if (Parameters != null) {
720                                 foreach (TdsMetaParameter param in Parameters) {
721                                         if (param.Direction == TdsParameterDirection.ReturnValue) {
722                                                 param.Value = result;
723                                                 break;
724                                         }
725                                 }
726                         }
727                 }
728
729                 #endregion // Methods
730
731 #if NET_2_0
732                 #region Asynchronous Methods
733
734                 public override IAsyncResult BeginExecuteNonQuery (string cmdText,
735                                                           TdsMetaParameterCollection parameters,
736                                                           AsyncCallback callback,
737                                                           object state)
738                 {
739                         Parameters = parameters;
740                         string sql = cmdText;
741                         if (Parameters != null && Parameters.Count > 0)
742                                 sql = BuildExec (cmdText);
743
744                         IAsyncResult ar = BeginExecuteQueryInternal (sql, false, callback, state);
745                         return ar;
746                 }
747
748                 public override void EndExecuteNonQuery (IAsyncResult ar)
749                 {
750                         EndExecuteQueryInternal (ar);
751                 }
752
753                 public override IAsyncResult BeginExecuteQuery (string cmdText,
754                                                                 TdsMetaParameterCollection parameters,
755                                                                 AsyncCallback callback,
756                                                                 object state)
757                 {
758                         Parameters = parameters;
759                         string sql = cmdText;
760                         if (Parameters != null && Parameters.Count > 0)
761                                 sql = BuildExec (cmdText);
762
763                         IAsyncResult ar = BeginExecuteQueryInternal (sql, true, callback, state);
764                         return ar;
765                 }
766
767                 public override void EndExecuteQuery (IAsyncResult ar)
768                 {
769                         EndExecuteQueryInternal (ar);
770                 }
771
772                 public override IAsyncResult BeginExecuteProcedure (string prolog,
773                                                                     string epilog,
774                                                                     string cmdText,
775                                                                     bool IsNonQuery,
776                                                                     TdsMetaParameterCollection parameters,
777                                                                     AsyncCallback callback,
778                                                                     object state)
779                 {
780                         Parameters = parameters;
781                         string pcall = BuildProcedureCall (cmdText);
782                         string sql = String.Format ("{0};{1};{2};", prolog, pcall, epilog);
783
784                         IAsyncResult ar = BeginExecuteQueryInternal (sql, !IsNonQuery, callback, state);
785                         return ar;
786                 }
787
788                 public override void EndExecuteProcedure (IAsyncResult ar)
789                 {
790                         EndExecuteQueryInternal (ar);
791                 }
792
793                 #endregion // Asynchronous Methods
794 #endif // NET_2_0
795         }
796 }