1 // created on 21/5/2002 at 20:03
3 // Npgsql.NpgsqlCommand.cs
6 // Francisco Jr. (fxjrlists@yahoo.com.br)
8 // Copyright (C) 2002 The Npgsql Development Team
9 // npgsql-general@gborg.postgresql.org
10 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
12 // This library is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU Lesser General Public
14 // License as published by the Free Software Foundation; either
15 // version 2.1 of the License, or (at your option) any later version.
17 // This library is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 // Lesser General Public License for more details.
22 // You should have received a copy of the GNU Lesser General Public
23 // License along with this library; if not, write to the Free Software
24 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 using System.Resources;
30 using System.ComponentModel;
31 using System.Collections;
42 /// Represents a SQL statement or function (stored procedure) to execute
43 /// against a PostgreSQL database. This class cannot be inherited.
46 [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlCommand)), ToolboxItem(true)]
48 public sealed class NpgsqlCommand : Component, IDbCommand, ICloneable
50 // Logging related values
51 private static readonly String CLASSNAME = "NpgsqlCommand";
52 private static ResourceManager resman = new ResourceManager(typeof(NpgsqlCommand));
54 private NpgsqlConnection connection;
55 private NpgsqlConnector connector;
56 private NpgsqlTransaction transaction;
58 private Int32 timeout;
59 private CommandType type;
60 private NpgsqlParameterCollection parameters;
61 private String planName;
63 private NpgsqlParse parse;
64 private NpgsqlBind bind;
66 private Boolean invalidTransactionDetected = false;
68 private CommandBehavior commandBehavior;
73 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class.
75 public NpgsqlCommand() : this(String.Empty, null, null)
78 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query.
80 /// <param name="cmdText">The text of the query.</param>
81 public NpgsqlCommand(String cmdText) : this(cmdText, null, null)
84 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query and a <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>.
86 /// <param name="cmdText">The text of the query.</param>
87 /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
88 public NpgsqlCommand(String cmdText, NpgsqlConnection connection) : this(cmdText, connection, null)
91 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query, a <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>, and the <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>.
93 /// <param name="cmdText">The text of the query.</param>
94 /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
95 /// <param name="transaction">The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see> in which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.</param>
96 public NpgsqlCommand(String cmdText, NpgsqlConnection connection, NpgsqlTransaction transaction)
98 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
100 planName = String.Empty;
102 this.connection = connection;
103 if (this.connection != null)
104 this.connector = connection.Connector;
106 parameters = new NpgsqlParameterCollection();
108 type = CommandType.Text;
109 this.Transaction = transaction;
110 commandBehavior = CommandBehavior.Default;
116 /// Used to execute internal commands.
118 internal NpgsqlCommand(String cmdText, NpgsqlConnector connector)
120 resman = new System.Resources.ResourceManager(this.GetType());
121 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
123 planName = String.Empty;
125 this.connector = connector;
126 type = CommandType.Text;
127 commandBehavior = CommandBehavior.Default;
129 parameters = new NpgsqlParameterCollection();
133 // Public properties.
135 /// Gets or sets the SQL statement or function (stored procedure) to execute at the data source.
137 /// <value>The Transact-SQL statement or stored procedure to execute. The default is an empty string.</value>
138 [Category("Data"), DefaultValue("")]
139 public String CommandText {
147 // [TODO] Validate commandtext.
148 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandText", value);
150 planName = String.Empty;
153 commandBehavior = CommandBehavior.Default;
158 /// Gets or sets the wait time before terminating the attempt
159 /// to execute a command and generating an error.
161 /// <value>The time (in seconds) to wait for the command to execute.
162 /// The default is 20 seconds.</value>
164 public Int32 CommandTimeout {
173 throw new ArgumentOutOfRangeException(resman.GetString("Exception_CommandTimeoutLessZero"));
176 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandTimeout", value);
181 /// Gets or sets a value indicating how the
182 /// <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> property is to be interpreted.
184 /// <value>One of the <see cref="System.Data.CommandType">CommandType</see> values. The default is <see cref="System.Data.CommandType">CommandType.Text</see>.</value>
185 [Category("Data"), DefaultValue(CommandType.Text)]
186 public CommandType CommandType {
195 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandType", value);
199 IDbConnection IDbCommand.Connection
208 Connection = (NpgsqlConnection) value;
209 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Connection", value);
214 /// Gets or sets the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>
215 /// used by this instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
217 /// <value>The connection to a data source. The default value is a null reference.</value>
218 [Category("Behavior"), DefaultValue(null)]
219 public NpgsqlConnection Connection {
222 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Connection");
228 if (this.Connection == value)
231 //if (this.transaction != null && this.transaction.Connection == null)
232 // this.transaction = null;
234 if (this.transaction != null && this.connection != null && this.Connector.Transaction != null)
235 throw new InvalidOperationException(resman.GetString("Exception_SetConnectionInTransaction"));
238 this.connection = value;
240 if (this.connection != null)
241 connector = this.connection.Connector;
243 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Connection", value);
247 internal NpgsqlConnector Connector {
250 if (connector == null && this.connection != null)
251 connector = this.connection.Connector;
257 IDataParameterCollection IDbCommand.Parameters {
265 /// Gets the <see cref="Npgsql.NpgsqlParameterCollection">NpgsqlParameterCollection</see>.
267 /// <value>The parameters of the SQL statement or function (stored procedure). The default is an empty collection.</value>
269 [Category("Data"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
272 public NpgsqlParameterCollection Parameters {
275 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Parameters");
281 IDbTransaction IDbCommand.Transaction
290 Transaction = (NpgsqlTransaction) value;
291 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Transaction", value);
296 /// Gets or sets the <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
297 /// within which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.
299 /// <value>The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>.
300 /// The default value is a null reference.</value>
302 [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
305 public NpgsqlTransaction Transaction {
308 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Transaction");
310 if (this.transaction != null && this.transaction.Connection == null)
312 this.transaction = null;
314 return this.transaction;
319 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Transaction" ,value);
321 this.transaction = (NpgsqlTransaction) value;
326 /// Gets or sets how command results are applied to the <see cref="System.Data.DataRow">DataRow</see>
327 /// when used by the <see cref="System.Data.Common.DbDataAdapter.Update">Update</see>
328 /// method of the <see cref="System.Data.Common.DbDataAdapter">DbDataAdapter</see>.
330 /// <value>One of the <see cref="System.Data.UpdateRowSource">UpdateRowSource</see> values.</value>
332 [Category("Behavior"), DefaultValue(UpdateRowSource.Both)]
335 public UpdateRowSource UpdatedRowSource {
339 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "UpdatedRowSource");
341 return UpdateRowSource.Both;
346 throw new NotImplementedException();
351 /// Attempts to cancel the execution of a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
353 /// <remarks>This Method isn't implemented yet.</remarks>
356 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Cancel");
358 // [TODO] Finish method implementation.
359 throw new NotImplementedException();
363 /// Create a new command based on this one.
365 /// <returns>A new NpgsqlCommand object.</returns>
366 Object ICloneable.Clone()
372 /// Create a new connection based on this one.
374 /// <returns>A new NpgsqlConnection object.</returns>
375 public NpgsqlCommand Clone()
377 // TODO: Add consistency checks.
379 return new NpgsqlCommand(CommandText, Connection, Transaction);
383 /// Creates a new instance of an <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.
385 /// <returns>An <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.</returns>
386 IDbDataParameter IDbCommand.CreateParameter()
388 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.CreateParameter");
390 return (NpgsqlParameter) CreateParameter();
394 /// Creates a new instance of a <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.
396 /// <returns>A <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.</returns>
397 public NpgsqlParameter CreateParameter()
399 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateParameter");
401 return new NpgsqlParameter();
405 /// Executes a SQL statement against the connection and returns the number of rows affected.
407 /// <returns>The number of rows affected.</returns>
408 public Int32 ExecuteNonQuery()
410 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteNonQuery");
414 UpdateOutputParameters();
417 // If nothing is returned, just return -1.
418 if(Connector.Mediator.CompletedResponses.Count == 0)
423 // Check if the response is available.
424 String firstCompletedResponse = (String)Connector.Mediator.CompletedResponses[0];
426 if (firstCompletedResponse == null)
429 String[] ret_string_tokens = firstCompletedResponse.Split(null); // whitespace separator.
432 // Check if the command was insert, delete or update.
433 // Only theses commands return rows affected.
434 // [FIXME] Is there a better way to check this??
435 if ((String.Compare(ret_string_tokens[0], "INSERT", true) == 0) ||
436 (String.Compare(ret_string_tokens[0], "UPDATE", true) == 0) ||
437 (String.Compare(ret_string_tokens[0], "DELETE", true) == 0))
439 // The number of rows affected is in the third token for insert queries
440 // and in the second token for update and delete queries.
441 // In other words, it is the last token in the 0-based array.
443 return Int32.Parse(ret_string_tokens[ret_string_tokens.Length - 1]);
450 private void UpdateOutputParameters()
452 // Check if there was some resultset returned. If so, put the result in output parameters.
453 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "UpdateOutputParameters");
456 ArrayList resultSets = Connector.Mediator.ResultSets;
458 if (resultSets.Count != 0)
460 NpgsqlResultSet nrs = (NpgsqlResultSet)resultSets[0];
462 if ((nrs != null) && (nrs.Count > 0))
464 NpgsqlAsciiRow nar = (NpgsqlAsciiRow)nrs[0];
467 Boolean hasMapping = false;
469 // First check if there is any mapping between parameter name and resultset name.
470 // If so, just update output parameters which has mapping.
472 foreach (NpgsqlParameter p in Parameters)
476 if (nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1)) > -1)
482 catch(ArgumentOutOfRangeException)
489 foreach (NpgsqlParameter p in Parameters)
491 if (((p.Direction == ParameterDirection.Output) ||
492 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
496 p.Value = nar[nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1))];
499 catch(ArgumentOutOfRangeException)
506 foreach (NpgsqlParameter p in Parameters)
508 if (((p.Direction == ParameterDirection.Output) ||
509 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
523 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
524 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
525 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
527 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
528 IDataReader IDbCommand.ExecuteReader()
530 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader");
532 return (NpgsqlDataReader) ExecuteReader();
536 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
537 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
538 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
539 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
541 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
542 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
543 IDataReader IDbCommand.ExecuteReader(CommandBehavior cb)
545 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader", cb);
547 return (NpgsqlDataReader) ExecuteReader(cb);
551 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
552 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
553 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
555 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
556 public NpgsqlDataReader ExecuteReader()
558 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader");
560 return ExecuteReader(CommandBehavior.Default);
564 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
565 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
566 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
567 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
569 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
570 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
571 /// <remarks>Currently the CommandBehavior parameter is ignored.</remarks>
572 public NpgsqlDataReader ExecuteReader(CommandBehavior cb)
574 // [FIXME] No command behavior handling.
576 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader", cb);
577 commandBehavior = cb;
581 UpdateOutputParameters();
583 // Get the resultsets and create a Datareader with them.
584 return new NpgsqlDataReader(Connector.Mediator.ResultSets, Connector.Mediator.CompletedResponses, connection, cb);
588 /// This method binds the parameters from parameters collection to the bind
591 private void BindParameters()
594 if (parameters.Count != 0)
596 Object[] parameterValues = new Object[parameters.Count];
597 for (Int32 i = 0; i < parameters.Count; i++)
599 // Do not quote strings, or escape existing quotes - this will be handled by the backend.
600 // DBNull or null values are returned as null.
601 // TODO: Would it be better to remove this null special handling out of ConvertToBackend??
602 parameterValues[i] = parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, true);
604 bind.ParameterValues = parameterValues;
607 Connector.Bind(bind);
608 Connector.Mediator.RequireReadyForQuery = false;
611 connector.CheckErrorsAndNotifications();
615 /// Executes the query, and returns the first column of the first row
616 /// in the result set returned by the query. Extra columns or rows are ignored.
618 /// <returns>The first column of the first row in the result set,
619 /// or a null reference if the result set is empty.</returns>
620 public Object ExecuteScalar()
622 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteScalar");
624 /*if ((type == CommandType.Text) || (type == CommandType.StoredProcedure))
626 connection.Query(this);
630 connection.Execute(new NpgsqlExecute(bind.PortalName, 0));
633 throw new NotImplementedException(resman.GetString("Exception_CommandTypeTableDirect"));
638 // Now get the results.
639 // Only the first column of the first row must be returned.
642 ArrayList resultSets = Connector.Mediator.ResultSets;
644 // First data is the RowDescription object.
645 // Check all resultsets as insert commands could have been sent along
646 // with resultset queries. The insert commands return null and and some queries
647 // may return empty resultsets, so, if we find one of these, skip to next resultset.
648 // If no resultset is found, return null as per specification.
650 NpgsqlAsciiRow ascii_row = null;
651 foreach( NpgsqlResultSet nrs in resultSets )
653 if( (nrs != null) && (nrs.Count > 0) )
655 ascii_row = (NpgsqlAsciiRow) nrs[0];
664 /// Creates a prepared version of the command on a PostgreSQL server.
666 public void Prepare()
668 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Prepare");
670 // Check the connection state.
671 CheckConnectionState();
673 if (! Connector.SupportsPrepare)
675 return; // Do nothing.
678 if (connector.BackendProtocolVersion == ProtocolVersion.Version2)
680 NpgsqlCommand command = new NpgsqlCommand(GetPrepareCommandText(), connector );
681 command.ExecuteNonQuery();
685 // Use the extended query parsing...
686 //planName = "NpgsqlPlan" + Connector.NextPlanIndex();
687 planName = Connector.NextPlanName();
688 String portalName = Connector.NextPortalName();
690 parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] {});
692 Connector.Parse(parse);
693 Connector.Mediator.RequireReadyForQuery = false;
696 // Check for errors and/or notifications and do the Right Thing.
697 connector.CheckErrorsAndNotifications();
699 bind = new NpgsqlBind("", planName, new Int16[] {0}, null, new Int16[] {0});
705 /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
707 protected override void Dispose (bool disposing)
712 // Only if explicitly calling Close or dispose we still have access to
713 // managed resources.
714 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
715 if (connection != null)
717 connection.Dispose();
719 base.Dispose(disposing);
725 /// This method checks the connection state to see if the connection
726 /// is set or it is open. If one of this conditions is not met, throws
727 /// an InvalidOperationException
729 private void CheckConnectionState()
731 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CheckConnectionState");
734 // Check the connection state.
735 if (Connector == null || Connector.State != ConnectionState.Open)
737 throw new InvalidOperationException(resman.GetString("Exception_ConnectionNotOpen"));
742 /// This method substitutes the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see>, if exist, in the command
743 /// to their actual values.
744 /// The parameter name format is <b>:ParameterName</b>.
746 /// <returns>A version of <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> with the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see> inserted.</returns>
747 internal String GetCommandText()
749 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetCommandText");
751 if (planName == String.Empty)
752 return GetClearCommandText();
754 return GetPreparedCommandText();
758 private String GetClearCommandText()
760 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetClearCommandText");
762 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
764 Boolean functionReturnsRecord = false; // Functions don't return record by default.
766 String result = text;
768 if (type == CommandType.StoredProcedure)
771 functionReturnsRecord = CheckFunctionReturnRecord();
773 // Check if just procedure name was passed. If so, does not replace parameter names and just pass parameter values in order they were added in parameters collection.
774 if (!result.Trim().EndsWith(")"))
776 addProcedureParenthesis = true;
780 if (Connector.SupportsPrepare)
781 result = "select * from " + result; // This syntax is only available in 7.3+ as well SupportsPrepare.
783 result = "select " + result; //Only a single result return supported. 7.2 and earlier.
785 else if (type == CommandType.TableDirect)
786 return "select * from " + result; // There is no parameter support on table direct.
788 if (parameters == null || parameters.Count == 0)
790 if (addProcedureParenthesis)
793 if (functionReturnsRecord)
794 result = AddFunctionReturnsRecordSupport(result);
797 result = AddSingleRowBehaviorSupport(result);
805 for (Int32 i = 0; i < parameters.Count; i++)
807 NpgsqlParameter Param = parameters[i];
810 if ((Param.Direction == ParameterDirection.Input) ||
811 (Param.Direction == ParameterDirection.InputOutput))
814 // If parenthesis don't need to be added, they were added by user with parameter names. Replace them.
815 if (!addProcedureParenthesis)
817 // adding the '::<datatype>' on the end of a parameter is a highly
818 // questionable practice, but it is great for debugging!
819 // Removed as this was going in infinite loop when the parameter name had the same name of parameter
820 // type name. i.e.: parameter name called :text of type text. It would conflict with the parameter type name ::text.
821 result = ReplaceParameterValue(
824 Param.TypeInfo.ConvertToBackend(Param.Value, false)
827 result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + ",";
831 if (addProcedureParenthesis)
833 // Remove a trailing comma added from parameter handling above. If any.
834 // Maybe there are only output parameters.
835 if (result.EndsWith(","))
836 result = result.Remove(result.Length - 1, 1);
841 if (functionReturnsRecord)
842 result = AddFunctionReturnsRecordSupport(result);
844 return AddSingleRowBehaviorSupport(result);
849 private Boolean CheckFunctionReturnRecord()
852 if (Parameters.Count == 0)
855 String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = 'record' ) and proargtypes='{0}' and proname='{1}';";
857 StringBuilder parameterTypes = new StringBuilder("");
859 foreach(NpgsqlParameter p in Parameters)
861 if ((p.Direction == ParameterDirection.Input) ||
862 (p.Direction == ParameterDirection.InputOutput))
864 parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " ");
869 NpgsqlCommand c = new NpgsqlCommand(String.Format(returnRecordQuery, parameterTypes.ToString(), CommandText), Connection);
871 Boolean ret = (Boolean) c.ExecuteScalar();
873 // reset any responses just before getting new ones
874 connector.Mediator.ResetResponses();
881 private String AddFunctionReturnsRecordSupport(String OriginalResult)
884 StringBuilder sb = new StringBuilder(OriginalResult);
888 foreach(NpgsqlParameter p in Parameters)
890 if ((p.Direction == ParameterDirection.Output) ||
891 (p.Direction == ParameterDirection.InputOutput))
893 sb.Append(String.Format("{0} {1}, ", p.ParameterName.Substring(1), p.TypeInfo.Name));
897 String result = sb.ToString();
899 result = result.Remove(result.Length - 2, 1);
912 private String GetPreparedCommandText()
914 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPreparedCommandText");
916 if (parameters.Count == 0)
917 return "execute " + planName;
920 StringBuilder result = new StringBuilder("execute " + planName + '(');
923 for (Int32 i = 0; i < parameters.Count; i++)
925 result.Append(parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false) + ',');
928 result = result.Remove(result.Length - 1, 1);
931 return result.ToString();
937 private String GetParseCommandText()
939 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetParseCommandText");
941 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
943 String parseCommand = text;
945 if (type == CommandType.StoredProcedure)
947 // Check if just procedure name was passed. If so, does not replace parameter names and just pass parameter values in order they were added in parameters collection.
948 if (!parseCommand.Trim().EndsWith(")"))
950 addProcedureParenthesis = true;
954 parseCommand = "select * from " + parseCommand; // This syntax is only available in 7.3+ as well SupportsPrepare.
956 else if (type == CommandType.TableDirect)
957 return "select * from " + parseCommand; // There is no parameter support on TableDirect.
959 if (parameters.Count > 0)
961 // The ReplaceParameterValue below, also checks if the parameter is present.
963 String parameterName;
966 for (i = 0; i < parameters.Count; i++)
968 if ((parameters[i].Direction == ParameterDirection.Input) ||
969 (parameters[i].Direction == ParameterDirection.InputOutput))
972 if (!addProcedureParenthesis)
974 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
975 parameterName = parameters[i].ParameterName;
976 //textCommand = textCommand.Replace(':' + parameterName, "$" + (i+1));
977 parseCommand = ReplaceParameterValue(parseCommand, parameterName, "$" + (i+1) + "::" + parameters[i].TypeInfo.Name);
980 parseCommand += "$" + (i+1) + "::" + parameters[i].TypeInfo.Name;
986 if (addProcedureParenthesis)
987 return parseCommand + ")";
994 private String GetPrepareCommandText()
996 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPrepareCommandText");
998 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1000 planName = Connector.NextPlanName();
1002 StringBuilder command = new StringBuilder("prepare " + planName);
1004 String textCommand = text;
1006 if (type == CommandType.StoredProcedure)
1008 // Check if just procedure name was passed. If so, does not replace parameter names and just pass parameter values in order they were added in parameters collection.
1009 if (!textCommand.Trim().EndsWith(")"))
1011 addProcedureParenthesis = true;
1015 textCommand = "select * from " + textCommand;
1017 else if (type == CommandType.TableDirect)
1018 return "select * from " + textCommand; // There is no parameter support on TableDirect.
1021 if (parameters.Count > 0)
1023 // The ReplaceParameterValue below, also checks if the parameter is present.
1025 String parameterName;
1028 for (i = 0; i < parameters.Count; i++)
1030 if ((parameters[i].Direction == ParameterDirection.Input) ||
1031 (parameters[i].Direction == ParameterDirection.InputOutput))
1034 if (!addProcedureParenthesis)
1036 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1037 parameterName = parameters[i].ParameterName;
1038 // The space in front of '$' fixes a parsing problem in 7.3 server
1039 // which gives errors of operator when finding the caracters '=$' in
1041 textCommand = ReplaceParameterValue(textCommand, parameterName, " $" + (i+1));
1044 textCommand += " $" + (i+1);
1049 //[TODO] Check if there is any missing parameters in the query.
1050 // For while, an error is thrown saying about the ':' char.
1052 command.Append('(');
1054 for (i = 0; i < parameters.Count; i++)
1056 // command.Append(NpgsqlTypesHelper.GetDefaultTypeInfo(parameters[i].DbType));
1057 command.Append(parameters[i].TypeInfo.Name);
1059 command.Append(',');
1062 command = command.Remove(command.Length - 1, 1);
1063 command.Append(')');
1067 if (addProcedureParenthesis)
1070 command.Append(" as ");
1071 command.Append(textCommand);
1074 return command.ToString();
1079 private String ReplaceParameterValue(String result, String parameterName, String paramVal)
1081 Int32 resLen = result.Length;
1082 Int32 paramStart = result.IndexOf(parameterName);
1083 Int32 paramLen = parameterName.Length;
1084 Int32 paramEnd = paramStart + paramLen;
1085 Boolean found = false;
1088 while(paramStart > -1)
1090 if((resLen > paramEnd) &&
1091 (result[paramEnd] == ' ' ||
1092 result[paramEnd] == ',' ||
1093 result[paramEnd] == ')' ||
1094 result[paramEnd] == ';' ||
1095 result[paramEnd] == '\n' ||
1096 result[paramEnd] == '\r' ||
1097 result[paramEnd] == '\t'))
1099 result = result.Substring(0, paramStart) + paramVal + result.Substring(paramEnd);
1102 else if(resLen == paramEnd)
1104 result = result.Substring(0, paramStart)+ paramVal;
1109 resLen = result.Length;
1110 paramStart = result.IndexOf(parameterName, paramStart);
1111 paramEnd = paramStart + paramLen;
1115 throw new IndexOutOfRangeException (String.Format(resman.GetString("Exception_ParamNotInQuery"), parameterName));
1119 }//ReplaceParameterValue
1122 private String AddSingleRowBehaviorSupport(String ResultCommandText)
1125 ResultCommandText = ResultCommandText.Trim();
1127 if ((commandBehavior & CommandBehavior.SingleRow) > 0)
1129 if (ResultCommandText.EndsWith(";"))
1130 ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1131 ResultCommandText += " limit 1;";
1137 return ResultCommandText;
1142 private void ExecuteCommand()
1144 // Check the connection state first.
1145 CheckConnectionState();
1147 // reset any responses just before getting new ones
1148 connector.Mediator.ResetResponses();
1153 Connector.Query(this);
1155 // Check for errors and/or notifications and do the Right Thing.
1156 connector.CheckErrorsAndNotifications();
1165 connector.Execute(new NpgsqlExecute(bind.PortalName, 0));
1167 // Check for errors and/or notifications and do the Right Thing.
1168 connector.CheckErrorsAndNotifications();
1172 // As per documentation:
1173 // "[...] When an error is detected while processing any extended-query message,
1174 // the backend issues ErrorResponse, then reads and discards messages until a
1175 // Sync is reached, then issues ReadyForQuery and returns to normal message processing.[...]"
1176 // So, send a sync command if we get any problems.