60cb4bee36cdf9ce054c308015066b509ac8b1ec
[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 //   Veerapuram Varadhan  (vvaradhan@novell.com)
11 //
12 // Copyright (C) 2002 Tim Coleman
13 // Portions (C) 2003 Motus Technologies Inc. (http://www.motus.com)
14 // Portions (C) 2003 Daniel Morgan
15 //
16 //
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
24 // 
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
27 // 
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 //
36
37 using System;
38 using System.Globalization;
39 using System.Text;
40
41 using Mono.Security.Protocol.Ntlm;
42
43 namespace Mono.Data.Tds.Protocol
44 {
45         public class Tds70 : Tds
46         {
47                 #region Fields
48
49                 //public readonly static TdsVersion Version = TdsVersion.tds70;
50                 static readonly decimal SMALLMONEY_MIN = -214748.3648m;
51                 static readonly decimal SMALLMONEY_MAX = 214748.3647m;
52
53                 #endregion // Fields
54
55                 #region Constructors
56
57                 public Tds70 (string server, int port)
58                         : this (server, port, 512, 15)
59                 {
60                 }
61
62                 public Tds70 (string server, int port, int packetSize, int timeout)
63                         : base (server, port, packetSize, timeout, TdsVersion.tds70)
64                 {
65                 }
66
67                 public Tds70 (string server, int port, int packetSize, int timeout, TdsVersion version)
68                         : base (server, port, packetSize, timeout, version)
69                 {
70                 }
71                 
72                 #endregion // Constructors
73
74                 #region Properties
75                 
76                 protected virtual byte[] ClientVersion {
77                         get { return new byte[] {0x00, 0x0, 0x0, 0x70};}
78                 }
79                 
80                 // Default precision is 28 for a 7.0 server. Unless and 
81                 // otherwise the server is started with /p option - which would be 38
82                 protected virtual byte Precision {
83                         get { return 28; }
84                 }
85                 
86                 #endregion // Properties
87                 
88                 #region Methods
89
90                 protected string BuildExec (string sql)
91                 {
92                         string esql = sql.Replace ("'", "''"); // escape single quote
93                         if (Parameters != null && Parameters.Count > 0)
94                                 return BuildProcedureCall (String.Format ("sp_executesql N'{0}', N'{1}', ", esql, BuildPreparedParameters ()));
95                         else
96                                 return BuildProcedureCall (String.Format ("sp_executesql N'{0}'", esql));
97                 }
98
99                 private string BuildParameters ()
100                 {
101                         if (Parameters == null || Parameters.Count == 0)
102                                 return String.Empty;
103
104                         StringBuilder result = new StringBuilder ();
105                         foreach (TdsMetaParameter p in Parameters) {
106                                 string parameterName = p.ParameterName;
107                                 if (parameterName [0] == '@') {
108                                         parameterName = parameterName.Substring (1);
109                                 }
110                                 if (p.Direction != TdsParameterDirection.ReturnValue) {
111                                         if (result.Length > 0)
112                                                 result.Append (", ");
113                                         if (p.Direction == TdsParameterDirection.InputOutput)
114                                                 result.AppendFormat ("@{0}={0} output", parameterName);
115                                         else
116                                                 result.Append (FormatParameter (p));
117                                 }
118                         }
119                         return result.ToString ();
120                 }
121
122                 private string BuildPreparedParameters ()
123                 {
124                         StringBuilder parms = new StringBuilder ();
125                         foreach (TdsMetaParameter p in Parameters) {
126                                 if (parms.Length > 0)
127                                         parms.Append (", ");
128                                 
129                                 // Set default precision according to the TdsVersion
130                                 // Current default is 29 for Tds80 
131                                 if (p.TypeName == "decimal")
132                                         p.Precision = (p.Precision !=0  ? p.Precision : (byte) Precision);
133                                                                                 
134                                 parms.Append (p.Prepare ());
135                                 if (p.Direction == TdsParameterDirection.Output)
136                                         parms.Append (" output");
137                         }
138                         return parms.ToString ();
139                 }
140
141                 private string BuildPreparedQuery (string id)
142                 {
143                         return BuildProcedureCall (String.Format ("sp_execute {0},", id));
144                 }
145
146                 private string BuildProcedureCall (string procedure)
147                 {
148                         string exec = String.Empty;
149
150                         StringBuilder declare = new StringBuilder ();
151                         StringBuilder select = new StringBuilder ();
152                         StringBuilder set = new StringBuilder ();
153                         
154                         int count = 0;
155                         if (Parameters != null) {
156                                 foreach (TdsMetaParameter p in Parameters) {
157                                         string parameterName = p.ParameterName;
158                                         if (parameterName [0] == '@') {
159                                                 parameterName = parameterName.Substring (1);
160                                         }
161
162                                         if (p.Direction != TdsParameterDirection.Input) {
163                                                 if (count == 0)
164                                                         select.Append ("select ");
165                                                 else
166                                                         select.Append (", ");
167                                                 select.Append ("@" + parameterName);
168                                                 
169                                                 if (p.TypeName == "decimal")
170                                                         p.Precision = (p.Precision !=0 ? p.Precision : (byte) Precision);
171                                                         
172                                                 declare.Append (String.Format ("declare {0}\n", p.Prepare ()));
173
174                                                 if (p.Direction != TdsParameterDirection.ReturnValue) {
175                                                         if (p.Direction == TdsParameterDirection.InputOutput)
176                                                                 set.Append (String.Format ("set {0}\n", FormatParameter(p)));
177                                                         else
178                                                                 set.Append (String.Format ("set @{0}=NULL\n", parameterName));
179                                                 }
180                                         
181                                                 count++;
182                                         }
183                                         if (p.Direction == TdsParameterDirection.ReturnValue)
184                                                 exec = "@" + parameterName + "=";
185                                 }
186                         }
187                         exec = "exec " + exec;
188
189                         return String.Format ("{0}{1}{2}{3} {4}\n{5}",
190                                 declare.ToString (), set.ToString (), exec,
191                                 procedure, BuildParameters (), select.ToString ());
192                 }
193
194                 public override bool Connect (TdsConnectionParameters connectionParameters)
195                 {
196                         if (IsConnected)
197                                 throw new InvalidOperationException ("The connection is already open.");
198         
199                         connectionParms = connectionParameters;
200
201                         SetLanguage (connectionParameters.Language);
202                         SetCharset ("utf-8");
203                 
204                         byte[] empty = new byte[0];
205                         short authLen = 0;
206                         byte pad = (byte) 0;
207                         
208                         byte[] domainMagic = { 6, 0x7d, 0x0f, 0xfd, 0xff, 0x0, 0x0, 0x0,
209                                                                         0x0, 0xe0, 0x83, 0x0, 0x0,
210                                                                         0x68, 0x01, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00 };
211                         byte[] sqlserverMagic = { 6, 0x0, 0x0, 0x0,
212                                                                                 0x0, 0x0, 0x0, 0x0,
213                                                                                 0x0, 0xe0, 0x03, 0x0,
214                                                                                 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
215                                                                                 0x0, 0x0, 0x0 };
216                         byte[] magic = null;
217                         
218                         if (connectionParameters.DomainLogin)
219                                 magic = domainMagic;
220                         else
221                                 magic = sqlserverMagic;
222                         
223                         string username = connectionParameters.User;
224                         string domain = null;
225
226                         int idx = username.IndexOf ("\\");
227                         if (idx != -1) {
228                                 domain = username.Substring (0, idx);
229                                 username = username.Substring (idx + 1);
230
231                                 connectionParameters.DefaultDomain = domain;
232                                 connectionParameters.User = username;
233                         } else {
234                                 domain = Environment.UserDomainName;
235                                 connectionParameters.DefaultDomain = domain;
236                         }
237
238                         short partialPacketSize = (short) (86 + (
239                                 connectionParameters.Hostname.Length +
240                                 connectionParameters.ApplicationName.Length +
241                                 DataSource.Length +
242                                 connectionParameters.LibraryName.Length +
243                                 Language.Length +
244                                 connectionParameters.Database.Length +
245                                 connectionParameters.AttachDBFileName.Length) * 2);
246
247                         if (connectionParameters.DomainLogin) {
248                                 authLen = ((short) (32 + (connectionParameters.Hostname.Length +
249                                         domain.Length)));
250                                 partialPacketSize += authLen;
251                         } else
252                                 partialPacketSize += ((short) ((username.Length + connectionParameters.Password.Length) * 2));
253                         
254                         int totalPacketSize = partialPacketSize;
255                         
256                         Comm.StartPacket (TdsPacketType.Logon70);
257                         
258                         Comm.Append (totalPacketSize);
259
260                         //Comm.Append (empty, 3, pad);
261                         //byte[] version = {0x00, 0x0, 0x0, 0x71};
262                         //Console.WriteLine ("Version: {0}", ClientVersion[3]);
263                         Comm.Append (ClientVersion); // TDS Version 7
264                         Comm.Append ((int)this.PacketSize); // Set the Block Size
265                         Comm.Append (empty, 3, pad);
266                         Comm.Append (magic);
267
268                         short curPos = 86;
269
270                         // Hostname
271                         Comm.Append (curPos);
272                         Comm.Append ((short) connectionParameters.Hostname.Length);
273                         curPos += (short) (connectionParameters.Hostname.Length * 2);
274
275                         if (connectionParameters.DomainLogin) {
276                                 Comm.Append((short)0);
277                                 Comm.Append((short)0);
278                                 Comm.Append((short)0);
279                                 Comm.Append((short)0);
280                         } else {
281                                 // Username
282                                 Comm.Append (curPos);
283                                 Comm.Append ((short) username.Length);
284                                 curPos += ((short) (username.Length * 2));
285
286                                 // Password
287                                 Comm.Append (curPos);
288                                 Comm.Append ((short) connectionParameters.Password.Length);
289                                 curPos += (short) (connectionParameters.Password.Length * 2);
290                         }
291
292                         // AppName
293                         Comm.Append (curPos);
294                         Comm.Append ((short) connectionParameters.ApplicationName.Length);
295                         curPos += (short) (connectionParameters.ApplicationName.Length * 2);
296
297                         // Server Name
298                         Comm.Append (curPos);
299                         Comm.Append ((short) DataSource.Length);
300                         curPos += (short) (DataSource.Length * 2);
301
302                         // Unknown
303                         Comm.Append ((short) curPos);
304                         Comm.Append ((short) 0);
305
306                         // Library Name
307                         Comm.Append (curPos);
308                         Comm.Append ((short) connectionParameters.LibraryName.Length);
309                         curPos += (short) (connectionParameters.LibraryName.Length * 2);
310
311                         // Language
312                         Comm.Append (curPos);
313                         Comm.Append ((short) Language.Length);
314                         curPos += (short) (Language.Length * 2);
315
316                         // Database
317                         Comm.Append (curPos);
318                         Comm.Append ((short) connectionParameters.Database.Length);
319                         curPos += (short) (connectionParameters.Database.Length * 2);
320
321                         // MAC Address
322                         Comm.Append((byte) 0);
323                         Comm.Append((byte) 0);
324                         Comm.Append((byte) 0);
325                         Comm.Append((byte) 0);
326                         Comm.Append((byte) 0);
327                         Comm.Append((byte) 0);
328
329                         // Authentication Stuff
330                         Comm.Append ((short) curPos);
331                         if (connectionParameters.DomainLogin) {
332                                 Comm.Append ((short) authLen);
333                                 curPos += (short) authLen;
334                         } else
335                                 Comm.Append ((short) 0);
336                         
337                         // Unknown
338                         Comm.Append (curPos);
339                         Comm.Append ((short)( connectionParameters.AttachDBFileName.Length));
340                         curPos += (short)(connectionParameters.AttachDBFileName.Length*2);
341                         
342                         // Connection Parameters
343                         Comm.Append (connectionParameters.Hostname);
344                         if (!connectionParameters.DomainLogin) {
345                                 // SQL Server Authentication
346                                 Comm.Append (connectionParameters.User);
347                                 string scrambledPwd = EncryptPassword (connectionParameters.Password);
348                                 Comm.Append (scrambledPwd);
349                         }
350                         Comm.Append (connectionParameters.ApplicationName);
351                         Comm.Append (DataSource);
352                         Comm.Append (connectionParameters.LibraryName);
353                         Comm.Append (Language);
354                         Comm.Append (connectionParameters.Database);
355
356                         if (connectionParameters.DomainLogin) {
357                                 // the rest of the packet is NTLMSSP authentication
358                                 Type1Message msg = new Type1Message ();
359                                 msg.Domain = domain;
360                                 msg.Host = connectionParameters.Hostname;
361                                 msg.Flags = NtlmFlags.NegotiateUnicode |
362                                         NtlmFlags.NegotiateNtlm |
363                                         NtlmFlags.NegotiateDomainSupplied |
364                                         NtlmFlags.NegotiateWorkstationSupplied |
365                                         NtlmFlags.NegotiateAlwaysSign; // 0xb201
366                                 Comm.Append (msg.GetBytes ());
367                         }
368
369                         Comm.Append (connectionParameters.AttachDBFileName);
370                         Comm.SendPacket ();
371                         MoreResults = true;
372                         SkipToEnd ();
373                         
374                         return IsConnected;
375                 }
376
377                 private static string EncryptPassword (string pass)
378                 {
379                         int xormask = 0x5a5a;
380                         int len = pass.Length;
381                         char[] chars = new char[len];
382
383                         for (int i = 0; i < len; ++i) {
384                                 int c = ((int) (pass[i])) ^ xormask;
385                                 int m1 = (c >> 4) & 0x0f0f;
386                                 int m2 = (c << 4) & 0xf0f0;
387                                 chars[i] = (char) (m1 | m2);
388                         }
389
390                         return new String (chars);
391                 }
392
393                 public override bool Reset ()
394                 {
395                         // Check validity of the connection - a false removes
396                         // the connection from the pool
397                         // NOTE: MS implementation will throw a connection-reset error as it will
398                         // try to use the same connection
399                         if (!Comm.IsConnected ())
400                                 return false;
401
402                         // Set "reset-connection" bit for the next message packet
403                         Comm.ResetConnection = true;
404                         base.Reset ();
405                         return true;
406                 }
407
408                 public override void ExecPrepared (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
409                 {
410                         Parameters = parameters;
411                         ExecuteQuery (BuildPreparedQuery (commandText), timeout, wantResults);
412                 }
413                         
414                 public override void ExecProc (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
415                 {
416                         Parameters = parameters;
417                         ExecRPC (commandText, parameters, timeout, wantResults);
418                 }
419
420                 private void WriteRpcParameterInfo (TdsMetaParameterCollection parameters)
421                 {
422                         if (parameters != null) {
423                                 foreach (TdsMetaParameter param in parameters) {
424                                         if (param.Direction == TdsParameterDirection.ReturnValue) 
425                                                 continue;
426                                         string pname = param.ParameterName;
427                                         if (pname != null && pname.Length > 0 && pname [0] == '@') {
428                                                 Comm.Append ( (byte) pname.Length);
429                                                 Comm.Append (pname);
430                                         } else {
431                                                 Comm.Append ( (byte) (pname.Length + 1));
432                                                 Comm.Append ("@" + pname);
433                                         }
434                                         short status = 0; // unused
435                                         if (param.Direction != TdsParameterDirection.Input)
436                                                 status |= 0x01; // output
437                                         Comm.Append ( (byte) status);
438                                         WriteParameterInfo (param);
439                                 }
440                         }
441                 }
442                 
443                 private void WritePreparedParameterInfo (TdsMetaParameterCollection parameters)
444                 {
445                         if (parameters == null)
446                                 return;
447                         
448                         string param = BuildPreparedParameters ();
449                         Comm.Append ((byte) 0x00); // no param meta data name
450                         Comm.Append ((byte) 0x00); // no status flags
451                         
452                         // Type_info - parameter info
453                         WriteParameterInfo (new TdsMetaParameter ("prep_params", 
454                                                                   param.Length > 4000 ? "ntext" : "nvarchar", 
455                                                                   param));
456                 }
457                 
458                 protected void ExecRPC (TdsRpcProcId rpcId, string sql, 
459                                         TdsMetaParameterCollection parameters, 
460                                         int timeout, bool wantResults)
461                 {
462                         // clean up
463                         InitExec ();
464                         Comm.StartPacket (TdsPacketType.RPC);
465                         
466                         Comm.Append ((ushort) 0xFFFF);
467                         Comm.Append ((ushort) rpcId);
468                         Comm.Append ((short) 0x02); // no meta data
469                         
470                         Comm.Append ((byte) 0x00); // no param meta data name
471                         Comm.Append ((byte) 0x00); // no status flags
472                         
473                         // Write sql as a parameter value - UCS2
474                         TdsMetaParameter param = new TdsMetaParameter ("sql", 
475                                                                        sql.Length > 4000 ? "ntext":"nvarchar",
476                                                                        sql);            
477                         WriteParameterInfo (param);
478                         
479                         // Write Parameter infos - name and type
480                         WritePreparedParameterInfo (parameters);
481
482                         // Write parameter/value info
483                         WriteRpcParameterInfo (parameters);
484                         Comm.SendPacket ();
485                         CheckForData (timeout);
486                         if (!wantResults)
487                                 SkipToEnd ();
488                 }
489                 
490                 protected override void ExecRPC (string rpcName, TdsMetaParameterCollection parameters, 
491                                                  int timeout, bool wantResults)
492                 {
493                         // clean up
494                         InitExec ();
495                         Comm.StartPacket (TdsPacketType.RPC);
496
497                         Comm.Append ( (short) rpcName.Length);
498                         Comm.Append (rpcName);
499                         Comm.Append ( (short) 0); //no meta data
500                         WriteRpcParameterInfo (parameters);
501                         Comm.SendPacket ();
502                         CheckForData (timeout);
503                         if (!wantResults)
504                                 SkipToEnd ();
505                 }
506
507                 private void WriteParameterInfo (TdsMetaParameter param)
508                 {
509                         /*
510                         Ms.net send non-nullable datatypes as nullable and allows setting null values
511                         to int/float etc.. So, using Nullable form of type for all data
512                         */
513                         param.IsNullable = true;
514                         TdsColumnType colType = param.GetMetaType ();
515                         param.IsNullable = false;
516
517                         bool partLenType = false;
518                         int size = param.Size;
519                         if (size < 1) {
520                                 if (size < 0)
521                                         partLenType = true;
522                                 size = param.GetActualSize ();
523                         }
524
525                         // Change colType according to the following table
526                         /* 
527                          * Original Type        Maxlen          New Type 
528                          * 
529                          * NVarChar             4000 UCS2       NText
530                          * BigVarChar           8000 ASCII      Text
531                          * BigVarBinary         8000 bytes      Image
532                          * 
533                          */
534                         TdsColumnType origColType = colType;
535                         if (colType == TdsColumnType.BigNVarChar) {
536                                 // param.GetActualSize() returns len*2
537                                 if (size == param.Size)
538                                         size <<= 1;
539                                 if ((size >> 1) > 4000)
540                                         colType = TdsColumnType.NText;
541                         } else if (colType == TdsColumnType.BigVarChar) {
542                                 if (size > 8000)
543                                         colType = TdsColumnType.Text;   
544                         } else if (colType == TdsColumnType.BigVarBinary) {
545                                 if (size > 8000)
546                                         colType = TdsColumnType.Image;
547                         }
548                         // Calculation of TypeInfo field
549                         /* 
550                          * orig size value              TypeInfo field
551                          * 
552                          * >= 0 <= Maxlen               origColType + content len
553                          * > Maxlen             NewType as per above table + content len
554                          * -1           origColType + USHORTMAXLEN (0xFFFF) + content len (TDS 9)
555                          * 
556                          */
557                         // Write updated colType, iff partLenType == false
558                         if (TdsVersion > TdsVersion.tds81 && partLenType) {
559                                 Comm.Append ((byte)origColType);
560                                 Comm.Append ((short)-1);
561                         } else if (ServerTdsVersion > TdsVersion.tds70 
562                                    && origColType == TdsColumnType.Decimal) {
563                                 Comm.Append ((byte)TdsColumnType.Numeric);
564                         } else {
565                                 Comm.Append ((byte)colType);
566                         }
567
568                         if (IsLargeType (colType))
569                                 Comm.Append ((short)size); // Parameter size passed in SqlParameter
570                         else if (IsBlobType (colType))
571                                 Comm.Append (size); // Parameter size passed in SqlParameter
572                         else
573                                 Comm.Append ((byte)size);
574
575                         // Precision and Scale are non-zero for only decimal/numeric
576                         if ( param.TypeName == "decimal" || param.TypeName == "numeric") {
577                                 Comm.Append ((param.Precision !=0 ) ? param.Precision : Precision);
578                                 Comm.Append (param.Scale);
579                                 // Convert the decimal value according to Scale
580                                 if (param.Value != null && param.Value != DBNull.Value &&
581                                     ((decimal)param.Value) != Decimal.MaxValue && 
582                                     ((decimal)param.Value) != Decimal.MinValue &&
583                                     ((decimal)param.Value) != long.MaxValue &&
584                                     ((decimal)param.Value) != long.MinValue &&
585                                     ((decimal)param.Value) != ulong.MaxValue &&
586                                     ((decimal)param.Value) != ulong.MinValue) {
587                                         long expo = (long)new Decimal (System.Math.Pow (10, (double)param.Scale));
588                                         long pVal = (long)(((decimal)param.Value) * expo);
589                                         param.Value = pVal;                             
590                                 }
591                         }
592
593                         
594                         /* VARADHAN: TDS 8 Debugging */
595                         /*
596                         if (Collation != null) {
597                                 Console.WriteLine ("Collation is not null");
598                                 Console.WriteLine ("Column Type: {0}", colType);
599                                 Console.WriteLine ("Collation bytes: {0} {1} {2} {3} {4}", Collation[0], Collation[1], Collation[2],
600                                                    Collation[3], Collation[4]);
601                         } else {
602                                 Console.WriteLine ("Collation is null");
603                         }
604                         */
605                         
606                         // Tds > 7.0 uses collation
607                         if (Collation != null && 
608                             (colType == TdsColumnType.BigChar || colType == TdsColumnType.BigNVarChar ||
609                              colType == TdsColumnType.BigVarChar || colType == TdsColumnType.NChar ||
610                              colType == TdsColumnType.NVarChar || colType == TdsColumnType.Text ||
611                              colType == TdsColumnType.NText))
612                                 Comm.Append (Collation);
613
614                         // LAMESPEC: size should be 0xFFFF for any bigvarchar, bignvarchar and bigvarbinary 
615                         // types if param value is NULL
616                         if ((colType == TdsColumnType.BigVarChar || 
617                              colType == TdsColumnType.BigNVarChar ||
618                              colType == TdsColumnType.BigVarBinary ||
619                              colType == TdsColumnType.Image) && 
620                             (param.Value == null || param.Value == DBNull.Value))
621                                 size = -1;
622                         else
623                                 size = param.GetActualSize ();
624
625                         if (IsLargeType (colType))
626                                 Comm.Append ((short)size); 
627                         else if (IsBlobType (colType))
628                                 Comm.Append (size); 
629                         else
630                                 Comm.Append ((byte)size);
631                         
632                         if (size > 0) {
633                                 switch (param.TypeName) {
634                                 case "money" : {
635                                         // 4 == SqlMoney::MoneyFormat.NumberDecimalDigits
636                                         Decimal val = Decimal.Round ((decimal) param.Value, 4);
637                                         int[] arr = Decimal.GetBits (val);
638
639                                         if (val >= 0) {
640                                                 Comm.Append (arr[1]);
641                                                 Comm.Append (arr[0]);
642                                         } else {
643                                                 Comm.Append (~arr[1]);
644                                                 Comm.Append (~arr[0] + 1);
645                                         }
646                                         break;
647                                 }
648                                 case "smallmoney": {
649                                         // 4 == SqlMoney::MoneyFormat.NumberDecimalDigits
650                                         Decimal val = Decimal.Round ((decimal) param.Value, 4);
651                                         if (val < SMALLMONEY_MIN || val > SMALLMONEY_MAX)
652                                                 throw new OverflowException (string.Format (
653                                                         CultureInfo.InvariantCulture,
654                                                         "Value '{0}' is not valid for SmallMoney."
655                                                         + "  Must be between {1:N4} and {2:N4}.",
656 #if NET_2_0
657                                                         val,
658 #else
659                                                         val.ToString (CultureInfo.CurrentCulture),
660 #endif
661                                                         SMALLMONEY_MIN, SMALLMONEY_MAX));
662
663                                         int[] arr = Decimal.GetBits (val);
664                                         int sign = (val>0 ? 1: -1);
665                                         Comm.Append (sign * arr[0]);
666                                         break;
667                                 }
668                                 case "datetime":
669                                         Comm.Append ((DateTime)param.Value, 8);
670                                         break;
671                                 case "smalldatetime":
672                                         Comm.Append ((DateTime)param.Value, 4);
673                                         break;
674                                 case "varchar" :
675                                 case "nvarchar" :
676                                 case "char" :
677                                 case "nchar" :
678                                 case "text" :
679                                 case "ntext" :
680                                         byte [] tmp = param.GetBytes ();
681                                         Comm.Append (tmp);
682                                         break;
683                                 case "uniqueidentifier" :
684                                         Comm.Append (((Guid)param.Value).ToByteArray());
685                                         break;
686                                 default :
687                                         Comm.Append (param.Value);
688                                         break;
689                                 }
690                         }
691                         return;
692                 }
693
694                 public override void Execute (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
695                 {
696                         Parameters = parameters;
697                         string sql = commandText;
698                         if (wantResults || (Parameters != null && Parameters.Count > 0))
699                                 sql = BuildExec (commandText);
700                         ExecuteQuery (sql, timeout, wantResults);
701                 }
702
703                 private string FormatParameter (TdsMetaParameter parameter)
704                 {
705                         string parameterName = parameter.ParameterName;
706                         if (parameterName [0] == '@') {
707                                 parameterName = parameterName.Substring (1);
708                         }
709                         if (parameter.Direction == TdsParameterDirection.Output)
710                                 return String.Format ("@{0}=@{0} output", parameterName);
711                         if (parameter.Value == null || parameter.Value == DBNull.Value)
712                                 return String.Format ("@{0}=NULL", parameterName);
713
714                         string value = null;
715                         switch (parameter.TypeName) {
716                         case "smalldatetime":
717                         case "datetime":
718                                 DateTime d = Convert.ToDateTime (parameter.Value);
719                                 value = String.Format (base.Locale,
720                                         "'{0:MMM dd yyyy hh:mm:ss.fff tt}'", d);
721                                 break;
722                         case "bigint":
723                         case "decimal":
724                         case "float":
725                         case "int":
726                         case "money":
727                         case "real":
728                         case "smallint":
729                         case "smallmoney":
730                         case "tinyint":
731                                 object paramValue = parameter.Value;
732                                 Type paramType = paramValue.GetType ();
733                                 if (paramType.IsEnum)
734                                         paramValue = Convert.ChangeType (paramValue,
735                                                 Type.GetTypeCode (paramType));
736                                 value = paramValue.ToString ();
737                                 break;
738                         case "nvarchar":
739                         case "nchar":
740                                 value = String.Format ("N'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
741                                 break;
742                         case "uniqueidentifier":
743                                 value = String.Format ("'{0}'", ((Guid) parameter.Value).ToString (string.Empty));
744                                 break;
745                         case "bit":
746                                 if (parameter.Value.GetType () == typeof (bool))
747                                         value = (((bool) parameter.Value) ? "0x1" : "0x0");
748                                 else
749                                         value = parameter.Value.ToString ();
750                                 break;
751                         case "image":
752                         case "binary":
753                         case "varbinary":
754                                 byte[] byteArray = (byte[]) parameter.Value;
755                                 // In 1.0 profile, BitConverter.ToString() throws ArgumentOutOfRangeException when passed a 0-length
756                                 // array, so handle that as a special case.
757                                 if (byteArray.Length == 0)
758                                         value = "0x";
759                                 else
760                                         value = String.Format ("0x{0}", BitConverter.ToString (byteArray).Replace ("-", string.Empty).ToLower ());
761                                 break;
762                         default:
763                                 value = String.Format ("'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
764                                 break;
765                         }
766
767                         return "@" + parameterName + "=" + value;
768                 }
769
770                 public override string Prepare (string commandText, TdsMetaParameterCollection parameters)
771                 {
772                         Parameters = parameters;
773
774                         TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
775                         TdsMetaParameter parm = new TdsMetaParameter ("@Handle", "int", null);
776                         parm.Direction = TdsParameterDirection.Output;
777                         parms.Add (parm);
778
779                         parms.Add (new TdsMetaParameter ("@VarDecl", "nvarchar", BuildPreparedParameters ()));
780                         parms.Add (new TdsMetaParameter ("@Query", "nvarchar", commandText));
781
782                         ExecProc ("sp_prepare", parms, 0, true);
783                         SkipToEnd ();
784                         return OutputParameters[0].ToString () ;
785                         //if (ColumnValues == null || ColumnValues [0] == null || ColumnValues [0] == DBNull.Value)
786                         //      throw new TdsInternalException ();
787                         //return string.Empty;
788                         //return ColumnValues [0].ToString ();
789                 }
790
791                 protected override void ProcessColumnInfo ()
792                 {
793                         int numColumns = Comm.GetTdsShort ();
794                         for (int i = 0; i < numColumns; i += 1) {
795                                 byte[] flagData = new byte[4];
796                                 for (int j = 0; j < 4; j += 1) 
797                                         flagData[j] = Comm.GetByte ();
798
799                                 bool nullable = (flagData[2] & 0x01) > 0;
800                                 //bool caseSensitive = (flagData[2] & 0x02) > 0;
801                                 bool writable = (flagData[2] & 0x0c) > 0;
802                                 bool autoIncrement = (flagData[2] & 0x10) > 0;
803                                 bool isIdentity = (flagData[2] & 0x10) > 0;
804
805                                 TdsColumnType columnType = (TdsColumnType) ((Comm.GetByte () & 0xff));
806
807                                 byte xColumnType = 0;
808                                 if (IsLargeType (columnType)) {
809                                         xColumnType = (byte) columnType;
810                                         if (columnType != TdsColumnType.NChar)
811                                                 columnType -= 128;
812                                 }
813
814                                 int columnSize;
815                                 string tableName = null;
816
817                                 if (IsBlobType (columnType)) {
818                                         columnSize = Comm.GetTdsInt ();
819                                         tableName = Comm.GetString (Comm.GetTdsShort ());
820                                 } else if (IsFixedSizeColumn (columnType)) {
821                                         columnSize = LookupBufferSize (columnType);
822                                 } else if (IsLargeType ((TdsColumnType) xColumnType)) {
823                                         columnSize = Comm.GetTdsShort ();
824                                 } else {
825                                         columnSize = Comm.GetByte () & 0xff;
826                                 }
827
828                                 if (IsWideType ((TdsColumnType) columnType))
829                                         columnSize /= 2;
830
831                                 byte precision = 0;
832                                 byte scale = 0;
833
834                                 if (columnType == TdsColumnType.Decimal || columnType == TdsColumnType.Numeric) {
835                                         precision = Comm.GetByte ();
836                                         scale = Comm.GetByte ();
837                                 } else {
838                                         precision = GetPrecision (columnType, columnSize);
839                                         scale = GetScale (columnType, columnSize);
840                                 }
841
842                                 string columnName = Comm.GetString (Comm.GetByte ());
843
844                                 TdsDataColumn col = new TdsDataColumn ();
845                                 Columns.Add (col);
846 #if NET_2_0
847                                 col.ColumnType = columnType;
848                                 col.ColumnName = columnName;
849                                 col.IsAutoIncrement = autoIncrement;
850                                 col.IsIdentity = isIdentity;
851                                 col.ColumnSize = columnSize;
852                                 col.NumericPrecision = precision;
853                                 col.NumericScale = scale;
854                                 col.IsReadOnly = !writable;
855                                 col.AllowDBNull = nullable;
856                                 col.BaseTableName = tableName;
857                                 col.DataTypeName = Enum.GetName (typeof (TdsColumnType), xColumnType);
858 #else
859                                 col ["ColumnType"] = columnType;
860                                 col ["ColumnName"] = columnName;
861                                 col ["IsAutoIncrement"] = autoIncrement;
862                                 col ["IsIdentity"] = isIdentity;
863                                 col ["ColumnSize"] = columnSize;
864                                 col ["NumericPrecision"] = precision;
865                                 col ["NumericScale"] = scale;
866                                 col ["IsReadOnly"] = !writable;
867                                 col ["AllowDBNull"] = nullable;
868                                 col ["BaseTableName"] = tableName;
869                                 col ["DataTypeName"] = Enum.GetName (typeof (TdsColumnType), xColumnType);
870 #endif
871                         }
872                 }
873
874                 public override void Unprepare (string statementId)
875                 {
876                         TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
877                         parms.Add (new TdsMetaParameter ("@P1", "int", Int32.Parse (statementId)));
878                         ExecProc ("sp_unprepare", parms, 0, false);
879                 }
880                 
881                 protected override bool IsValidRowCount (byte status, byte op)
882                 {
883                         if ((status & (byte)0x10) == 0 || op == (byte)0xc1)
884                                 return false;
885                         return true; 
886                 }
887
888                 protected override void ProcessReturnStatus ()
889                 {
890                         int result = Comm.GetTdsInt ();
891                         if (Parameters != null) {
892                                 foreach (TdsMetaParameter param in Parameters) {
893                                         if (param.Direction == TdsParameterDirection.ReturnValue) {
894                                                 param.Value = result;
895                                                 break;
896                                         }
897                                 }
898                         }
899                 }
900
901                 byte GetScale (TdsColumnType type, int columnSize)
902                 {
903                         switch (type) {
904                         case TdsColumnType.DateTime:
905                                 return 0x03;
906                         case TdsColumnType.DateTime4:
907                                 return 0x00;
908                         case TdsColumnType.DateTimeN:
909                                 switch (columnSize) {
910                                 case 4:
911                                         return 0x00;
912                                 case 8:
913                                         return 0x03;
914                                 }
915                                 break;
916                         default:
917                                 return 0xff;
918                         }
919
920                         throw new NotSupportedException (string.Format (
921                                 CultureInfo.InvariantCulture,
922                                 "Fixed scale not defined for column " +
923                                 "type '{0}' with size {1}.", type, columnSize));
924                 }
925
926                 byte GetPrecision (TdsColumnType type, int columnSize)
927                 {
928                         switch (type) {
929                         case TdsColumnType.Binary:
930                                 return 0xff;
931                         case TdsColumnType.Bit:
932                                 return 0xff;
933                         case TdsColumnType.Char:
934                                 return 0xff;
935                         case TdsColumnType.DateTime:
936                                 return 0x17;
937                         case TdsColumnType.DateTime4:
938                                 return 0x10;
939                         case TdsColumnType.DateTimeN:
940                                 switch (columnSize) {
941                                 case 4:
942                                         return 0x10;
943                                 case 8:
944                                         return 0x17;
945                                 }
946                                 break;
947                         case TdsColumnType.Real:
948                                 return 0x07;
949                         case TdsColumnType.Float8:
950                                 return 0x0f;
951                         case TdsColumnType.FloatN:
952                                 switch (columnSize) {
953                                 case 4:
954                                         return 0x07;
955                                 case 8:
956                                         return 0x0f;
957                                 }
958                                 break;
959                         case TdsColumnType.Image:
960                                 return 0xff;
961                         case TdsColumnType.Int1:
962                                 return 0x03;
963                         case TdsColumnType.Int2:
964                                 return 0x05;
965                         case TdsColumnType.Int4:
966                                 return 0x0a;
967                         case TdsColumnType.IntN:
968                                 switch (columnSize) {
969                                 case 1:
970                                         return 0x03;
971                                 case 2:
972                                         return 0x05;
973                                 case 4:
974                                         return 0x0a;
975                                 }
976                                 break;
977                         case TdsColumnType.Void:
978                                 return 0x01;
979                         case TdsColumnType.Text:
980                                 return 0xff;
981                         case TdsColumnType.UniqueIdentifier:
982                                 return 0xff;
983                         case TdsColumnType.VarBinary:
984                                 return 0xff;
985                         case TdsColumnType.VarChar:
986                                 return 0xff;
987                         case TdsColumnType.Money:
988                                 return 19;
989                         case TdsColumnType.NText:
990                                 return 0xff;
991                         case TdsColumnType.NVarChar:
992                                 return 0xff;
993                         case TdsColumnType.BitN:
994                                 return 0xff;
995                         case TdsColumnType.MoneyN:
996                                 switch (columnSize) {
997                                 case 4:
998                                         return 0x0a;
999                                 case 8:
1000                                         return 0x13;
1001                                 }
1002                                 break;
1003                         case TdsColumnType.Money4:
1004                                 return 0x0a;
1005                         case TdsColumnType.NChar:
1006                                 return 0xff;
1007                         case TdsColumnType.BigBinary:
1008                                 return 0xff;
1009                         case TdsColumnType.BigVarBinary:
1010                                 return 0xff;
1011                         case TdsColumnType.BigVarChar:
1012                                 return 0xff;
1013                         case TdsColumnType.BigNVarChar:
1014                                 return 0xff;
1015                         case TdsColumnType.BigChar:
1016                                 return 0xff;
1017                         case TdsColumnType.SmallMoney:
1018                                 return 0x0a;
1019                         case TdsColumnType.Variant:
1020                                 return 0xff;
1021                         case TdsColumnType.BigInt:
1022                                 return 0xff;
1023                         }
1024
1025                         throw new NotSupportedException (string.Format (
1026                                 CultureInfo.InvariantCulture,
1027                                 "Fixed precision not defined for column " +
1028                                 "type '{0}' with size {1}.", type, columnSize));
1029                 }
1030
1031                 #endregion // Methods
1032
1033 #if NET_2_0
1034                 #region Asynchronous Methods
1035
1036                 public override IAsyncResult BeginExecuteNonQuery (string cmdText,
1037                                                           TdsMetaParameterCollection parameters,
1038                                                           AsyncCallback callback,
1039                                                           object state)
1040                 {
1041                         Parameters = parameters;
1042                         string sql = cmdText;
1043                         if (Parameters != null && Parameters.Count > 0)
1044                                 sql = BuildExec (cmdText);
1045
1046                         IAsyncResult ar = BeginExecuteQueryInternal (sql, false, callback, state);
1047                         return ar;
1048                 }
1049
1050                 public override void EndExecuteNonQuery (IAsyncResult ar)
1051                 {
1052                         EndExecuteQueryInternal (ar);
1053                 }
1054
1055                 public override IAsyncResult BeginExecuteQuery (string cmdText,
1056                                                                 TdsMetaParameterCollection parameters,
1057                                                                 AsyncCallback callback,
1058                                                                 object state)
1059                 {
1060                         Parameters = parameters;
1061                         string sql = cmdText;
1062                         if (Parameters != null && Parameters.Count > 0)
1063                                 sql = BuildExec (cmdText);
1064
1065                         IAsyncResult ar = BeginExecuteQueryInternal (sql, true, callback, state);
1066                         return ar;
1067                 }
1068
1069                 public override void EndExecuteQuery (IAsyncResult ar)
1070                 {
1071                         EndExecuteQueryInternal (ar);
1072                 }
1073
1074                 public override IAsyncResult BeginExecuteProcedure (string prolog,
1075                                                                     string epilog,
1076                                                                     string cmdText,
1077                                                                     bool IsNonQuery,
1078                                                                     TdsMetaParameterCollection parameters,
1079                                                                     AsyncCallback callback,
1080                                                                     object state)
1081                 {
1082                         Parameters = parameters;
1083                         string pcall = BuildProcedureCall (cmdText);
1084                         string sql = String.Format ("{0};{1};{2};", prolog, pcall, epilog);
1085
1086                         IAsyncResult ar = BeginExecuteQueryInternal (sql, !IsNonQuery, callback, state);
1087                         return ar;
1088                 }
1089
1090                 public override void EndExecuteProcedure (IAsyncResult ar)
1091                 {
1092                         EndExecuteQueryInternal (ar);
1093                 }
1094
1095                 #endregion // Asynchronous Methods
1096 #endif // NET_2_0
1097         }
1098 }