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