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 parameterName = p.ParameterName;
87 if (parameterName [0] == '@') {
88 parameterName = parameterName.Substring (1);
90 if (p.Direction != TdsParameterDirection.ReturnValue) {
91 if (result.Length > 0)
93 if (p.Direction == TdsParameterDirection.InputOutput)
94 result.AppendFormat ("@{0}={0} output", parameterName);
96 result.Append (FormatParameter (p));
99 return result.ToString ();
102 private string BuildPreparedParameters ()
104 StringBuilder parms = new StringBuilder ();
105 foreach (TdsMetaParameter p in Parameters) {
106 if (parms.Length > 0)
108 parms.Append (p.Prepare ());
109 if (p.Direction == TdsParameterDirection.Output)
110 parms.Append (" output");
112 return parms.ToString ();
115 private string BuildPreparedQuery (string id)
117 return BuildProcedureCall (String.Format ("sp_execute {0},", id));
120 private string BuildProcedureCall (string procedure)
122 string exec = String.Empty;
124 StringBuilder declare = new StringBuilder ();
125 StringBuilder select = new StringBuilder ();
126 StringBuilder set = new StringBuilder ();
129 if (Parameters != null) {
130 foreach (TdsMetaParameter p in Parameters) {
131 if (p.Direction != TdsParameterDirection.Input) {
133 select.Append ("select ");
135 select.Append (", ");
136 select.Append (p.ParameterName);
138 declare.Append (String.Format ("declare {0}\n", p.Prepare ()));
140 if (p.Direction != TdsParameterDirection.ReturnValue) {
141 if (p.Direction == TdsParameterDirection.InputOutput)
142 set.Append (String.Format ("set {0}\n", FormatParameter(p)));
144 set.Append (String.Format ("set {0}=NULL\n", p.ParameterName));
150 if (p.Direction == TdsParameterDirection.ReturnValue)
151 exec = p.ParameterName + "=";
154 exec = "exec " + exec;
156 return String.Format ("{0}{1}{2}{3} {4}\n{5}",
157 declare.ToString (), set.ToString (), exec,
158 procedure, BuildParameters (), select.ToString ());
161 public override bool Connect (TdsConnectionParameters connectionParameters)
164 throw new InvalidOperationException ("The connection is already open.");
166 connectionParms = connectionParameters;
168 SetLanguage (connectionParameters.Language);
169 SetCharset ("utf-8");
171 byte[] empty = new byte[0];
175 byte[] domainMagic = { 6, 0x7d, 0x0f, 0xfd, 0xff, 0x0, 0x0, 0x0,
176 0x0, 0xe0, 0x83, 0x0, 0x0,
177 0x68, 0x01, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00 };
178 byte[] sqlserverMagic = { 6, 0x0, 0x0, 0x0,
180 0x0, 0xe0, 0x03, 0x0,
181 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
185 if (connectionParameters.DomainLogin)
188 magic = sqlserverMagic;
190 string username = connectionParameters.User;
191 string domain = null;
193 int idx = username.IndexOf ("\\");
195 domain = username.Substring (0, idx);
196 username = username.Substring (idx + 1);
198 connectionParameters.DefaultDomain = domain;
199 connectionParameters.User = username;
201 domain = Environment.UserDomainName;
202 connectionParameters.DefaultDomain = domain;
205 short partialPacketSize = (short) (86 + (
206 connectionParameters.Hostname.Length +
207 connectionParameters.ApplicationName.Length +
209 connectionParameters.LibraryName.Length +
211 connectionParameters.Database.Length +
212 connectionParameters.AttachDBFileName.Length) * 2);
214 if (connectionParameters.DomainLogin) {
215 authLen = ((short) (32 + (connectionParameters.Hostname.Length +
217 partialPacketSize += authLen;
219 partialPacketSize += ((short) ((username.Length + connectionParameters.Password.Length) * 2));
221 int totalPacketSize = partialPacketSize;
223 Comm.StartPacket (TdsPacketType.Logon70);
225 Comm.Append (totalPacketSize);
227 //Comm.Append (empty, 3, pad);
228 byte[] version = {0x00, 0x0, 0x0, 0x70};
229 Comm.Append (version); // TDS Version 7
230 Comm.Append ((int)this.PacketSize); // Set the Block Size
231 Comm.Append (empty, 3, pad);
237 Comm.Append (curPos);
238 Comm.Append ((short) connectionParameters.Hostname.Length);
239 curPos += (short) (connectionParameters.Hostname.Length * 2);
241 if (connectionParameters.DomainLogin) {
242 Comm.Append((short)0);
243 Comm.Append((short)0);
244 Comm.Append((short)0);
245 Comm.Append((short)0);
248 Comm.Append (curPos);
249 Comm.Append ((short) username.Length);
250 curPos += ((short) (username.Length * 2));
253 Comm.Append (curPos);
254 Comm.Append ((short) connectionParameters.Password.Length);
255 curPos += (short) (connectionParameters.Password.Length * 2);
259 Comm.Append (curPos);
260 Comm.Append ((short) connectionParameters.ApplicationName.Length);
261 curPos += (short) (connectionParameters.ApplicationName.Length * 2);
264 Comm.Append (curPos);
265 Comm.Append ((short) DataSource.Length);
266 curPos += (short) (DataSource.Length * 2);
269 Comm.Append ((short) curPos);
270 Comm.Append ((short) 0);
273 Comm.Append (curPos);
274 Comm.Append ((short) connectionParameters.LibraryName.Length);
275 curPos += (short) (connectionParameters.LibraryName.Length * 2);
278 Comm.Append (curPos);
279 Comm.Append ((short) Language.Length);
280 curPos += (short) (Language.Length * 2);
283 Comm.Append (curPos);
284 Comm.Append ((short) connectionParameters.Database.Length);
285 curPos += (short) (connectionParameters.Database.Length * 2);
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);
293 Comm.Append((byte) 0);
295 // Authentication Stuff
296 Comm.Append ((short) curPos);
297 if (connectionParameters.DomainLogin) {
298 Comm.Append ((short) authLen);
299 curPos += (short) authLen;
301 Comm.Append ((short) 0);
304 Comm.Append (curPos);
305 Comm.Append ((short)( connectionParameters.AttachDBFileName.Length));
306 curPos += (short)(connectionParameters.AttachDBFileName.Length*2);
308 // Connection Parameters
309 Comm.Append (connectionParameters.Hostname);
310 if (!connectionParameters.DomainLogin) {
311 // SQL Server Authentication
312 Comm.Append (connectionParameters.User);
313 string scrambledPwd = EncryptPassword (connectionParameters.Password);
314 Comm.Append (scrambledPwd);
316 Comm.Append (connectionParameters.ApplicationName);
317 Comm.Append (DataSource);
318 Comm.Append (connectionParameters.LibraryName);
319 Comm.Append (Language);
320 Comm.Append (connectionParameters.Database);
322 if (connectionParameters.DomainLogin) {
323 // the rest of the packet is NTLMSSP authentication
324 Type1Message msg = new Type1Message ();
326 msg.Host = connectionParameters.Hostname;
327 msg.Flags = NtlmFlags.NegotiateUnicode |
328 NtlmFlags.NegotiateNtlm |
329 NtlmFlags.NegotiateDomainSupplied |
330 NtlmFlags.NegotiateWorkstationSupplied |
331 NtlmFlags.NegotiateAlwaysSign; // 0xb201
332 Comm.Append (msg.GetBytes ());
335 Comm.Append (connectionParameters.AttachDBFileName);
343 private static string EncryptPassword (string pass)
345 int xormask = 0x5a5a;
346 int len = pass.Length;
347 char[] chars = new char[len];
349 for (int i = 0; i < len; ++i) {
350 int c = ((int) (pass[i])) ^ xormask;
351 int m1 = (c >> 4) & 0x0f0f;
352 int m2 = (c << 4) & 0xf0f0;
353 chars[i] = (char) (m1 | m2);
356 return new String (chars);
359 public override bool Reset ()
361 // Check validity of the connection - a false removes
362 // the connection from the pool
363 // NOTE: MS implementation will throw a connection-reset error as it will
364 // try to use the same connection
365 if (!Comm.IsConnected ())
368 // Set "reset-connection" bit for the next message packet
369 Comm.ResetConnection = true;
374 public override void ExecPrepared (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
376 Parameters = parameters;
377 ExecuteQuery (BuildPreparedQuery (commandText), timeout, wantResults);
380 public override void ExecProc (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
382 Parameters = parameters;
383 ExecRPC (commandText, parameters, timeout, wantResults);
386 protected override void ExecRPC (string rpcName, TdsMetaParameterCollection parameters,
387 int timeout, bool wantResults)
391 Comm.StartPacket (TdsPacketType.RPC);
393 Comm.Append ( (short) rpcName.Length);
394 Comm.Append (rpcName);
395 Comm.Append ( (short) 0); //no meta data
396 if (parameters != null) {
397 foreach (TdsMetaParameter param in parameters) {
398 if (param.Direction == TdsParameterDirection.ReturnValue)
400 string pname = param.ParameterName;
401 if (pname != null && pname.Length > 0 && pname [0] == '@') {
402 Comm.Append ( (byte) pname.Length);
405 Comm.Append ( (byte) (pname.Length + 1));
406 Comm.Append ("@" + pname);
408 short status = 0; // unused
409 if (param.Direction != TdsParameterDirection.Input)
410 status |= 0x01; // output
411 Comm.Append ( (byte) status);
412 WriteParameterInfo (param);
416 CheckForData (timeout);
421 private void WriteParameterInfo (TdsMetaParameter param)
424 Ms.net send non-nullable datatypes as nullable and allows setting null values
425 to int/float etc.. So, using Nullable form of type for all data
427 param.IsNullable = true;
428 TdsColumnType colType = param.GetMetaType ();
429 param.IsNullable = false;
431 Comm.Append ((byte)colType); // type
433 int size = param.Size;
435 size = param.GetActualSize ();
438 If column type is SqlDbType.NVarChar the size of parameter is multiplied by 2
439 FIXME: Need to check for other types
441 if (colType == TdsColumnType.BigNVarChar)
443 if (IsLargeType (colType))
444 Comm.Append ((short)size); // Parameter size passed in SqlParameter
445 else if (IsBlobType (colType))
446 Comm.Append (size); // Parameter size passed in SqlParameter
448 Comm.Append ((byte)size);
450 // Precision and Scale are non-zero for only decimal/numeric
451 if ( param.TypeName == "decimal" || param.TypeName == "numeric") {
452 Comm.Append ((param.Precision !=0 ) ? param.Precision : (byte) 28);
453 Comm.Append (param.Scale);
456 size = param.GetActualSize ();
457 if (IsLargeType (colType))
458 Comm.Append ((short)size);
459 else if (IsBlobType (colType))
462 Comm.Append ((byte)size);
465 switch (param.TypeName) {
467 Decimal val = (decimal) param.Value;
468 int[] arr = Decimal.GetBits (val);
471 Comm.Append (arr[1]);
472 Comm.Append (arr[0]);
474 Comm.Append (~arr[1]);
475 Comm.Append (~arr[0] + 1);
480 Decimal val = (decimal) param.Value;
481 if (val < SMALLMONEY_MIN || val > SMALLMONEY_MAX)
482 throw new OverflowException (string.Format (
483 CultureInfo.InvariantCulture,
484 "Value '{0}' is not valid for SmallMoney."
485 + " Must be between {1:N4} and {2:N4}.",
489 val.ToString (CultureInfo.CurrentCulture),
491 SMALLMONEY_MIN, SMALLMONEY_MAX));
493 int[] arr = Decimal.GetBits (val);
494 int sign = (val>0 ? 1: -1);
495 Comm.Append (sign * arr[0]);
499 Comm.Append ((DateTime)param.Value, 8);
501 case "smalldatetime":
502 Comm.Append ((DateTime)param.Value, 4);
510 byte [] tmp = param.GetBytes ();
513 case "uniqueidentifier" :
514 Comm.Append (((Guid)param.Value).ToByteArray());
517 Comm.Append (param.Value);
524 public override void Execute (string commandText, TdsMetaParameterCollection parameters, int timeout, bool wantResults)
526 Parameters = parameters;
527 string sql = commandText;
528 if (wantResults || (Parameters != null && Parameters.Count > 0))
529 sql = BuildExec (commandText);
530 ExecuteQuery (sql, timeout, wantResults);
533 private string FormatParameter (TdsMetaParameter parameter)
535 string parameterName = parameter.ParameterName;
536 if (parameterName [0] == '@') {
537 parameterName = parameterName.Substring (1);
539 if (parameter.Direction == TdsParameterDirection.Output)
540 return String.Format ("@{0}={0} output", parameterName);
541 if (parameter.Value == null || parameter.Value == DBNull.Value)
542 return parameter.ParameterName + "=NULL";
545 switch (parameter.TypeName) {
546 case "smalldatetime":
548 DateTime d = Convert.ToDateTime (parameter.Value);
549 value = String.Format (base.Locale,
550 "'{0:MMM dd yyyy hh:mm:ss.fff tt}'", d);
561 object paramValue = parameter.Value;
562 Type paramType = paramValue.GetType ();
563 if (paramType.IsEnum)
564 paramValue = Convert.ChangeType (paramValue,
565 Type.GetTypeCode (paramType));
566 value = paramValue.ToString ();
570 value = String.Format ("N'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
572 case "uniqueidentifier":
573 value = String.Format ("'{0}'", ((Guid) parameter.Value).ToString (string.Empty));
576 if (parameter.Value.GetType () == typeof (bool))
577 value = (((bool) parameter.Value) ? "0x1" : "0x0");
579 value = parameter.Value.ToString ();
584 byte[] byteArray = (byte[]) parameter.Value;
585 // In 1.0 profile, BitConverter.ToString() throws ArgumentOutOfRangeException when passed a 0-length
586 // array, so handle that as a special case.
587 if (byteArray.Length == 0)
590 value = String.Format ("0x{0}", BitConverter.ToString (byteArray).Replace ("-", string.Empty).ToLower ());
593 value = String.Format ("'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
597 return "@" + parameterName + "=" + value;
600 public override string Prepare (string commandText, TdsMetaParameterCollection parameters)
602 Parameters = parameters;
604 TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
605 TdsMetaParameter parm = new TdsMetaParameter ("@Handle", "int", null);
606 parm.Direction = TdsParameterDirection.Output;
609 parms.Add (new TdsMetaParameter ("@VarDecl", "nvarchar", BuildPreparedParameters ()));
610 parms.Add (new TdsMetaParameter ("@Query", "nvarchar", commandText));
612 ExecProc ("sp_prepare", parms, 0, true);
614 return OutputParameters[0].ToString () ;
615 //if (ColumnValues == null || ColumnValues [0] == null || ColumnValues [0] == DBNull.Value)
616 // throw new TdsInternalException ();
617 //return string.Empty;
618 //return ColumnValues [0].ToString ();
621 protected override TdsDataColumnCollection ProcessColumnInfo ()
623 TdsDataColumnCollection result = new TdsDataColumnCollection ();
624 int numColumns = Comm.GetTdsShort ();
625 for (int i = 0; i < numColumns; i += 1) {
626 byte[] flagData = new byte[4];
627 for (int j = 0; j < 4; j += 1)
628 flagData[j] = Comm.GetByte ();
630 bool nullable = (flagData[2] & 0x01) > 0;
631 //bool caseSensitive = (flagData[2] & 0x02) > 0;
632 bool writable = (flagData[2] & 0x0c) > 0;
633 bool autoIncrement = (flagData[2] & 0x10) > 0;
634 bool isIdentity = (flagData[2] & 0x10) > 0;
636 TdsColumnType columnType = (TdsColumnType) (Comm.GetByte () & 0xff);
637 if ((byte) columnType == 0xef)
638 columnType = TdsColumnType.NChar;
640 byte xColumnType = 0;
641 if (IsLargeType (columnType)) {
642 xColumnType = (byte) columnType;
643 if (columnType != TdsColumnType.NChar)
648 string tableName = null;
650 if (IsBlobType (columnType)) {
651 columnSize = Comm.GetTdsInt ();
652 tableName = Comm.GetString (Comm.GetTdsShort ());
653 } else if (IsFixedSizeColumn (columnType))
654 columnSize = LookupBufferSize (columnType);
655 else if (IsLargeType ((TdsColumnType) xColumnType))
656 columnSize = Comm.GetTdsShort ();
658 columnSize = Comm.GetByte () & 0xff;
663 switch (columnType) {
664 case TdsColumnType.NText:
665 case TdsColumnType.NChar:
666 case TdsColumnType.NVarChar:
669 case TdsColumnType.Decimal:
670 case TdsColumnType.Numeric:
674 precision = Comm.GetByte ();
675 scale = Comm.GetByte ();
679 string columnName = Comm.GetString (Comm.GetByte ());
681 TdsDataColumn col = new TdsDataColumn ();
684 col.ColumnType = columnType;
685 col.ColumnName = columnName;
686 col.IsAutoIncrement = autoIncrement;
687 col.IsIdentity = isIdentity;
688 col.ColumnSize = columnSize;
689 col.NumericPrecision = precision;
690 col.NumericScale = scale;
691 col.IsReadOnly = !writable;
692 col.AllowDBNull = nullable;
693 col.BaseTableName = tableName;
695 col ["ColumnType"] = columnType;
696 col ["ColumnName"] = columnName;
697 col ["IsAutoIncrement"] = autoIncrement;
698 col ["IsIdentity"] = isIdentity;
699 col ["ColumnSize"] = columnSize;
700 col ["NumericPrecision"] = precision;
701 col ["NumericScale"] = scale;
702 col ["IsReadOnly"] = !writable;
703 col ["AllowDBNull"] = nullable;
704 col ["BaseTableName"] = tableName;
710 public override void Unprepare (string statementId)
712 TdsMetaParameterCollection parms = new TdsMetaParameterCollection ();
713 parms.Add (new TdsMetaParameter ("@P1", "int", Int32.Parse (statementId)));
714 ExecProc ("sp_unprepare", parms, 0, false);
717 protected override bool IsValidRowCount (byte status, byte op)
719 if ((status & (byte)0x10) == 0 || op == (byte)0xc1)
724 protected override void ProcessReturnStatus ()
726 int result = Comm.GetTdsInt ();
727 if (Parameters != null) {
728 foreach (TdsMetaParameter param in Parameters) {
729 if (param.Direction == TdsParameterDirection.ReturnValue) {
730 param.Value = result;
737 #endregion // Methods
740 #region Asynchronous Methods
742 public override IAsyncResult BeginExecuteNonQuery (string cmdText,
743 TdsMetaParameterCollection parameters,
744 AsyncCallback callback,
747 Parameters = parameters;
748 string sql = cmdText;
749 if (Parameters != null && Parameters.Count > 0)
750 sql = BuildExec (cmdText);
752 IAsyncResult ar = BeginExecuteQueryInternal (sql, false, callback, state);
756 public override void EndExecuteNonQuery (IAsyncResult ar)
758 EndExecuteQueryInternal (ar);
761 public override IAsyncResult BeginExecuteQuery (string cmdText,
762 TdsMetaParameterCollection parameters,
763 AsyncCallback callback,
766 Parameters = parameters;
767 string sql = cmdText;
768 if (Parameters != null && Parameters.Count > 0)
769 sql = BuildExec (cmdText);
771 IAsyncResult ar = BeginExecuteQueryInternal (sql, true, callback, state);
775 public override void EndExecuteQuery (IAsyncResult ar)
777 EndExecuteQueryInternal (ar);
780 public override IAsyncResult BeginExecuteProcedure (string prolog,
784 TdsMetaParameterCollection parameters,
785 AsyncCallback callback,
788 Parameters = parameters;
789 string pcall = BuildProcedureCall (cmdText);
790 string sql = String.Format ("{0};{1};{2};", prolog, pcall, epilog);
792 IAsyncResult ar = BeginExecuteQueryInternal (sql, !IsNonQuery, callback, state);
796 public override void EndExecuteProcedure (IAsyncResult ar)
798 EndExecuteQueryInternal (ar);
801 #endregion // Asynchronous Methods