2 // Mono.Data.Tds.Protocol.Tds70.cs
5 // Tim Coleman (tim@timcoleman.com)
6 // Diego Caravana (diego@toth.it)
7 // Sebastien Pouliot (sebastien@ximian.com)
8 // Daniel Morgan (danielmorgan@verizon.net)
10 // Copyright (C) 2002 Tim Coleman
11 // Portions (C) 2003 Motus Technologies Inc. (http://www.motus.com)
12 // Portions (C) 2003 Daniel Morgan
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 using System.Globalization;
40 using Mono.Security.Protocol.Ntlm;
42 namespace Mono.Data.Tds.Protocol
44 public sealed class Tds70 : Tds
48 public readonly static TdsVersion Version = TdsVersion.tds70;
49 static readonly decimal SMALLMONEY_MIN = -214748.3648m;
50 static readonly decimal SMALLMONEY_MAX = 214748.3647m;
56 public Tds70 (string server, int port)
57 : this (server, port, 512, 15)
61 public Tds70 (string server, int port, int packetSize, int timeout)
62 : base (server, port, packetSize, timeout, Version)
66 #endregion // Constructors
70 private string BuildExec (string sql)
72 string esql = sql.Replace ("'", "''"); // escape single quote
73 if (Parameters != null && Parameters.Count > 0)
74 return BuildProcedureCall (String.Format ("sp_executesql N'{0}', N'{1}', ", esql, BuildPreparedParameters ()));
76 return BuildProcedureCall (String.Format ("sp_executesql N'{0}'", esql));
79 private string BuildParameters ()
81 if (Parameters == null || Parameters.Count == 0)
84 StringBuilder result = new StringBuilder ();
85 foreach (TdsMetaParameter p in Parameters) {
86 string includeAt = "@";
87 if (p.ParameterName [0] == '@')
88 includeAt = string.Empty;
89 if (p.Direction != TdsParameterDirection.ReturnValue) {
90 if (result.Length > 0)
92 if (p.Direction == TdsParameterDirection.InputOutput)
93 result.Append (String.Format("{0}{1}={1} output", includeAt, p.ParameterName));
95 result.Append (FormatParameter (p));
98 return result.ToString ();
101 private string BuildPreparedParameters ()
103 StringBuilder parms = new StringBuilder ();
104 foreach (TdsMetaParameter p in Parameters) {
105 if (parms.Length > 0)
107 parms.Append (p.Prepare ());
108 if (p.Direction == TdsParameterDirection.Output)
109 parms.Append (" output");
111 return parms.ToString ();
114 private string BuildPreparedQuery (string id)
116 return BuildProcedureCall (String.Format ("sp_execute {0},", id));
119 private string BuildProcedureCall (string procedure)
121 string exec = String.Empty;
123 StringBuilder declare = new StringBuilder ();
124 StringBuilder select = new StringBuilder ();
125 StringBuilder set = new StringBuilder ();
128 if (Parameters != null) {
129 foreach (TdsMetaParameter p in Parameters) {
130 if (p.Direction != TdsParameterDirection.Input) {
132 select.Append ("select ");
134 select.Append (", ");
135 select.Append (p.ParameterName);
137 declare.Append (String.Format ("declare {0}\n", p.Prepare ()));
139 if (p.Direction != TdsParameterDirection.ReturnValue) {
140 if (p.Direction == TdsParameterDirection.InputOutput)
141 set.Append (String.Format ("set {0}\n", FormatParameter(p)));
143 set.Append (String.Format ("set {0}=NULL\n", p.ParameterName));
149 if (p.Direction == TdsParameterDirection.ReturnValue)
150 exec = p.ParameterName + "=";
153 exec = "exec " + exec;
155 return String.Format ("{0}{1}{2}{3} {4}\n{5}",
156 declare.ToString (), set.ToString (), exec,
157 procedure, BuildParameters (), select.ToString ());
160 public override bool Connect (TdsConnectionParameters connectionParameters)
163 throw new InvalidOperationException ("The connection is already open.");
165 connectionParms = connectionParameters;
167 SetLanguage (connectionParameters.Language);
168 SetCharset ("utf-8");
170 byte[] empty = new byte[0];
174 byte[] domainMagic = { 6, 0x7d, 0x0f, 0xfd, 0xff, 0x0, 0x0, 0x0,
175 0x0, 0xe0, 0x83, 0x0, 0x0,
176 0x68, 0x01, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00 };
177 byte[] sqlserverMagic = { 6, 0x0, 0x0, 0x0,
179 0x0, 0xe0, 0x03, 0x0,
180 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
184 if (connectionParameters.DomainLogin)
187 magic = sqlserverMagic;
189 string username = connectionParameters.User;
190 string domain = null;
192 int idx = username.IndexOf ("\\");
194 domain = username.Substring (0, idx);
195 username = username.Substring (idx + 1);
197 connectionParameters.DefaultDomain = domain;
198 connectionParameters.User = username;
200 domain = Environment.UserDomainName;
201 connectionParameters.DefaultDomain = domain;
204 short partialPacketSize = (short) (86 + (
205 connectionParameters.Hostname.Length +
206 connectionParameters.ApplicationName.Length +
208 connectionParameters.LibraryName.Length +
210 connectionParameters.Database.Length +
211 connectionParameters.AttachDBFileName.Length) * 2);
213 if (connectionParameters.DomainLogin) {
214 authLen = ((short) (32 + (connectionParameters.Hostname.Length +
216 partialPacketSize += authLen;
218 partialPacketSize += ((short) ((username.Length + connectionParameters.Password.Length) * 2));
220 int totalPacketSize = partialPacketSize;
222 Comm.StartPacket (TdsPacketType.Logon70);
224 Comm.Append (totalPacketSize);
226 //Comm.Append (empty, 3, pad);
227 byte[] version = {0x00, 0x0, 0x0, 0x70};
228 Comm.Append (version); // TDS Version 7
229 Comm.Append ((int)this.PacketSize); // Set the Block Size
230 Comm.Append (empty, 3, pad);
236 Comm.Append (curPos);
237 Comm.Append ((short) connectionParameters.Hostname.Length);
238 curPos += (short) (connectionParameters.Hostname.Length * 2);
240 if (connectionParameters.DomainLogin) {
241 Comm.Append((short)0);
242 Comm.Append((short)0);
243 Comm.Append((short)0);
244 Comm.Append((short)0);
247 Comm.Append (curPos);
248 Comm.Append ((short) username.Length);
249 curPos += ((short) (username.Length * 2));
252 Comm.Append (curPos);
253 Comm.Append ((short) connectionParameters.Password.Length);
254 curPos += (short) (connectionParameters.Password.Length * 2);
258 Comm.Append (curPos);
259 Comm.Append ((short) connectionParameters.ApplicationName.Length);
260 curPos += (short) (connectionParameters.ApplicationName.Length * 2);
263 Comm.Append (curPos);
264 Comm.Append ((short) DataSource.Length);
265 curPos += (short) (DataSource.Length * 2);
268 Comm.Append ((short) curPos);
269 Comm.Append ((short) 0);
272 Comm.Append (curPos);
273 Comm.Append ((short) connectionParameters.LibraryName.Length);
274 curPos += (short) (connectionParameters.LibraryName.Length * 2);
277 Comm.Append (curPos);
278 Comm.Append ((short) Language.Length);
279 curPos += (short) (Language.Length * 2);
282 Comm.Append (curPos);
283 Comm.Append ((short) connectionParameters.Database.Length);
284 curPos += (short) (connectionParameters.Database.Length * 2);
287 Comm.Append((byte) 0);
288 Comm.Append((byte) 0);
289 Comm.Append((byte) 0);
290 Comm.Append((byte) 0);
291 Comm.Append((byte) 0);
292 Comm.Append((byte) 0);
294 // Authentication Stuff
295 Comm.Append ((short) curPos);
296 if (connectionParameters.DomainLogin) {
297 Comm.Append ((short) authLen);
298 curPos += (short) authLen;
300 Comm.Append ((short) 0);
303 Comm.Append (curPos);
304 Comm.Append ((short)( connectionParameters.AttachDBFileName.Length));
305 curPos += (short)(connectionParameters.AttachDBFileName.Length*2);
307 // Connection Parameters
308 Comm.Append (connectionParameters.Hostname);
309 if (!connectionParameters.DomainLogin) {
310 // SQL Server Authentication
311 Comm.Append (connectionParameters.User);
312 string scrambledPwd = EncryptPassword (connectionParameters.Password);
313 Comm.Append (scrambledPwd);
315 Comm.Append (connectionParameters.ApplicationName);
316 Comm.Append (DataSource);
317 Comm.Append (connectionParameters.LibraryName);
318 Comm.Append (Language);
319 Comm.Append (connectionParameters.Database);
321 if (connectionParameters.DomainLogin) {
322 // the rest of the packet is NTLMSSP authentication
323 Type1Message msg = new Type1Message ();
325 msg.Host = connectionParameters.Hostname;
326 msg.Flags = NtlmFlags.NegotiateUnicode |
327 NtlmFlags.NegotiateNtlm |
328 NtlmFlags.NegotiateDomainSupplied |
329 NtlmFlags.NegotiateWorkstationSupplied |
330 NtlmFlags.NegotiateAlwaysSign; // 0xb201
331 Comm.Append (msg.GetBytes ());
334 Comm.Append (connectionParameters.AttachDBFileName);
342 private static string EncryptPassword (string pass)
344 int xormask = 0x5a5a;
345 int len = pass.Length;
346 char[] chars = new char[len];
348 for (int i = 0; i < len; ++i) {
349 int c = ((int) (pass[i])) ^ xormask;
350 int m1 = (c >> 4) & 0x0f0f;
351 int m2 = (c << 4) & 0xf0f0;
352 chars[i] = (char) (m1 | m2);
355 return new String (chars);
358 public override bool Reset ()
360 // Check validity of the connection - a false removes
361 // the connection from the pool
362 // NOTE: MS implementation will throw a connection-reset error as it will
363 // try to use the same connection
364 if (!Comm.IsConnected ())
367 // Set "reset-connection" bit for the next message packet
368 Comm.ResetConnection = true;
373 public override void ExecPrepared (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
375 Parameters = parameters;
376 ExecuteQuery (BuildPreparedQuery (commandText), timeout, wantResults);
379 public override void ExecProc (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
381 Parameters = parameters;
382 ExecRPC (commandText, parameters, timeout, wantResults);
385 protected override void ExecRPC (string rpcName, TdsMetaParameterCollection parameters,
386 int timeout, bool wantResults)
390 Comm.StartPacket (TdsPacketType.RPC);
392 Comm.Append ( (short) rpcName.Length);
393 Comm.Append (rpcName);
394 Comm.Append ( (short) 0); //no meta data
395 if (parameters != null) {
396 foreach (TdsMetaParameter param in parameters) {
397 if (param.Direction == TdsParameterDirection.ReturnValue)
399 Comm.Append ( (byte) param.ParameterName.Length );
400 Comm.Append (param.ParameterName);
401 short status = 0; // unused
402 if (param.Direction != TdsParameterDirection.Input)
403 status |= 0x01; // output
404 Comm.Append ( (byte) status);
405 WriteParameterInfo (param);
409 CheckForData (timeout);
414 private void WriteParameterInfo (TdsMetaParameter param)
417 Ms.net send non-nullable datatypes as nullable and allows setting null values
418 to int/float etc.. So, using Nullable form of type for all data
420 param.IsNullable = true;
421 TdsColumnType colType = param.GetMetaType ();
422 param.IsNullable = false;
424 Comm.Append ((byte)colType); // type
426 int size = param.Size;
428 size = param.GetActualSize ();
431 If column type is SqlDbType.NVarChar the size of parameter is multiplied by 2
432 FIXME: Need to check for other types
434 if (colType == TdsColumnType.BigNVarChar)
436 if (IsLargeType (colType))
437 Comm.Append ((short)size); // Parameter size passed in SqlParameter
438 else if (IsBlobType (colType))
439 Comm.Append (size); // Parameter size passed in SqlParameter
441 Comm.Append ((byte)size);
443 // Precision and Scale are non-zero for only decimal/numeric
444 if ( param.TypeName == "decimal" || param.TypeName == "numeric") {
445 Comm.Append ((param.Precision !=0 ) ? param.Precision : (byte) 28);
446 Comm.Append (param.Scale);
449 size = param.GetActualSize ();
450 if (IsLargeType (colType))
451 Comm.Append ((short)size);
452 else if (IsBlobType (colType))
455 Comm.Append ((byte)size);
458 switch (param.TypeName) {
460 Decimal val = (decimal) param.Value;
461 int[] arr = Decimal.GetBits (val);
464 Comm.Append (arr[1]);
465 Comm.Append (arr[0]);
467 Comm.Append (~arr[1]);
468 Comm.Append (~arr[0] + 1);
473 Decimal val = (decimal) param.Value;
474 if (val < SMALLMONEY_MIN || val > SMALLMONEY_MAX)
475 throw new OverflowException (string.Format (
476 CultureInfo.InvariantCulture,
477 "Value '{0}' is not valid for SmallMoney."
478 + " Must be between {1:N4} and {2:N4}.",
482 val.ToString (CultureInfo.CurrentCulture),
484 SMALLMONEY_MIN, SMALLMONEY_MAX));
486 int[] arr = Decimal.GetBits (val);
487 int sign = (val>0 ? 1: -1);
488 Comm.Append (sign * arr[0]);
492 Comm.Append ((DateTime)param.Value, 8);
494 case "smalldatetime":
495 Comm.Append ((DateTime)param.Value, 4);
503 byte [] tmp = param.GetBytes ();
506 case "uniqueidentifier" :
507 Comm.Append (((Guid)param.Value).ToByteArray());
510 Comm.Append (param.Value);
517 public override void Execute (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
519 Parameters = parameters;
520 string sql = commandText;
521 if (wantResults || (Parameters != null && Parameters.Count > 0))
522 sql = BuildExec (commandText);
523 ExecuteQuery (sql, timeout, wantResults);
526 private string FormatParameter (TdsMetaParameter parameter)
528 string includeAt = "@";
529 if (parameter.ParameterName [0] == '@')
530 includeAt = string.Empty;
531 if (parameter.Direction == TdsParameterDirection.Output)
532 return String.Format ("{0}{1}={1} output", includeAt, parameter.ParameterName);
533 if (parameter.Value == null || parameter.Value == DBNull.Value)
534 return parameter.ParameterName + "=NULL";
537 switch (parameter.TypeName) {
538 case "smalldatetime":
540 DateTime d = Convert.ToDateTime (parameter.Value);
541 value = String.Format (base.Locale,
542 "'{0:MMM dd yyyy hh:mm:ss.fff tt}'", d);
553 object paramValue = parameter.Value;
554 Type paramType = paramValue.GetType ();
555 if (paramType.IsEnum)
556 paramValue = Convert.ChangeType (paramValue,
557 Type.GetTypeCode (paramType));
558 value = paramValue.ToString ();
562 value = String.Format ("N'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
564 case "uniqueidentifier":
565 value = String.Format ("'{0}'", ((Guid) parameter.Value).ToString (string.Empty));
568 if (parameter.Value.GetType () == typeof (bool))
569 value = (((bool) parameter.Value) ? "0x1" : "0x0");
571 value = parameter.Value.ToString ();
576 byte[] byteArray = (byte[]) parameter.Value;
577 // In 1.0 profile, BitConverter.ToString() throws ArgumentOutOfRangeException when passed a 0-length
578 // array, so handle that as a special case.
579 if (byteArray.Length == 0)
582 value = String.Format ("0x{0}", BitConverter.ToString (byteArray).Replace ("-", string.Empty).ToLower ());
585 value = String.Format ("'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
589 return includeAt + parameter.ParameterName + "=" + value;
592 public override string Prepare (string commandText, TdsMetaParameterCollection parameters)
594 Parameters = parameters;
596 TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
597 TdsMetaParameter parm = new TdsMetaParameter ("@Handle", "int", null);
598 parm.Direction = TdsParameterDirection.Output;
601 parms.Add (new TdsMetaParameter ("@VarDecl", "nvarchar", BuildPreparedParameters ()));
602 parms.Add (new TdsMetaParameter ("@Query", "nvarchar", commandText));
604 ExecProc ("sp_prepare", parms, 0, true);
606 return OutputParameters[0].ToString () ;
607 //if (ColumnValues == null || ColumnValues [0] == null || ColumnValues [0] == DBNull.Value)
608 // throw new TdsInternalException ();
609 //return string.Empty;
610 //return ColumnValues [0].ToString ();
613 protected override TdsDataColumnCollection ProcessColumnInfo ()
615 TdsDataColumnCollection result = new TdsDataColumnCollection ();
616 int numColumns = Comm.GetTdsShort ();
617 for (int i = 0; i < numColumns; i += 1) {
618 byte[] flagData = new byte[4];
619 for (int j = 0; j < 4; j += 1)
620 flagData[j] = Comm.GetByte ();
622 bool nullable = (flagData[2] & 0x01) > 0;
623 //bool caseSensitive = (flagData[2] & 0x02) > 0;
624 bool writable = (flagData[2] & 0x0c) > 0;
625 bool autoIncrement = (flagData[2] & 0x10) > 0;
626 bool isIdentity = (flagData[2] & 0x10) > 0;
628 TdsColumnType columnType = (TdsColumnType) (Comm.GetByte () & 0xff);
629 if ((byte) columnType == 0xef)
630 columnType = TdsColumnType.NChar;
632 byte xColumnType = 0;
633 if (IsLargeType (columnType)) {
634 xColumnType = (byte) columnType;
635 if (columnType != TdsColumnType.NChar)
640 string tableName = null;
642 if (IsBlobType (columnType)) {
643 columnSize = Comm.GetTdsInt ();
644 tableName = Comm.GetString (Comm.GetTdsShort ());
645 } else if (IsFixedSizeColumn (columnType))
646 columnSize = LookupBufferSize (columnType);
647 else if (IsLargeType ((TdsColumnType) xColumnType))
648 columnSize = Comm.GetTdsShort ();
650 columnSize = Comm.GetByte () & 0xff;
655 switch (columnType) {
656 case TdsColumnType.NText:
657 case TdsColumnType.NChar:
658 case TdsColumnType.NVarChar:
661 case TdsColumnType.Decimal:
662 case TdsColumnType.Numeric:
666 precision = Comm.GetByte ();
667 scale = Comm.GetByte ();
671 string columnName = Comm.GetString (Comm.GetByte ());
673 TdsDataColumn col = new TdsDataColumn ();
676 col.ColumnType = columnType;
677 col.ColumnName = columnName;
678 col.IsAutoIncrement = autoIncrement;
679 col.IsIdentity = isIdentity;
680 col.ColumnSize = columnSize;
681 col.NumericPrecision = precision;
682 col.NumericScale = scale;
683 col.IsReadOnly = !writable;
684 col.AllowDBNull = nullable;
685 col.BaseTableName = tableName;
687 col ["ColumnType"] = columnType;
688 col ["ColumnName"] = columnName;
689 col ["IsAutoIncrement"] = autoIncrement;
690 col ["IsIdentity"] = isIdentity;
691 col ["ColumnSize"] = columnSize;
692 col ["NumericPrecision"] = precision;
693 col ["NumericScale"] = scale;
694 col ["IsReadOnly"] = !writable;
695 col ["AllowDBNull"] = nullable;
696 col ["BaseTableName"] = tableName;
702 public override void Unprepare (string statementId)
704 TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
705 parms.Add (new TdsMetaParameter ("@P1", "int", Int32.Parse (statementId)));
706 ExecProc ("sp_unprepare", parms, 0, false);
709 protected override bool IsValidRowCount (byte status, byte op)
711 if ((status & (byte)0x10) == 0 || op == (byte)0xc1)
716 protected override void ProcessReturnStatus ()
718 int result = Comm.GetTdsInt ();
719 if (Parameters != null) {
720 foreach (TdsMetaParameter param in Parameters) {
721 if (param.Direction == TdsParameterDirection.ReturnValue) {
722 param.Value = result;
729 #endregion // Methods
732 #region Asynchronous Methods
734 public override IAsyncResult BeginExecuteNonQuery (string cmdText,
735 TdsMetaParameterCollection parameters,
736 AsyncCallback callback,
739 Parameters = parameters;
740 string sql = cmdText;
741 if (Parameters != null && Parameters.Count > 0)
742 sql = BuildExec (cmdText);
744 IAsyncResult ar = BeginExecuteQueryInternal (sql, false, callback, state);
748 public override void EndExecuteNonQuery (IAsyncResult ar)
750 EndExecuteQueryInternal (ar);
753 public override IAsyncResult BeginExecuteQuery (string cmdText,
754 TdsMetaParameterCollection parameters,
755 AsyncCallback callback,
758 Parameters = parameters;
759 string sql = cmdText;
760 if (Parameters != null && Parameters.Count > 0)
761 sql = BuildExec (cmdText);
763 IAsyncResult ar = BeginExecuteQueryInternal (sql, true, callback, state);
767 public override void EndExecuteQuery (IAsyncResult ar)
769 EndExecuteQueryInternal (ar);
772 public override IAsyncResult BeginExecuteProcedure (string prolog,
776 TdsMetaParameterCollection parameters,
777 AsyncCallback callback,
780 Parameters = parameters;
781 string pcall = BuildProcedureCall (cmdText);
782 string sql = String.Format ("{0};{1};{2};", prolog, pcall, epilog);
784 IAsyncResult ar = BeginExecuteQueryInternal (sql, !IsNonQuery, callback, state);
788 public override void EndExecuteProcedure (IAsyncResult ar)
790 EndExecuteQueryInternal (ar);
793 #endregion // Asynchronous Methods