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