2 // Mono.Data.SybaseClient.SybaseCommand.cs
5 // Rodrigo Moya (rodrigo@ximian.com)
6 // Daniel Morgan (danmorg@sc.rr.com)
7 // Tim Coleman (tim@timcoleman.com)
9 // (C) Ximian, Inc 2002 http://www.ximian.com/
10 // (C) Daniel Morgan, 2002
11 // Copyright (C) Tim Coleman, 2002
14 using Mono.Data.TdsClient.Internal;
16 using System.Collections;
17 using System.Collections.Specialized;
18 using System.ComponentModel;
20 using System.Data.Common;
21 using System.Runtime.InteropServices;
25 namespace Mono.Data.SybaseClient {
26 public sealed class SybaseCommand : Component, IDbCommand, ICloneable
30 bool disposed = false;
33 bool designTimeVisible;
36 CommandType commandType;
37 SybaseConnection connection;
38 SybaseTransaction transaction;
39 UpdateRowSource updatedRowSource;
41 CommandBehavior behavior = CommandBehavior.Default;
42 NameValueCollection preparedStatements = new NameValueCollection ();
43 SybaseParameterCollection parameters;
49 public SybaseCommand()
50 : this (String.Empty, null, null)
54 public SybaseCommand (string commandText)
55 : this (commandText, null, null)
57 commandText = commandText;
60 public SybaseCommand (string commandText, SybaseConnection connection)
61 : this (commandText, connection, null)
63 Connection = connection;
66 public SybaseCommand (string commandText, SybaseConnection connection, SybaseTransaction transaction)
68 this.commandText = commandText;
69 this.connection = connection;
70 this.transaction = transaction;
71 this.commandType = CommandType.Text;
72 this.updatedRowSource = UpdateRowSource.Both;
74 this.designTimeVisible = false;
75 this.commandTimeout = 30;
76 parameters = new SybaseParameterCollection (this);
79 #endregion // Constructors
83 internal CommandBehavior CommandBehavior {
84 get { return behavior; }
87 public string CommandText {
88 get { return commandText; }
89 set { commandText = value; }
92 public int CommandTimeout {
93 get { return commandTimeout; }
95 if (commandTimeout < 0)
96 throw new ArgumentException ("The property value assigned is less than 0.");
97 commandTimeout = value;
101 public CommandType CommandType {
102 get { return commandType; }
104 if (value == CommandType.TableDirect)
105 throw new ArgumentException ("CommandType.TableDirect is not supported by the Mono SybaseClient Data Provider.");
110 public SybaseConnection Connection {
111 get { return connection; }
113 if (transaction != null && connection.Transaction != null && connection.Transaction.IsOpen)
114 throw new InvalidOperationException ("The Connection property was changed while a transaction was in progress.");
120 public bool DesignTimeVisible {
121 get { return designTimeVisible; }
122 set { designTimeVisible = value; }
125 public SybaseParameterCollection Parameters {
126 get { return parameters; }
130 get { return Connection.Tds; }
133 IDbConnection IDbCommand.Connection {
134 get { return Connection; }
136 if (!(value is SybaseConnection))
137 throw new InvalidCastException ("The value was not a valid SybaseConnection.");
138 Connection = (SybaseConnection) value;
142 IDataParameterCollection IDbCommand.Parameters {
143 get { return Parameters; }
146 IDbTransaction IDbCommand.Transaction {
147 get { return Transaction; }
149 if (!(value is SybaseTransaction))
150 throw new ArgumentException ();
151 Transaction = (SybaseTransaction) value;
155 public SybaseTransaction Transaction {
156 get { return transaction; }
157 set { transaction = value; }
160 public UpdateRowSource UpdatedRowSource {
161 get { return updatedRowSource; }
162 set { updatedRowSource = value; }
169 private string BuildCommand ()
171 string statementHandle = preparedStatements [commandText];
172 if (statementHandle != null) {
173 string proc = String.Format ("sp_execute {0}", statementHandle);
174 if (parameters.Count > 0)
176 return BuildProcedureCall (proc, parameters);
179 if (commandType == CommandType.StoredProcedure)
180 return BuildProcedureCall (commandText, parameters);
182 string sql = String.Empty;
183 if ((behavior & CommandBehavior.KeyInfo) > 0)
184 sql += "SET FMTONLY OFF; SET NO_BROWSETABLE ON;";
185 if ((behavior & CommandBehavior.SchemaOnly) > 0)
186 sql += "SET FMTONLY ON;";
188 switch (commandType) {
189 case CommandType.Text :
193 throw new InvalidOperationException ("The CommandType was invalid.");
195 return BuildExec (sql);
198 [MonoTODO ("This throws a SybaseException.")]
199 private string BuildExec (string sql)
201 StringBuilder declare = new StringBuilder ();
202 StringBuilder assign = new StringBuilder ();
204 sql = sql.Replace ("'", "''");
205 foreach (SybaseParameter parameter in parameters) {
206 declare.Append ("declare ");
207 declare.Append (parameter.Prepare (parameter.ParameterName));
208 if (parameter.Direction == ParameterDirection.Output)
209 declare.Append (" output");
210 declare.Append ('\n');
211 assign.Append (String.Format ("select {0}={1}\n", parameter.ParameterName, FormatParameter (parameter)));
212 sql = sql.Replace (parameter.ParameterName, String.Format ("' + {0} + '", parameter.ParameterName));
215 return String.Format ("{0}{1}execute ({2})", declare.ToString (), assign.ToString (), sql);
218 private string BuildPrepare ()
220 StringBuilder parms = new StringBuilder ();
221 foreach (SybaseParameter parameter in parameters) {
222 if (parms.Length > 0)
224 parms.Append (parameter.Prepare (parameter.ParameterName));
225 if (parameter.Direction == ParameterDirection.Output)
226 parms.Append (" output");
229 SybaseParameterCollection localParameters = new SybaseParameterCollection (this);
230 SybaseParameter parm;
232 parm = new SybaseParameter ("@P1", SybaseType.Int);
233 parm.Direction = ParameterDirection.Output;
234 localParameters.Add (parm);
236 parm = new SybaseParameter ("@P2", SybaseType.NVarChar);
237 parm.Value = parms.ToString ();
238 parm.Size = ((string) parm.Value).Length;
239 localParameters.Add (parm);
241 parm = new SybaseParameter ("@P3", SybaseType.NVarChar);
242 parm.Value = commandText;
243 parm.Size = ((string) parm.Value).Length;
244 localParameters.Add (parm);
246 return BuildProcedureCall ("sp_prepare", localParameters);
249 private static string BuildProcedureCall (string procedure, SybaseParameterCollection parameters)
251 StringBuilder parms = new StringBuilder ();
252 StringBuilder declarations = new StringBuilder ();
253 StringBuilder outParms = new StringBuilder ();
254 StringBuilder set = new StringBuilder ();
257 foreach (SybaseParameter parameter in parameters) {
258 string parmName = String.Format ("@P{0}", index);
260 switch (parameter.Direction) {
261 case ParameterDirection.Input :
262 if (parms.Length > 0)
264 parms.Append (FormatParameter (parameter));
266 case ParameterDirection.Output :
267 if (parms.Length > 0)
269 parms.Append (parmName);
270 parms.Append (" output");
272 if (outParms.Length > 0) {
273 outParms.Append (", ");
274 declarations.Append (", ");
277 outParms.Append ("select ");
278 declarations.Append ("declare ");
281 declarations.Append (parameter.Prepare (parmName));
282 set.Append (String.Format ("set {0}=NULL\n", parmName));
283 outParms.Append (parmName);
286 throw new NotImplementedException ("Only support input and output parameters.");
290 if (declarations.Length > 0)
291 declarations.Append ('\n');
293 return String.Format ("{0}{1}{2} {3}\n{4}", declarations.ToString (), set.ToString (), procedure, parms.ToString (), outParms.ToString ());
296 public void Cancel ()
298 if (Connection == null || Connection.Tds == null)
300 Connection.Tds.Cancel ();
303 internal void CloseDataReader (bool moreResults)
305 GetOutputParameters ();
306 Connection.DataReader = null;
308 if ((behavior & CommandBehavior.CloseConnection) != 0)
312 public SybaseParameter CreateParameter ()
314 return new SybaseParameter ();
317 internal void DeriveParameters ()
319 if (commandType != CommandType.StoredProcedure)
320 throw new InvalidOperationException (String.Format ("SybaseCommand DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{0}", commandType));
321 ValidateCommand ("DeriveParameters");
323 SybaseParameterCollection localParameters = new SybaseParameterCollection (this);
324 localParameters.Add ("@P1", SybaseType.NVarChar, commandText.Length).Value = commandText;
326 Connection.Tds.ExecuteQuery (BuildProcedureCall ("sp_procedure_params_rowset", localParameters));
327 SybaseDataReader reader = new SybaseDataReader (this);
329 object[] dbValues = new object[reader.FieldCount];
331 while (reader.Read ()) {
332 reader.GetValues (dbValues);
333 parameters.Add (new SybaseParameter (dbValues));
338 public int ExecuteNonQuery ()
340 ValidateCommand ("ExecuteNonQuery");
341 string sql = String.Empty;
344 if (Parameters.Count > 0)
345 sql = BuildCommand ();
350 result = Connection.Tds.ExecuteNonQuery (sql, CommandTimeout);
352 catch (TdsTimeoutException e) {
353 throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
356 GetOutputParameters ();
360 public SybaseDataReader ExecuteReader ()
362 return ExecuteReader (CommandBehavior.Default);
365 public SybaseDataReader ExecuteReader (CommandBehavior behavior)
367 ValidateCommand ("ExecuteReader");
368 this.behavior = behavior;
371 Connection.Tds.ExecuteQuery (BuildCommand (), CommandTimeout);
373 catch (TdsTimeoutException e) {
374 throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
377 Connection.DataReader = new SybaseDataReader (this);
378 return Connection.DataReader;
381 public object ExecuteScalar ()
383 ValidateCommand ("ExecuteScalar");
385 Connection.Tds.ExecuteQuery (BuildCommand (), CommandTimeout);
387 catch (TdsTimeoutException e) {
388 throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
391 if (!Connection.Tds.NextResult () || !Connection.Tds.NextRow ())
394 object result = Connection.Tds.ColumnValues [0];
395 CloseDataReader (true);
399 [MonoTODO ("Include offset from SybaseParameter for binary/string types.")]
400 static string FormatParameter (SybaseParameter parameter)
402 if (parameter.Value == null)
405 switch (parameter.SybaseType) {
406 case SybaseType.BigInt :
407 case SybaseType.Decimal :
408 case SybaseType.Float :
409 case SybaseType.Int :
410 case SybaseType.Money :
411 case SybaseType.Real :
412 case SybaseType.SmallInt :
413 case SybaseType.SmallMoney :
414 case SybaseType.TinyInt :
415 return parameter.Value.ToString ();
416 case SybaseType.NVarChar :
417 case SybaseType.NChar :
418 return String.Format ("N'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
419 case SybaseType.UniqueIdentifier :
420 return String.Format ("0x{0}", ((Guid) parameter.Value).ToString ("N"));
422 if (parameter.Value.GetType () == typeof (bool))
423 return (((bool) parameter.Value) ? "0x1" : "0x0");
424 return parameter.Value.ToString ();
425 case SybaseType.Image:
426 case SybaseType.Binary:
427 case SybaseType.VarBinary:
428 return String.Format ("0x{0}", BitConverter.ToString ((byte[]) parameter.Value).Replace ("-", "").ToLower ());
430 return String.Format ("'{0}'", parameter.Value.ToString ().Replace ("'", "''"));
434 private void GetOutputParameters ()
436 Connection.Tds.SkipToEnd ();
438 IList list = Connection.Tds.ColumnValues;
440 if (list != null && list.Count > 0) {
442 foreach (SybaseParameter parameter in parameters) {
443 if (parameter.Direction != ParameterDirection.Input) {
444 parameter.Value = list [index];
447 if (index >= list.Count)
453 object ICloneable.Clone ()
455 return new SybaseCommand (commandText, Connection);
458 IDbDataParameter IDbCommand.CreateParameter ()
460 return CreateParameter ();
463 IDataReader IDbCommand.ExecuteReader ()
465 return ExecuteReader ();
468 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
470 return ExecuteReader (behavior);
473 public void Prepare ()
475 ValidateCommand ("Prepare");
476 Connection.Tds.ExecuteNonQuery (BuildPrepare ());
478 if (Connection.Tds.OutputParameters.Count == 0 || Connection.Tds.OutputParameters[0] == null)
479 throw new Exception ("Could not prepare the statement.");
481 preparedStatements [commandText] = ((int) Connection.Tds.OutputParameters [0]).ToString ();
484 public void ResetCommandTimeout ()
489 private void ValidateCommand (string method)
491 if (Connection == null)
492 throw new InvalidOperationException (String.Format ("{0} requires a Connection object to continue.", method));
493 if (Connection.Transaction != null && transaction != Connection.Transaction)
494 throw new InvalidOperationException ("The Connection object does not have the same transaction as the command object.");
495 if (Connection.State != ConnectionState.Open)
496 throw new InvalidOperationException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
497 if (commandText == String.Empty || commandText == null)
498 throw new InvalidOperationException ("The command text for this Command has not been set.");
499 if (Connection.DataReader != null)
500 throw new InvalidOperationException ("There is already an open DataReader associated with this Connection which must be closed first.");
503 #endregion // Methods