New test.
[mono.git] / mcs / class / Mono.Data.SybaseClient / Mono.Data.SybaseClient / SybaseCommand.cs
index 872faf4d4c74ae30ac5ba9df47f84af9a5c85a4c..ad31b479b1541418a74818d1e20f21ac6f469ded 100644 (file)
 // Copyright (C) Tim Coleman, 2002
 //
 
-using Mono.Data.TdsClient.Internal;
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using Mono.Data.Tds;
+using Mono.Data.Tds.Protocol;
 using System;
+using System.Collections;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.Data;
 using System.Data.Common;
 using System.Runtime.InteropServices;
 using System.Text;
-using System.Xml;
 
 namespace Mono.Data.SybaseClient {
        public sealed class SybaseCommand : Component, IDbCommand, ICloneable
        {
                #region Fields
 
+               bool disposed = false;
                int commandTimeout;
                bool designTimeVisible;
                string commandText;
-
                CommandType commandType;
                SybaseConnection connection;
                SybaseTransaction transaction;
-
-               SybaseParameterCollection parameters = new SybaseParameterCollection ();
-               private CommandBehavior behavior = CommandBehavior.Default;
-
-               NameValueCollection procedureCache = new NameValueCollection ();
+               UpdateRowSource updatedRowSource;
+               CommandBehavior behavior = CommandBehavior.Default;
+               SybaseParameterCollection parameters;
+               string preparedStatement = null;
 
                #endregion // Fields
 
@@ -66,8 +87,11 @@ namespace Mono.Data.SybaseClient {
                        this.connection = connection;
                        this.transaction = transaction;
                        this.commandType = CommandType.Text;
+                       this.updatedRowSource = UpdateRowSource.Both;
+
                        this.designTimeVisible = false;
                        this.commandTimeout = 30;
+                       parameters = new SybaseParameterCollection (this);
                }
 
                #endregion // Constructors
@@ -79,8 +103,12 @@ namespace Mono.Data.SybaseClient {
                }
 
                public string CommandText {
-                       get { return CommandText; }
-                       set { commandText = value; }
+                       get { return commandText; }
+                       set { 
+                               if (value != commandText && preparedStatement != null)
+                                       Unprepare ();
+                               commandText = value; 
+                       }
                }
 
                public int CommandTimeout {
@@ -94,8 +122,11 @@ namespace Mono.Data.SybaseClient {
 
                public CommandType CommandType  {
                        get { return commandType; }
-                       [MonoTODO ("Validate")]
-                       set { commandType = value; }
+                       set { 
+                               if (value == CommandType.TableDirect)
+                                       throw new ArgumentException ("CommandType.TableDirect is not supported by the Mono SybaseClient Data Provider.");
+                               commandType = value; 
+                       }
                }
 
                public SybaseConnection Connection {
@@ -118,7 +149,7 @@ namespace Mono.Data.SybaseClient {
                }
 
                internal ITds Tds {
-                       get { return connection.Tds; }
+                       get { return Connection.Tds; }
                }
 
                IDbConnection IDbCommand.Connection {
@@ -148,10 +179,9 @@ namespace Mono.Data.SybaseClient {
                        set { transaction = value; }
                }       
 
-               [MonoTODO]
                public UpdateRowSource UpdatedRowSource {
-                       get { throw new NotImplementedException (); }
-                       set { throw new NotImplementedException (); }
+                       get { return updatedRowSource; }
+                       set { updatedRowSource = value; }
                }
 
                #endregion // Fields
@@ -160,29 +190,18 @@ namespace Mono.Data.SybaseClient {
 
                public void Cancel () 
                {
-                       if (connection == null || connection.Tds == null)
+                       if (Connection == null || Connection.Tds == null)
                                return;
-                       connection.Tds.Cancel ();
+                       Connection.Tds.Cancel ();
                }
 
                internal void CloseDataReader (bool moreResults)
                {
-                       while (moreResults)
-                               moreResults = connection.Tds.NextResult ();
+                       GetOutputParameters ();
+                       Connection.DataReader = null;
 
-                       if (connection.Tds.OutputParameters.Count > 0) {
-                               int index = 0;
-                               foreach (SybaseParameter parameter in parameters) {
-                                       if (parameter.Direction != ParameterDirection.Input)
-                                               parameter.Value = connection.Tds.OutputParameters[index];
-                                       index += 1;
-                                       if (index >= connection.Tds.OutputParameters.Count)
-                                               break;
-                               }
-                       }
-                       connection.DataReaderOpen = false;
                        if ((behavior & CommandBehavior.CloseConnection) != 0)
-                               connection.Close ();
+                               Connection.Close ();
                }
 
                public SybaseParameter CreateParameter () 
@@ -190,9 +209,84 @@ namespace Mono.Data.SybaseClient {
                        return new SybaseParameter ();
                }
 
+               internal void DeriveParameters ()
+               {
+                       if (commandType != CommandType.StoredProcedure)
+                               throw new InvalidOperationException (String.Format ("SybaseCommand DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{0}", commandType));
+                       ValidateCommand ("DeriveParameters");
+
+                       SybaseParameterCollection localParameters = new SybaseParameterCollection (this);
+                       localParameters.Add ("@P1", SybaseType.NVarChar, commandText.Length).Value = commandText;
+
+                       string sql = "sp_procedure_params_rowset";
+
+                       Connection.Tds.ExecProc (sql, localParameters.MetaParameters, 0, true);
+
+                       SybaseDataReader reader = new SybaseDataReader (this);
+                       parameters.Clear ();
+                       object[] dbValues = new object[reader.FieldCount];
+
+                       while (reader.Read ()) {
+                               reader.GetValues (dbValues);
+                               parameters.Add (new SybaseParameter (dbValues));
+                       }
+                       reader.Close ();        
+               }
+
+               private void Execute (CommandBehavior behavior, bool wantResults)
+               {
+                       Tds.RecordsAffected = -1; 
+                       TdsMetaParameterCollection parms = Parameters.MetaParameters;
+                       if (preparedStatement == null) {
+                               bool schemaOnly = ((CommandBehavior & CommandBehavior.SchemaOnly) > 0);
+                               bool keyInfo = ((CommandBehavior & CommandBehavior.SchemaOnly) > 0);
+
+                               StringBuilder sql1 = new StringBuilder ();
+                               StringBuilder sql2 = new StringBuilder ();
+
+                               if (schemaOnly || keyInfo)
+                                       sql1.Append ("SET FMTONLY OFF;");
+                               if (keyInfo) {
+                                       sql1.Append ("SET NO_BROWSETABLE ON;");
+                                       sql2.Append ("SET NO_BROWSETABLE OFF;");
+                               }
+                               if (schemaOnly) {
+                                       sql1.Append ("SET FMTONLY ON;");
+                                       sql2.Append ("SET FMTONLY OFF;");
+                               }
+
+                               switch (CommandType) {
+                               case CommandType.StoredProcedure:
+                                       if (keyInfo || schemaOnly)
+                                               Connection.Tds.Execute (sql1.ToString ());
+                                       Connection.Tds.ExecProc (CommandText, parms, CommandTimeout, wantResults);
+                                       if (keyInfo || schemaOnly)
+                                               Connection.Tds.Execute (sql2.ToString ());
+                                       break;
+                               case CommandType.Text:
+                                       string sql = String.Format ("{0}{1}{2}", sql1.ToString (), CommandText, sql2.ToString ());
+                                       Connection.Tds.Execute (sql, parms, CommandTimeout, wantResults);
+                                       break;
+                               }
+                       }
+                       else 
+                               Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
+               }
+
                public int ExecuteNonQuery ()
                {
-                       int result = connection.Tds.ExecuteNonQuery (ValidateQuery ("ExecuteNonQuery"));
+                       ValidateCommand ("ExecuteNonQuery");
+                       int result = 0;
+
+                       try {
+                               Execute (CommandBehavior.Default, false);
+                               result = Tds.RecordsAffected;
+                       }
+                       catch (TdsTimeoutException e) {
+                               throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
+                       }
+
+                       GetOutputParameters ();
                        return result;
                }
 
@@ -203,106 +297,57 @@ namespace Mono.Data.SybaseClient {
 
                public SybaseDataReader ExecuteReader (CommandBehavior behavior)
                {
-                       this.behavior = behavior;
-                       connection.Tds.ExecuteQuery (ValidateQuery ("ExecuteReader"));
-                       connection.DataReaderOpen = true;
-                       return new SybaseDataReader (this);
+                       ValidateCommand ("ExecuteReader");
+                       try {
+                               Execute (behavior, true);
+                       }
+                       catch (TdsTimeoutException e) {
+                               throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
+                       }
+                       Connection.DataReader = new SybaseDataReader (this);
+                       return Connection.DataReader;
                }
 
                public object ExecuteScalar ()
                {
-                       connection.Tds.ExecuteQuery (ValidateQuery ("ExecuteScalar"));
-
-                       bool moreResults = connection.Tds.NextResult ();
-
-                       if (!moreResults)
-                               return null;
-
-                       moreResults = connection.Tds.NextRow ();
+                       ValidateCommand ("ExecuteScalar");
+                       try {
+                               Execute (CommandBehavior.Default, true);
+                       }
+                       catch (TdsTimeoutException e) {
+                               throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
+                       }
 
-                       if (!moreResults)
+                       if (!Connection.Tds.NextResult () || !Connection.Tds.NextRow ())
                                return null;
 
-                       object result = connection.Tds.ColumnValues[0];
+                       object result = Connection.Tds.ColumnValues [0];
                        CloseDataReader (true);
                        return result;
                }
 
-               static string FormatParameter (SybaseParameter parameter)
+               private void GetOutputParameters ()
                {
-                       if (parameter.Value == null)
-                               return "NULL";
-
-                       switch (parameter.SybaseType) {
-                               case SybaseType.BigInt :
-                               case SybaseType.Bit :
-                               case SybaseType.Decimal :
-                               case SybaseType.Float :
-                               case SybaseType.Int :
-                               case SybaseType.Money :
-                               case SybaseType.Real :
-                               case SybaseType.SmallInt :
-                               case SybaseType.SmallMoney :
-                               case SybaseType.TinyInt :
-                                       return parameter.Value.ToString ();
-                               default:
-                                       return String.Format ("'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
-                       }
-               }
+                       Connection.Tds.SkipToEnd ();
 
-               static string FormatQuery (string commandText, CommandType commandType, SybaseParameterCollection parameters)
-               {
-                       StringBuilder result = new StringBuilder ();
-
-                       switch (commandType) {
-                       case CommandType.Text :
-                               return commandText;
-                       case CommandType.TableDirect :
-                               return String.Format ("SELECT * FROM {0}", commandText);
-                       case CommandType.StoredProcedure :
-
-                               StringBuilder parms = new StringBuilder ();
-                               StringBuilder declarations = new StringBuilder ();
+                       IList list = Connection.Tds.ColumnValues;
 
+                       if (list != null && list.Count > 0) {
+                               int index = 0;
                                foreach (SybaseParameter parameter in parameters) {
-                                       switch (parameter.Direction) {
-                                       case ParameterDirection.Input :
-                                               if (parms.Length > 0)
-                                                       result.Append (",");
-                                               parms.Append (FormatParameter (parameter));
-                                               break;
-                                       case ParameterDirection.Output :
-                                               if (parms.Length > 0)
-                                                       parms.Append (",");
-                                               parms.Append (parameter.ParameterName);
-                                               parms.Append (" OUT");
-
-                                               if (declarations.Length == 0)
-                                                       declarations.Append ("DECLARE ");
-                                               else
-                                                       declarations.Append (",");
-
-                                               declarations.Append (parameter.Prepare ());
-                                               break;
-                                       default :
-                                               throw new NotImplementedException ("Only support input and output parameters.");
+                                       if (parameter.Direction != ParameterDirection.Input) {
+                                               parameter.Value = list [index];
+                                               index += 1;
                                        }
+                                       if (index >= list.Count)
+                                               break;
                                }
-                               result.Append (declarations.ToString ());
-                               result.Append (" EXEC ");
-                               result.Append (commandText);
-                               result.Append (" ");
-                               result.Append (parms);
-                               return result.ToString ();
-                       default:
-                               throw new InvalidOperationException ("The CommandType was not recognized.");
                        }
                }
 
-               [MonoTODO]
                object ICloneable.Clone ()
                {
-                       throw new NotImplementedException ();
+                       return new SybaseCommand (commandText, Connection);
                }
 
                IDbDataParameter IDbCommand.CreateParameter ()
@@ -320,37 +365,11 @@ namespace Mono.Data.SybaseClient {
                        return ExecuteReader (behavior);
                }
 
-               void IDisposable.Dispose ()
-               {
-                       Dispose (true);
-               }
-
                public void Prepare ()
                {
-                       bool prependComma = false;
-                       Guid uniqueId = Guid.NewGuid ();
-                       string procedureName = String.Format ("#mono#{0}", uniqueId.ToString ("N"));
-                       StringBuilder procedureString = new StringBuilder ();
-
-                       procedureString.Append ("CREATE PROC ");
-                       procedureString.Append (procedureName);
-                       procedureString.Append (" (");
-
-                       foreach (SybaseParameter parameter in parameters) {
-                               if (prependComma)
-                                       procedureString.Append (", ");
-                               else
-                                       prependComma = true;
-                               procedureString.Append (parameter.Prepare ());
-                               if (parameter.Direction == ParameterDirection.Output)
-                                       procedureString.Append (" OUT");
-                       }
-                               
-                       procedureString.Append (") AS ");
-                       procedureString.Append (commandText);
-                       string cmdText = FormatQuery (procedureName, CommandType.StoredProcedure, parameters);
-                       connection.Tds.ExecuteNonQuery (procedureString.ToString ());
-                       procedureCache[commandText] = cmdText;
+                       ValidateCommand ("Prepare");
+                       if (CommandType == CommandType.Text) 
+                               preparedStatement = Connection.Tds.Prepare (CommandText, Parameters.MetaParameters);
                }
 
                public void ResetCommandTimeout ()
@@ -358,25 +377,24 @@ namespace Mono.Data.SybaseClient {
                        commandTimeout = 30;
                }
 
-               string ValidateQuery (string methodName)
+               private void Unprepare ()
                {
-                       if (connection == null)
-                               throw new InvalidOperationException (String.Format ("{0} requires a Connection object to continue.", methodName));
-                       if (connection.Transaction != null && transaction != connection.Transaction)
+                       Connection.Tds.Unprepare (preparedStatement); 
+                       preparedStatement = null;
+               }
+
+               private void ValidateCommand (string method)
+               {
+                       if (Connection == null)
+                               throw new InvalidOperationException (String.Format ("{0} requires a Connection object to continue.", method));
+                       if (Connection.Transaction != null && transaction != Connection.Transaction)
                                throw new InvalidOperationException ("The Connection object does not have the same transaction as the command object.");
-                       if (connection.State != ConnectionState.Open)
-                               throw new InvalidOperationException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", methodName));
+                       if (Connection.State != ConnectionState.Open)
+                               throw new InvalidOperationException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
                        if (commandText == String.Empty || commandText == null)
                                throw new InvalidOperationException ("The command text for this Command has not been set.");
-
-                       string sql = procedureCache[commandText];
-                       if (sql == null)
-                               sql = FormatQuery (commandText, commandType, parameters);
-               
-                       if ((behavior & CommandBehavior.KeyInfo) != 0)
-                               sql += " FOR BROWSE";
-
-                       return sql;
+                       if (Connection.DataReader != null)
+                               throw new InvalidOperationException ("There is already an open DataReader associated with this Connection which must be closed first.");
                }
 
                #endregion // Methods