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")] Tds70(string server, int port)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")] Tds70(string server, int port, int packetSize, int timeout)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")] Tds70(string server, int port, int packetSize, int timeout, TdsVersion version)71 public Tds70 (string server, int port, int packetSize, int timeout, TdsVersion version) 72 : this (server, port, packetSize, timeout, 0, version) 73 { 74 } 75 Tds70(string server, int port, int lifetime)76 public Tds70 (string server, int port, int lifetime) 77 : this (server, port, 512, 15, lifetime) 78 { 79 } 80 Tds70(string server, int port, int packetSize, int timeout, int lifeTime)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 Tds70(string server, int port, int packetSize, int timeout, int lifeTime, TdsVersion version)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 BuildExec(string sql)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 BuildParameters()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 BuildPreparedParameters()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 || p.Direction == TdsParameterDirection.InputOutput) 155 parms.Append (" output"); 156 } 157 return parms.ToString (); 158 } 159 BuildPreparedQuery(string id)160 private string BuildPreparedQuery (string id) 161 { 162 return BuildProcedureCall (String.Format ("sp_execute {0},", id)); 163 } 164 BuildProcedureCall(string procedure)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 Connect(TdsConnectionParameters connectionParameters)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 EncryptPassword(SecureString secPass)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 Reset()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 ExecPrepared(string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)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 ExecProc(string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)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 WriteRpcParameterInfo(TdsMetaParameterCollection parameters)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 WritePreparedParameterInfo(TdsMetaParameterCollection parameters)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 ExecRPC(TdsRpcProcId rpcId, string sql, TdsMetaParameterCollection parameters, int timeout, bool wantResults)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 ExecRPC(string rpcName, TdsMetaParameterCollection parameters, int timeout, bool wantResults)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 WriteParameterInfo(TdsMetaParameter param)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 } else if (colType == TdsColumnType.DateTime2 || 590 colType == TdsColumnType.DateTimeOffset) { 591 // HACK: Wire-level DateTime{2,Offset} 592 // require TDS 7.3, which this driver 593 // does not implement correctly--so we 594 // serialize to ASCII instead. 595 colType = TdsColumnType.Char; 596 } 597 // Calculation of TypeInfo field 598 /* 599 * orig size value TypeInfo field 600 * 601 * >= 0 <= Maxlen origColType + content len 602 * > Maxlen NewType as per above table + content len 603 * -1 origColType + USHORTMAXLEN (0xFFFF) + content len (TDS 9) 604 * 605 */ 606 // Write updated colType, iff partLenType == false 607 if (TdsVersion > TdsVersion.tds81 && partLenType) { 608 Comm.Append ((byte)origColType); 609 Comm.Append ((short)-1); 610 } else if (ServerTdsVersion > TdsVersion.tds70 611 && origColType == TdsColumnType.Decimal) { 612 Comm.Append ((byte)TdsColumnType.Numeric); 613 } else { 614 Comm.Append ((byte)colType); 615 } 616 617 if (IsLargeType (colType)) 618 Comm.Append ((short)size); // Parameter size passed in SqlParameter 619 else if (IsBlobType (colType)) 620 Comm.Append (size); // Parameter size passed in SqlParameter 621 else 622 Comm.Append ((byte)size); 623 624 // Precision and Scale are non-zero for only decimal/numeric 625 if ( param.TypeName == "decimal" || param.TypeName == "numeric") { 626 Comm.Append ((param.Precision !=0 ) ? param.Precision : Precision); 627 Comm.Append (param.Scale); 628 // Convert the decimal value according to Scale 629 if (param.Value != null && param.Value != DBNull.Value && 630 ((decimal)param.Value) != Decimal.MaxValue && 631 ((decimal)param.Value) != Decimal.MinValue && 632 ((decimal)param.Value) != long.MaxValue && 633 ((decimal)param.Value) != long.MinValue && 634 ((decimal)param.Value) != ulong.MaxValue && 635 ((decimal)param.Value) != ulong.MinValue) { 636 long expo = (long)new Decimal (System.Math.Pow (10, (double)param.Scale)); 637 long pVal = (long)(((decimal)param.Value) * expo); 638 param.Value = pVal; 639 } 640 } 641 642 643 /* VARADHAN: TDS 8 Debugging */ 644 /* 645 if (Collation != null) { 646 Console.WriteLine ("Collation is not null"); 647 Console.WriteLine ("Column Type: {0}", colType); 648 Console.WriteLine ("Collation bytes: {0} {1} {2} {3} {4}", Collation[0], Collation[1], Collation[2], 649 Collation[3], Collation[4]); 650 } else { 651 Console.WriteLine ("Collation is null"); 652 } 653 */ 654 655 // Tds > 7.0 uses collation 656 if (Collation != null && 657 (colType == TdsColumnType.BigChar || colType == TdsColumnType.BigNVarChar || 658 colType == TdsColumnType.BigVarChar || colType == TdsColumnType.NChar || 659 colType == TdsColumnType.NVarChar || colType == TdsColumnType.Text || 660 colType == TdsColumnType.NText)) 661 Comm.Append (Collation); 662 663 // LAMESPEC: size should be 0xFFFF for any bigvarchar, bignvarchar and bigvarbinary 664 // types if param value is NULL 665 if ((colType == TdsColumnType.BigVarChar || 666 colType == TdsColumnType.BigNVarChar || 667 colType == TdsColumnType.BigVarBinary || 668 colType == TdsColumnType.Image) && 669 (param.Value == null || param.Value == DBNull.Value)) 670 size = -1; 671 else 672 size = param.GetActualSize (); 673 674 if (IsLargeType (colType)) 675 Comm.Append ((short)size); 676 else if (IsBlobType (colType)) 677 Comm.Append (size); 678 else 679 Comm.Append ((byte)size); 680 681 if (size > 0) { 682 switch (param.TypeName) { 683 case "money" : { 684 // 4 == SqlMoney::MoneyFormat.NumberDecimalDigits 685 Decimal val = Decimal.Round ((decimal) param.Value, 4); 686 int[] arr = Decimal.GetBits (val); 687 688 if (val >= 0) { 689 Comm.Append (arr[1]); 690 Comm.Append (arr[0]); 691 } else { 692 Comm.Append (~arr[1]); 693 Comm.Append (~arr[0] + 1); 694 } 695 break; 696 } 697 case "smallmoney": { 698 // 4 == SqlMoney::MoneyFormat.NumberDecimalDigits 699 Decimal val = Decimal.Round ((decimal) param.Value, 4); 700 if (val < SMALLMONEY_MIN || val > SMALLMONEY_MAX) 701 throw new OverflowException (string.Format ( 702 CultureInfo.InvariantCulture, 703 "Value '{0}' is not valid for SmallMoney." 704 + " Must be between {1:N4} and {2:N4}.", 705 val, 706 SMALLMONEY_MIN, SMALLMONEY_MAX)); 707 708 int[] arr = Decimal.GetBits (val); 709 int sign = (val>0 ? 1: -1); 710 Comm.Append (sign * arr[0]); 711 break; 712 } 713 case "datetime": 714 Comm.Append ((DateTime)param.Value, 8); 715 break; 716 case "smalldatetime": 717 Comm.Append ((DateTime)param.Value, 4); 718 break; 719 case "varchar" : 720 case "nvarchar" : 721 case "char" : 722 case "nchar" : 723 case "text" : 724 case "ntext" : 725 case "datetime2": 726 case "datetimeoffset": 727 byte [] tmp = param.GetBytes (); 728 Comm.Append (tmp); 729 break; 730 case "uniqueidentifier" : 731 Comm.Append (((Guid)param.Value).ToByteArray()); 732 break; 733 default : 734 Comm.Append (param.Value); 735 break; 736 } 737 } 738 return; 739 } 740 Execute(string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)741 public override void Execute (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults) 742 { 743 Parameters = parameters; 744 string sql = commandText; 745 if (wantResults || (Parameters != null && Parameters.Count > 0)) 746 sql = BuildExec (commandText); 747 ExecuteQuery (sql, timeout, wantResults); 748 } 749 FormatParameter(TdsMetaParameter parameter)750 private string FormatParameter (TdsMetaParameter parameter) 751 { 752 string parameterName = parameter.ParameterName; 753 if (parameterName [0] == '@') { 754 parameterName = parameterName.Substring (1); 755 } 756 if (parameter.Direction == TdsParameterDirection.Output) 757 return String.Format ("@{0}=@{0} output", parameterName); 758 if (parameter.Value == null || parameter.Value == DBNull.Value) 759 return String.Format ("@{0}=NULL", parameterName); 760 761 string value = null; 762 switch (parameter.TypeName) { 763 case "smalldatetime": 764 case "datetime": 765 DateTime d = Convert.ToDateTime (parameter.Value); 766 value = String.Format (base.Locale, 767 "'{0:MMM dd yyyy hh:mm:ss.fff tt}'", d); 768 break; 769 case "bigint": 770 case "decimal": 771 case "float": 772 case "int": 773 case "money": 774 case "real": 775 case "smallint": 776 case "smallmoney": 777 case "tinyint": 778 object paramValue = parameter.Value; 779 Type paramType = paramValue.GetType (); 780 if (paramType.IsEnum) 781 paramValue = Convert.ChangeType (paramValue, 782 Type.GetTypeCode (paramType)); 783 value = paramValue.ToString (); 784 break; 785 case "nvarchar": 786 case "nchar": 787 value = String.Format ("N'{0}'", parameter.Value.ToString ().Replace ("'", "''")); 788 break; 789 case "uniqueidentifier": 790 value = String.Format ("'{0}'", ((Guid) parameter.Value).ToString (string.Empty)); 791 break; 792 case "bit": 793 if (parameter.Value.GetType () == typeof (bool)) 794 value = (((bool) parameter.Value) ? "0x1" : "0x0"); 795 else 796 value = parameter.Value.ToString (); 797 break; 798 case "image": 799 case "binary": 800 case "varbinary": 801 byte[] byteArray = (byte[]) parameter.Value; 802 // In 1.0 profile, BitConverter.ToString() throws ArgumentOutOfRangeException when passed a 0-length 803 // array, so handle that as a special case. 804 if (byteArray.Length == 0) 805 value = "0x"; 806 else 807 value = String.Format ("0x{0}", BitConverter.ToString (byteArray).Replace ("-", string.Empty).ToLower ()); 808 break; 809 default: 810 value = String.Format ("'{0}'", parameter.Value.ToString ().Replace ("'", "''")); 811 break; 812 } 813 814 return "@" + parameterName + "=" + value; 815 } 816 Prepare(string commandText, TdsMetaParameterCollection parameters)817 public override string Prepare (string commandText, TdsMetaParameterCollection parameters) 818 { 819 Parameters = parameters; 820 821 TdsMetaParameterCollection parms = new TdsMetaParameterCollection (); 822 // Tested with MS SQL 2008 RC2 Express and MS SQL 2012 Express: 823 // You may pass either -1 or 0, but not null as initial value of @Handle, 824 // which is an output parameter. 825 TdsMetaParameter parm = new TdsMetaParameter ("@Handle", "int", -1); 826 parm.Direction = TdsParameterDirection.Output; 827 parms.Add (parm); 828 829 parms.Add (new TdsMetaParameter ("@VarDecl", "nvarchar", BuildPreparedParameters ())); 830 parms.Add (new TdsMetaParameter ("@Query", "nvarchar", commandText)); 831 832 ExecProc ("sp_prepare", parms, 0, true); 833 SkipToEnd (); 834 return OutputParameters[0].ToString () ; 835 //if (ColumnValues == null || ColumnValues [0] == null || ColumnValues [0] == DBNull.Value) 836 // throw new TdsInternalException (); 837 //return string.Empty; 838 //return ColumnValues [0].ToString (); 839 } 840 ProcessColumnInfo()841 protected override void ProcessColumnInfo () 842 { 843 int numColumns = Comm.GetTdsShort (); 844 for (int i = 0; i < numColumns; i += 1) { 845 byte[] flagData = new byte[4]; 846 for (int j = 0; j < 4; j += 1) 847 flagData[j] = Comm.GetByte (); 848 849 bool nullable = (flagData[2] & 0x01) > 0; 850 //bool caseSensitive = (flagData[2] & 0x02) > 0; 851 bool writable = (flagData[2] & 0x0c) > 0; 852 bool autoIncrement = (flagData[2] & 0x10) > 0; 853 bool isIdentity = (flagData[2] & 0x10) > 0; 854 855 TdsColumnType columnType = (TdsColumnType) ((Comm.GetByte () & 0xff)); 856 857 byte xColumnType = 0; 858 if (IsLargeType (columnType)) { 859 xColumnType = (byte) columnType; 860 if (columnType != TdsColumnType.NChar) 861 columnType -= 128; 862 } 863 864 int columnSize; 865 string tableName = null; 866 867 if (IsBlobType (columnType)) { 868 columnSize = Comm.GetTdsInt (); 869 tableName = Comm.GetString (Comm.GetTdsShort ()); 870 } else if (IsFixedSizeColumn (columnType)) { 871 columnSize = LookupBufferSize (columnType); 872 } else if (IsLargeType ((TdsColumnType) xColumnType)) { 873 columnSize = Comm.GetTdsShort (); 874 } else { 875 columnSize = Comm.GetByte () & 0xff; 876 } 877 878 if (IsWideType ((TdsColumnType) columnType)) 879 columnSize /= 2; 880 881 byte precision = 0; 882 byte scale = 0; 883 884 if (columnType == TdsColumnType.Decimal || columnType == TdsColumnType.Numeric) { 885 precision = Comm.GetByte (); 886 scale = Comm.GetByte (); 887 } else { 888 precision = GetPrecision (columnType, columnSize); 889 scale = GetScale (columnType, columnSize); 890 } 891 892 string columnName = Comm.GetString (Comm.GetByte ()); 893 894 TdsDataColumn col = new TdsDataColumn (); 895 Columns.Add (col); 896 col.ColumnType = columnType; 897 col.ColumnName = columnName; 898 col.IsAutoIncrement = autoIncrement; 899 col.IsIdentity = isIdentity; 900 col.ColumnSize = columnSize; 901 col.NumericPrecision = precision; 902 col.NumericScale = scale; 903 col.IsReadOnly = !writable; 904 col.AllowDBNull = nullable; 905 col.BaseTableName = tableName; 906 col.DataTypeName = Enum.GetName (typeof (TdsColumnType), xColumnType); 907 } 908 } 909 Unprepare(string statementId)910 public override void Unprepare (string statementId) 911 { 912 TdsMetaParameterCollection parms = new TdsMetaParameterCollection (); 913 parms.Add (new TdsMetaParameter ("@P1", "int", Int32.Parse (statementId))); 914 ExecProc ("sp_unprepare", parms, 0, false); 915 } 916 IsValidRowCount(byte status, byte op)917 protected override bool IsValidRowCount (byte status, byte op) 918 { 919 if ((status & (byte)0x10) == 0 || op == (byte)0xc1) 920 return false; 921 return true; 922 } 923 ProcessReturnStatus()924 protected override void ProcessReturnStatus () 925 { 926 int result = Comm.GetTdsInt (); 927 if (Parameters != null) { 928 foreach (TdsMetaParameter param in Parameters) { 929 if (param.Direction == TdsParameterDirection.ReturnValue) { 930 param.Value = result; 931 break; 932 } 933 } 934 } 935 } 936 GetScale(TdsColumnType type, int columnSize)937 byte GetScale (TdsColumnType type, int columnSize) 938 { 939 switch (type) { 940 case TdsColumnType.DateTime: 941 return 0x03; 942 case TdsColumnType.DateTime4: 943 return 0x00; 944 case TdsColumnType.DateTimeN: 945 switch (columnSize) { 946 case 4: 947 return 0x00; 948 case 8: 949 return 0x03; 950 } 951 break; 952 default: 953 return 0xff; 954 } 955 956 throw new NotSupportedException (string.Format ( 957 CultureInfo.InvariantCulture, 958 "Fixed scale not defined for column " + 959 "type '{0}' with size {1}.", type, columnSize)); 960 } 961 GetPrecision(TdsColumnType type, int columnSize)962 byte GetPrecision (TdsColumnType type, int columnSize) 963 { 964 switch (type) { 965 case TdsColumnType.Binary: 966 return 0xff; 967 case TdsColumnType.Bit: 968 return 0xff; 969 case TdsColumnType.Char: 970 return 0xff; 971 case TdsColumnType.DateTime: 972 return 0x17; 973 case TdsColumnType.DateTime4: 974 return 0x10; 975 case TdsColumnType.DateTimeN: 976 switch (columnSize) { 977 case 4: 978 return 0x10; 979 case 8: 980 return 0x17; 981 } 982 break; 983 case TdsColumnType.Real: 984 return 0x07; 985 case TdsColumnType.Float8: 986 return 0x0f; 987 case TdsColumnType.FloatN: 988 switch (columnSize) { 989 case 4: 990 return 0x07; 991 case 8: 992 return 0x0f; 993 } 994 break; 995 case TdsColumnType.Image: 996 return 0xff; 997 case TdsColumnType.Int1: 998 return 0x03; 999 case TdsColumnType.Int2: 1000 return 0x05; 1001 case TdsColumnType.Int4: 1002 return 0x0a; 1003 case TdsColumnType.IntN: 1004 switch (columnSize) { 1005 case 1: 1006 return 0x03; 1007 case 2: 1008 return 0x05; 1009 case 4: 1010 return 0x0a; 1011 } 1012 break; 1013 case TdsColumnType.Void: 1014 return 0x01; 1015 case TdsColumnType.Text: 1016 return 0xff; 1017 case TdsColumnType.UniqueIdentifier: 1018 return 0xff; 1019 case TdsColumnType.VarBinary: 1020 return 0xff; 1021 case TdsColumnType.VarChar: 1022 return 0xff; 1023 case TdsColumnType.Money: 1024 return 19; 1025 case TdsColumnType.NText: 1026 return 0xff; 1027 case TdsColumnType.NVarChar: 1028 return 0xff; 1029 case TdsColumnType.BitN: 1030 return 0xff; 1031 case TdsColumnType.MoneyN: 1032 switch (columnSize) { 1033 case 4: 1034 return 0x0a; 1035 case 8: 1036 return 0x13; 1037 } 1038 break; 1039 case TdsColumnType.Money4: 1040 return 0x0a; 1041 case TdsColumnType.NChar: 1042 return 0xff; 1043 case TdsColumnType.BigBinary: 1044 return 0xff; 1045 case TdsColumnType.BigVarBinary: 1046 return 0xff; 1047 case TdsColumnType.BigVarChar: 1048 return 0xff; 1049 case TdsColumnType.BigNVarChar: 1050 return 0xff; 1051 case TdsColumnType.BigChar: 1052 return 0xff; 1053 case TdsColumnType.SmallMoney: 1054 return 0x0a; 1055 case TdsColumnType.Variant: 1056 return 0xff; 1057 case TdsColumnType.BigInt: 1058 return 0xff; 1059 } 1060 1061 throw new NotSupportedException (string.Format ( 1062 CultureInfo.InvariantCulture, 1063 "Fixed precision not defined for column " + 1064 "type '{0}' with size {1}.", type, columnSize)); 1065 } 1066 1067 #endregion // Methods 1068 1069 #region Asynchronous Methods 1070 BeginExecuteNonQuery(string cmdText, TdsMetaParameterCollection parameters, AsyncCallback callback, object state)1071 public override IAsyncResult BeginExecuteNonQuery (string cmdText, 1072 TdsMetaParameterCollection parameters, 1073 AsyncCallback callback, 1074 object state) 1075 { 1076 Parameters = parameters; 1077 string sql = cmdText; 1078 if (Parameters != null && Parameters.Count > 0) 1079 sql = BuildExec (cmdText); 1080 1081 IAsyncResult ar = BeginExecuteQueryInternal (sql, false, callback, state); 1082 return ar; 1083 } 1084 EndExecuteNonQuery(IAsyncResult ar)1085 public override void EndExecuteNonQuery (IAsyncResult ar) 1086 { 1087 EndExecuteQueryInternal (ar); 1088 } 1089 BeginExecuteQuery(string cmdText, TdsMetaParameterCollection parameters, AsyncCallback callback, object state)1090 public override IAsyncResult BeginExecuteQuery (string cmdText, 1091 TdsMetaParameterCollection parameters, 1092 AsyncCallback callback, 1093 object state) 1094 { 1095 Parameters = parameters; 1096 string sql = cmdText; 1097 if (Parameters != null && Parameters.Count > 0) 1098 sql = BuildExec (cmdText); 1099 1100 IAsyncResult ar = BeginExecuteQueryInternal (sql, true, callback, state); 1101 return ar; 1102 } 1103 EndExecuteQuery(IAsyncResult ar)1104 public override void EndExecuteQuery (IAsyncResult ar) 1105 { 1106 EndExecuteQueryInternal (ar); 1107 } 1108 BeginExecuteProcedure(string prolog, string epilog, string cmdText, bool IsNonQuery, TdsMetaParameterCollection parameters, AsyncCallback callback, object state)1109 public override IAsyncResult BeginExecuteProcedure (string prolog, 1110 string epilog, 1111 string cmdText, 1112 bool IsNonQuery, 1113 TdsMetaParameterCollection parameters, 1114 AsyncCallback callback, 1115 object state) 1116 { 1117 Parameters = parameters; 1118 string pcall = BuildProcedureCall (cmdText); 1119 string sql = String.Format ("{0};{1};{2};", prolog, pcall, epilog); 1120 1121 IAsyncResult ar = BeginExecuteQueryInternal (sql, !IsNonQuery, callback, state); 1122 return ar; 1123 } 1124 EndExecuteProcedure(IAsyncResult ar)1125 public override void EndExecuteProcedure (IAsyncResult ar) 1126 { 1127 EndExecuteQueryInternal (ar); 1128 } 1129 1130 #endregion // Asynchronous Methods 1131 } 1132 } 1133