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;
35 using System.Text.RegularExpressions;
44 /// Represents a SQL statement or function (stored procedure) to execute
45 /// against a PostgreSQL database. This class cannot be inherited.
48 [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlCommand)), ToolboxItem(true)]
50 public sealed class NpgsqlCommand : Component, IDbCommand, ICloneable
52 // Logging related values
53 private static readonly String CLASSNAME = "NpgsqlCommand";
54 private static ResourceManager resman = new ResourceManager(typeof(NpgsqlCommand));
55 private static readonly Regex parameterReplace = new Regex(@"([:@][\w\.]*)", RegexOptions.Singleline);
57 private NpgsqlConnection connection;
58 private NpgsqlConnector connector;
59 private NpgsqlTransaction transaction;
61 private Int32 timeout;
62 private CommandType type;
63 private NpgsqlParameterCollection parameters;
64 private String planName;
66 private NpgsqlParse parse;
67 private NpgsqlBind bind;
69 private Boolean invalidTransactionDetected = false;
71 private CommandBehavior commandBehavior;
73 private Int64 lastInsertedOID = 0;
78 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class.
80 public NpgsqlCommand() : this(String.Empty, null, null)
83 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query.
85 /// <param name="cmdText">The text of the query.</param>
86 public NpgsqlCommand(String cmdText) : this(cmdText, null, null)
89 /// 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>.
91 /// <param name="cmdText">The text of the query.</param>
92 /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
93 public NpgsqlCommand(String cmdText, NpgsqlConnection connection) : this(cmdText, connection, null)
96 /// 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>.
98 /// <param name="cmdText">The text of the query.</param>
99 /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
100 /// <param name="transaction">The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see> in which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.</param>
101 public NpgsqlCommand(String cmdText, NpgsqlConnection connection, NpgsqlTransaction transaction)
103 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
105 planName = String.Empty;
107 this.connection = connection;
109 if (this.connection != null)
110 this.connector = connection.Connector;
112 parameters = new NpgsqlParameterCollection();
114 type = CommandType.Text;
115 this.Transaction = transaction;
116 commandBehavior = CommandBehavior.Default;
122 /// Used to execute internal commands.
124 internal NpgsqlCommand(String cmdText, NpgsqlConnector connector)
126 resman = new System.Resources.ResourceManager(this.GetType());
127 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
130 planName = String.Empty;
132 this.connector = connector;
133 type = CommandType.Text;
134 commandBehavior = CommandBehavior.Default;
136 parameters = new NpgsqlParameterCollection();
140 // Public properties.
142 /// Gets or sets the SQL statement or function (stored procedure) to execute at the data source.
144 /// <value>The Transact-SQL statement or stored procedure to execute. The default is an empty string.</value>
145 [Category("Data"), DefaultValue("")]
146 public String CommandText {
154 // [TODO] Validate commandtext.
155 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandText", value);
157 planName = String.Empty;
160 commandBehavior = CommandBehavior.Default;
165 /// Gets or sets the wait time before terminating the attempt
166 /// to execute a command and generating an error.
168 /// <value>The time (in seconds) to wait for the command to execute.
169 /// The default is 20 seconds.</value>
171 public Int32 CommandTimeout {
180 throw new ArgumentOutOfRangeException(resman.GetString("Exception_CommandTimeoutLessZero"));
183 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandTimeout", value);
188 /// Gets or sets a value indicating how the
189 /// <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> property is to be interpreted.
191 /// <value>One of the <see cref="System.Data.CommandType">CommandType</see> values. The default is <see cref="System.Data.CommandType">CommandType.Text</see>.</value>
192 [Category("Data"), DefaultValue(CommandType.Text)]
193 public CommandType CommandType {
202 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandType", value);
206 IDbConnection IDbCommand.Connection
215 Connection = (NpgsqlConnection) value;
216 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Connection", value);
221 /// Gets or sets the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>
222 /// used by this instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
224 /// <value>The connection to a data source. The default value is a null reference.</value>
225 [Category("Behavior"), DefaultValue(null)]
226 public NpgsqlConnection Connection {
229 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Connection");
235 if (this.Connection == value)
238 //if (this.transaction != null && this.transaction.Connection == null)
239 // this.transaction = null;
241 if (this.transaction != null && this.connection != null && this.Connector.Transaction != null)
242 throw new InvalidOperationException(resman.GetString("Exception_SetConnectionInTransaction"));
245 this.connection = value;
247 if (this.connection != null)
248 connector = this.connection.Connector;
250 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Connection", value);
254 internal NpgsqlConnector Connector {
257 if (this.connection != null)
258 connector = this.connection.Connector;
264 IDataParameterCollection IDbCommand.Parameters {
272 /// Gets the <see cref="Npgsql.NpgsqlParameterCollection">NpgsqlParameterCollection</see>.
274 /// <value>The parameters of the SQL statement or function (stored procedure). The default is an empty collection.</value>
276 [Category("Data"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
279 public NpgsqlParameterCollection Parameters {
282 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Parameters");
288 IDbTransaction IDbCommand.Transaction
297 Transaction = (NpgsqlTransaction) value;
298 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Transaction", value);
303 /// Gets or sets the <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
304 /// within which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.
306 /// <value>The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>.
307 /// The default value is a null reference.</value>
309 [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
312 public NpgsqlTransaction Transaction {
315 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Transaction");
317 if (this.transaction != null && this.transaction.Connection == null)
319 this.transaction = null;
321 return this.transaction;
326 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Transaction" ,value);
328 this.transaction = (NpgsqlTransaction) value;
333 /// Gets or sets how command results are applied to the <see cref="System.Data.DataRow">DataRow</see>
334 /// when used by the <see cref="System.Data.Common.DbDataAdapter.Update">Update</see>
335 /// method of the <see cref="System.Data.Common.DbDataAdapter">DbDataAdapter</see>.
337 /// <value>One of the <see cref="System.Data.UpdateRowSource">UpdateRowSource</see> values.</value>
339 [Category("Behavior"), DefaultValue(UpdateRowSource.Both)]
342 public UpdateRowSource UpdatedRowSource {
346 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "UpdatedRowSource");
348 return UpdateRowSource.Both;
357 /// Returns oid of inserted row. This is only updated when using executenonQuery and when command inserts just a single row. If table is created without oids, this will always be 0.
360 public Int64 LastInsertedOID
364 return lastInsertedOID;
370 /// Attempts to cancel the execution of a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
372 /// <remarks>This Method isn't implemented yet.</remarks>
375 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Cancel");
377 // [TODO] Finish method implementation.
378 throw new NotImplementedException();
382 /// Create a new command based on this one.
384 /// <returns>A new NpgsqlCommand object.</returns>
385 Object ICloneable.Clone()
391 /// Create a new connection based on this one.
393 /// <returns>A new NpgsqlConnection object.</returns>
394 public NpgsqlCommand Clone()
396 // TODO: Add consistency checks.
398 return new NpgsqlCommand(CommandText, Connection, Transaction);
402 /// Creates a new instance of an <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.
404 /// <returns>An <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.</returns>
405 IDbDataParameter IDbCommand.CreateParameter()
407 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.CreateParameter");
409 return (NpgsqlParameter) CreateParameter();
413 /// Creates a new instance of a <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.
415 /// <returns>A <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.</returns>
416 public NpgsqlParameter CreateParameter()
418 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateParameter");
420 return new NpgsqlParameter();
424 /// Executes a SQL statement against the connection and returns the number of rows affected.
426 /// <returns>The number of rows affected.</returns>
427 public Int32 ExecuteNonQuery()
429 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteNonQuery");
431 // Initialize lastInsertOID
436 UpdateOutputParameters();
439 // If nothing is returned, just return -1.
440 if(Connector.Mediator.CompletedResponses.Count == 0)
445 // Check if the response is available.
446 String firstCompletedResponse = (String)Connector.Mediator.CompletedResponses[0];
448 if (firstCompletedResponse == null)
451 String[] ret_string_tokens = firstCompletedResponse.Split(null); // whitespace separator.
454 // Check if the command was insert, delete, update, fetch or move.
455 // Only theses commands return rows affected.
456 // [FIXME] Is there a better way to check this??
457 if ((String.Compare(ret_string_tokens[0], "INSERT", true) == 0) ||
458 (String.Compare(ret_string_tokens[0], "UPDATE", true) == 0) ||
459 (String.Compare(ret_string_tokens[0], "DELETE", true) == 0) ||
460 (String.Compare(ret_string_tokens[0], "FETCH", true) == 0) ||
461 (String.Compare(ret_string_tokens[0], "MOVE", true) == 0))
465 if (String.Compare(ret_string_tokens[0], "INSERT", true) == 0)
466 // Get oid of inserted row.
467 lastInsertedOID = Int32.Parse(ret_string_tokens[1]);
469 // The number of rows affected is in the third token for insert queries
470 // and in the second token for update and delete queries.
471 // In other words, it is the last token in the 0-based array.
473 return Int32.Parse(ret_string_tokens[ret_string_tokens.Length - 1]);
481 private void UpdateOutputParameters()
483 // Check if there was some resultset returned. If so, put the result in output parameters.
484 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "UpdateOutputParameters");
487 ArrayList resultSets = Connector.Mediator.ResultSets;
489 if (resultSets.Count != 0)
491 NpgsqlResultSet nrs = (NpgsqlResultSet)resultSets[0];
493 if ((nrs != null) && (nrs.Count > 0))
495 NpgsqlAsciiRow nar = (NpgsqlAsciiRow)nrs[0];
498 Boolean hasMapping = false;
500 // First check if there is any mapping between parameter name and resultset name.
501 // If so, just update output parameters which has mapping.
503 foreach (NpgsqlParameter p in Parameters)
507 if (nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1)) > -1)
513 catch(ArgumentOutOfRangeException)
520 foreach (NpgsqlParameter p in Parameters)
522 if (((p.Direction == ParameterDirection.Output) ||
523 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
527 p.Value = nar[nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1))];
530 catch(ArgumentOutOfRangeException)
537 foreach (NpgsqlParameter p in Parameters)
539 if (((p.Direction == ParameterDirection.Output) ||
540 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
554 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
555 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
556 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
558 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
559 IDataReader IDbCommand.ExecuteReader()
561 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader");
563 return (NpgsqlDataReader) ExecuteReader();
567 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
568 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
569 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
570 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
572 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
573 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
574 IDataReader IDbCommand.ExecuteReader(CommandBehavior cb)
576 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader", cb);
578 return (NpgsqlDataReader) ExecuteReader(cb);
582 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
583 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
584 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
586 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
587 public NpgsqlDataReader ExecuteReader()
589 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader");
591 return ExecuteReader(CommandBehavior.Default);
595 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
596 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
597 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
598 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
600 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
601 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
602 /// <remarks>Currently the CommandBehavior parameter is ignored.</remarks>
603 public NpgsqlDataReader ExecuteReader(CommandBehavior cb)
605 // [FIXME] No command behavior handling.
607 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader", cb);
608 commandBehavior = cb;
612 UpdateOutputParameters();
614 // Get the resultsets and create a Datareader with them.
615 return new NpgsqlDataReader(Connector.Mediator.ResultSets, Connector.Mediator.CompletedResponses, cb, this);
619 /// This method binds the parameters from parameters collection to the bind
622 private void BindParameters()
625 if (parameters.Count != 0)
627 Object[] parameterValues = new Object[parameters.Count];
628 Int16[] parameterFormatCodes = bind.ParameterFormatCodes;
630 for (Int32 i = 0; i < parameters.Count; i++)
632 // Do not quote strings, or escape existing quotes - this will be handled by the backend.
633 // DBNull or null values are returned as null.
634 // TODO: Would it be better to remove this null special handling out of ConvertToBackend??
636 // Do special handling of bytea values. They will be send in binary form.
637 // TODO: Add binary format support for all supported types. Not only bytea.
638 if (parameters[i].TypeInfo.NpgsqlDbType != NpgsqlDbType.Bytea)
641 parameterValues[i] = parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, true);
645 parameterFormatCodes[i] = (Int16) FormatCode.Binary;
646 parameterValues[i]=(byte[])parameters[i].Value;
649 bind.ParameterValues = parameterValues;
650 bind.ParameterFormatCodes = parameterFormatCodes;
653 Connector.Bind(bind);
655 // See Prepare() method for a discussion of this.
656 Connector.Mediator.RequireReadyForQuery = false;
660 connector.CheckErrorsAndNotifications();
664 /// Executes the query, and returns the first column of the first row
665 /// in the result set returned by the query. Extra columns or rows are ignored.
667 /// <returns>The first column of the first row in the result set,
668 /// or a null reference if the result set is empty.</returns>
669 public Object ExecuteScalar()
671 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteScalar");
675 // Now get the results.
676 // Only the first column of the first row must be returned.
679 ArrayList resultSets = Connector.Mediator.ResultSets;
681 // First data is the RowDescription object.
682 // Check all resultsets as insert commands could have been sent along
683 // with resultset queries. The insert commands return null and and some queries
684 // may return empty resultsets, so, if we find one of these, skip to next resultset.
685 // If no resultset is found, return null as per specification.
687 NpgsqlAsciiRow ascii_row = null;
688 foreach( NpgsqlResultSet nrs in resultSets )
690 if( (nrs != null) && (nrs.Count > 0) )
692 ascii_row = (NpgsqlAsciiRow) nrs[0];
701 /// Creates a prepared version of the command on a PostgreSQL server.
703 public void Prepare()
705 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Prepare");
707 // Check the connection state.
708 CheckConnectionState();
710 // reset any responses just before getting new ones
711 Connector.Mediator.ResetResponses();
713 // Set command timeout.
714 connector.Mediator.CommandTimeout = CommandTimeout;
716 if (! connector.SupportsPrepare)
718 return; // Do nothing.
721 if (connector.BackendProtocolVersion == ProtocolVersion.Version2)
723 NpgsqlCommand command = new NpgsqlCommand(GetPrepareCommandText(), connector );
724 command.ExecuteNonQuery();
731 connector.StopNotificationThread();
733 // Use the extended query parsing...
734 planName = connector.NextPlanName();
735 String portalName = connector.NextPortalName();
737 parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] {});
739 connector.Parse(parse);
741 // We need that because Flush() doesn't cause backend to send
742 // ReadyForQuery on error. Without ReadyForQuery, we don't return
743 // from query extended processing.
745 // We could have used Connector.Flush() which sends us back a
746 // ReadyForQuery, but on postgresql server below 8.1 there is an error
747 // with extended query processing which hinders us from using it.
748 connector.Mediator.RequireReadyForQuery = false;
751 // Check for errors and/or notifications and do the Right Thing.
752 connector.CheckErrorsAndNotifications();
756 NpgsqlDescribe describe = new NpgsqlDescribe('S', planName);
759 connector.Describe(describe);
763 Npgsql.NpgsqlRowDescription returnRowDesc = connector.Mediator.LastRowDescription;
765 Int16[] resultFormatCodes;
768 if (returnRowDesc != null)
770 resultFormatCodes = new Int16[returnRowDesc.NumFields];
772 for (int i=0; i < returnRowDesc.NumFields; i++)
774 Npgsql.NpgsqlRowDescriptionFieldData returnRowDescData = returnRowDesc[i];
777 if (returnRowDescData.type_info != null && returnRowDescData.type_info.NpgsqlDbType == NpgsqlTypes.NpgsqlDbType.Bytea)
780 resultFormatCodes[i] = (Int16)FormatCode.Binary;
784 resultFormatCodes[i] = (Int16)FormatCode.Text;
790 resultFormatCodes = new Int16[]{0};
792 bind = new NpgsqlBind("", planName, new Int16[Parameters.Count], null, resultFormatCodes);
796 // See ExecuteCommand method for a discussion of this.
803 connector.ResumeNotificationThread();
813 /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
815 protected override void Dispose (bool disposing)
820 // Only if explicitly calling Close or dispose we still have access to
821 // managed resources.
822 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
823 if (connection != null)
825 connection.Dispose();
827 base.Dispose(disposing);
833 /// This method checks the connection state to see if the connection
834 /// is set or it is open. If one of this conditions is not met, throws
835 /// an InvalidOperationException
837 private void CheckConnectionState()
839 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CheckConnectionState");
842 // Check the connection state.
843 if (Connector == null || Connector.State != ConnectionState.Open)
845 throw new InvalidOperationException(resman.GetString("Exception_ConnectionNotOpen"));
850 /// This method substitutes the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see>, if exist, in the command
851 /// to their actual values.
852 /// The parameter name format is <b>:ParameterName</b>.
854 /// <returns>A version of <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> with the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see> inserted.</returns>
855 internal String GetCommandText()
857 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetCommandText");
859 if (planName == String.Empty)
860 return GetClearCommandText();
862 return GetPreparedCommandText();
866 private String GetClearCommandText()
868 if (NpgsqlEventLog.Level == LogLevel.Debug)
869 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetClearCommandText");
871 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
873 Boolean functionReturnsRecord = false; // Functions don't return record by default.
875 Boolean functionReturnsRefcursor = false; // Functions don't return refcursor by default.
877 String result = text;
879 if (type == CommandType.StoredProcedure)
882 if (Parameters.Count > 0)
883 functionReturnsRecord = CheckFunctionReturn("record");
885 functionReturnsRefcursor = CheckFunctionReturn("refcursor");
887 // 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. Also check if command text finishes in a ";" which would make Npgsql incorrectly append a "()" when executing this command text.
888 if ((!result.Trim().EndsWith(")")) && (!result.Trim().EndsWith(";")))
890 addProcedureParenthesis = true;
894 if (Connector.SupportsPrepare)
895 result = "select * from " + result; // This syntax is only available in 7.3+ as well SupportsPrepare.
897 result = "select " + result; //Only a single result return supported. 7.2 and earlier.
899 else if (type == CommandType.TableDirect)
900 return "select * from " + result; // There is no parameter support on table direct.
902 if (parameters == null || parameters.Count == 0)
904 if (addProcedureParenthesis)
908 // If function returns ref cursor just process refcursor-result function call
909 // and return command which will be used to return data from refcursor.
911 if (functionReturnsRefcursor)
912 return ProcessRefcursorFunctionReturn(result);
915 if (functionReturnsRecord)
916 result = AddFunctionReturnsRecordSupport(result);
919 result = AddSingleRowBehaviorSupport(result);
921 result = AddSchemaOnlyBehaviorSupport(result);
927 // Get parameters in query string to translate them to their actual values.
929 // This regular expression gets all the parameters in format :param or @param
930 // and everythingelse.
931 // This is only needed if query string has parameters. Else, just append the
932 // parameter values in order they were put in parameter collection.
935 // If parenthesis don't need to be added, they were added by user with parameter names. Replace them.
936 if (!addProcedureParenthesis)
938 StringBuilder sb = new StringBuilder();
940 string[] queryparts = parameterReplace.Split(result);
942 foreach (String s in queryparts)
944 if (s == string.Empty)
947 if ((s[0] == ':' || s[0] == '@') &&
948 Parameters.TryGetValue(s, out p))
950 // It's a parameter. Lets handle it.
951 if ((p.Direction == ParameterDirection.Input) ||
952 (p.Direction == ParameterDirection.InputOutput))
955 // adding the '::<datatype>' on the end of a parameter is a highly
956 // questionable practice, but it is great for debugging!
957 sb.Append(p.TypeInfo.ConvertToBackend(p.Value, false));
959 // Only add data type info if we are calling an stored procedure.
961 if (type == CommandType.StoredProcedure)
964 sb.Append(p.TypeInfo.Name);
966 if (p.TypeInfo.UseSize && (p.Size > 0))
967 sb.Append("(").Append(p.Size).Append(")");
976 result = sb.ToString();
982 for (Int32 i = 0; i < parameters.Count; i++)
984 NpgsqlParameter Param = parameters[i];
987 if ((Param.Direction == ParameterDirection.Input) ||
988 (Param.Direction == ParameterDirection.InputOutput))
991 result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + "::" + Param.TypeInfo.Name + ",";
995 // Remove a trailing comma added from parameter handling above. If any.
996 // Maybe there are only output parameters. If so, there will be no comma.
997 if (result.EndsWith(","))
998 result = result.Remove(result.Length - 1, 1);
1003 if (functionReturnsRecord)
1004 result = AddFunctionReturnsRecordSupport(result);
1006 // If function returns ref cursor just process refcursor-result function call
1007 // and return command which will be used to return data from refcursor.
1009 if (functionReturnsRefcursor)
1010 return ProcessRefcursorFunctionReturn(result);
1013 result = AddSingleRowBehaviorSupport(result);
1015 result = AddSchemaOnlyBehaviorSupport(result);
1022 private Boolean CheckFunctionReturn(String ReturnType)
1025 String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = :typename ) and proargtypes=:proargtypes and proname=:proname;";
1027 StringBuilder parameterTypes = new StringBuilder("");
1029 foreach(NpgsqlParameter p in Parameters)
1031 if ((p.Direction == ParameterDirection.Input) ||
1032 (p.Direction == ParameterDirection.InputOutput))
1034 parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " ");
1039 NpgsqlCommand c = new NpgsqlCommand(returnRecordQuery, Connection);
1041 c.Parameters.Add(new NpgsqlParameter("typename", NpgsqlDbType.Text));
1042 c.Parameters.Add(new NpgsqlParameter("proargtypes", NpgsqlDbType.Text));
1043 c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text));
1045 c.Parameters[0].Value = ReturnType;
1046 c.Parameters[1].Value = parameterTypes.ToString();
1047 c.Parameters[2].Value = CommandText;
1050 Boolean ret = (Boolean) c.ExecuteScalar();
1052 // reset any responses just before getting new ones
1053 connector.Mediator.ResetResponses();
1055 // Set command timeout.
1056 connector.Mediator.CommandTimeout = CommandTimeout;
1064 private String AddFunctionReturnsRecordSupport(String OriginalResult)
1067 StringBuilder sb = new StringBuilder(OriginalResult);
1071 foreach(NpgsqlParameter p in Parameters)
1073 if ((p.Direction == ParameterDirection.Output) ||
1074 (p.Direction == ParameterDirection.InputOutput))
1076 sb.Append(String.Format("{0} {1}, ", p.ParameterName.Substring(1), p.TypeInfo.Name));
1080 String result = sb.ToString();
1082 result = result.Remove(result.Length - 2, 1);
1094 /// This methods takes a string with a function call witch returns a refcursor or a set of
1095 /// refcursor. It will return the names of the open cursors/portals which will hold
1096 /// results. In turn, it returns the string which is needed to get the data of this cursors
1097 /// in form of one resultset for each cursor open. This way, clients don't need to do anything
1098 /// else besides calling function normally to get results in this way.
1101 private String ProcessRefcursorFunctionReturn(String FunctionCall)
1103 NpgsqlCommand c = new NpgsqlCommand(FunctionCall, Connection);
1105 NpgsqlDataReader dr = c.ExecuteReader();
1107 StringBuilder sb = new StringBuilder();
1111 sb.Append("fetch all from \"").Append(dr.GetString(0)).Append("\";");
1115 sb.Append(";"); // Just in case there is no response from refcursor function return.
1117 // reset any responses just before getting new ones
1118 connector.Mediator.ResetResponses();
1120 // Set command timeout.
1121 connector.Mediator.CommandTimeout = CommandTimeout;
1123 return sb.ToString();
1130 private String GetPreparedCommandText()
1132 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPreparedCommandText");
1134 if (parameters.Count == 0)
1135 return "execute " + planName;
1138 StringBuilder result = new StringBuilder("execute " + planName + '(');
1141 for (Int32 i = 0; i < parameters.Count; i++)
1143 result.Append(parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false) + ',');
1146 result = result.Remove(result.Length - 1, 1);
1149 return result.ToString();
1155 private String GetParseCommandText()
1157 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetParseCommandText");
1159 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1161 String parseCommand = text;
1163 if (type == CommandType.StoredProcedure)
1165 // 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.
1166 if (!parseCommand.Trim().EndsWith(")"))
1168 addProcedureParenthesis = true;
1169 parseCommand += "(";
1172 parseCommand = "select * from " + parseCommand; // This syntax is only available in 7.3+ as well SupportsPrepare.
1174 else if (type == CommandType.TableDirect)
1175 return "select * from " + parseCommand; // There is no parameter support on TableDirect.
1177 if (parameters.Count > 0)
1179 // The ReplaceParameterValue below, also checks if the parameter is present.
1181 String parameterName;
1184 for (i = 0; i < parameters.Count; i++)
1186 if ((parameters[i].Direction == ParameterDirection.Input) ||
1187 (parameters[i].Direction == ParameterDirection.InputOutput))
1190 if (!addProcedureParenthesis)
1192 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1193 parameterName = parameters[i].ParameterName;
1194 //textCommand = textCommand.Replace(':' + parameterName, "$" + (i+1));
1195 parseCommand = ReplaceParameterValue(parseCommand, parameterName, "$" + (i+1) + "::" + parameters[i].TypeInfo.Name);
1198 parseCommand += "$" + (i+1) + "::" + parameters[i].TypeInfo.Name;
1204 if (addProcedureParenthesis)
1205 return parseCommand + ")";
1207 return parseCommand;
1212 private String GetPrepareCommandText()
1214 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPrepareCommandText");
1216 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1218 planName = Connector.NextPlanName();
1220 StringBuilder command = new StringBuilder("prepare " + planName);
1222 String textCommand = text;
1224 if (type == CommandType.StoredProcedure)
1226 // 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.
1227 if (!textCommand.Trim().EndsWith(")"))
1229 addProcedureParenthesis = true;
1233 textCommand = "select * from " + textCommand;
1235 else if (type == CommandType.TableDirect)
1236 return "select * from " + textCommand; // There is no parameter support on TableDirect.
1239 if (parameters.Count > 0)
1241 // The ReplaceParameterValue below, also checks if the parameter is present.
1243 String parameterName;
1246 for (i = 0; i < parameters.Count; i++)
1248 if ((parameters[i].Direction == ParameterDirection.Input) ||
1249 (parameters[i].Direction == ParameterDirection.InputOutput))
1252 if (!addProcedureParenthesis)
1254 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1255 parameterName = parameters[i].ParameterName;
1256 // The space in front of '$' fixes a parsing problem in 7.3 server
1257 // which gives errors of operator when finding the caracters '=$' in
1259 textCommand = ReplaceParameterValue(textCommand, parameterName, " $" + (i+1));
1262 textCommand += " $" + (i+1);
1267 //[TODO] Check if there are any missing parameters in the query.
1268 // For while, an error is thrown saying about the ':' char.
1270 command.Append('(');
1272 for (i = 0; i < parameters.Count; i++)
1274 // command.Append(NpgsqlTypesHelper.GetDefaultTypeInfo(parameters[i].DbType));
1275 command.Append(parameters[i].TypeInfo.Name);
1277 command.Append(',');
1280 command = command.Remove(command.Length - 1, 1);
1281 command.Append(')');
1285 if (addProcedureParenthesis)
1288 command.Append(" as ");
1289 command.Append(textCommand);
1292 return command.ToString();
1297 private static String ReplaceParameterValue(String result, String parameterName, String paramVal)
1300 String quote_pattern = @"['][^']*[']";
1301 String pattern = "[- |\n\r\t,)(;=+/]" + parameterName + "([- |\n\r\t,)(;=+/]|$)";
1303 String withoutquote = result;
1304 Boolean found = false;
1306 // Suppress quoted string from query (because we ave to ignore them)
1307 MatchCollection results = Regex.Matches(result,quote_pattern);
1308 foreach (Match match in results)
1310 start = match.Index;
1311 end = match.Index + match.Length;
1312 String spaces = new String(' ', match.Length-2);
1313 withoutquote = withoutquote.Substring(0,start + 1) + spaces + withoutquote.Substring(end - 1);
1317 // Now we look for the searched parameters on the "withoutquote" string
1318 results = Regex.Matches(withoutquote,pattern);
1319 if (results.Count == 0)
1320 // If no parameter is found, go out!
1322 // We take the first parameter found
1324 Match match = results[0];
1325 start = match.Index;
1326 if ((match.Length - parameterName.Length) == 2)
1327 // If the found string is not the end of the string
1328 end = match.Index + match.Length - 1;
1330 // If the found string is the end of the string
1331 end = match.Index + match.Length;
1332 result = result.Substring(0, start + 1) + paramVal + result.Substring(end);
1333 withoutquote = withoutquote.Substring(0,start + 1) + paramVal + withoutquote.Substring(end);
1337 throw new IndexOutOfRangeException (String.Format(resman.GetString("Exception_ParamNotInQuery"),
1343 private String AddSingleRowBehaviorSupport(String ResultCommandText)
1346 ResultCommandText = ResultCommandText.Trim();
1348 // Do not add SingleRowBehavior if SchemaOnly behavior is set.
1350 if ((commandBehavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly)
1351 return ResultCommandText;
1353 if ((commandBehavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow)
1355 if (ResultCommandText.EndsWith(";"))
1356 ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1357 ResultCommandText += " limit 1;";
1363 return ResultCommandText;
1367 private String AddSchemaOnlyBehaviorSupport(String ResultCommandText)
1370 ResultCommandText = ResultCommandText.Trim();
1372 if ((commandBehavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly)
1374 if (ResultCommandText.EndsWith(";"))
1375 ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1376 ResultCommandText += " limit 0;";
1381 return ResultCommandText;
1386 private void ExecuteCommand()
1388 // Check the connection state first.
1389 CheckConnectionState();
1391 // reset any responses just before getting new ones
1392 Connector.Mediator.ResetResponses();
1394 // Set command timeout.
1395 connector.Mediator.CommandTimeout = CommandTimeout;
1398 connector.StopNotificationThread();
1403 connector.Query(this);
1406 connector.ResumeNotificationThread();
1408 // Check for errors and/or notifications and do the Right Thing.
1409 connector.CheckErrorsAndNotifications();
1421 connector.Execute(new NpgsqlExecute(bind.PortalName, 0));
1423 // Check for errors and/or notifications and do the Right Thing.
1424 connector.CheckErrorsAndNotifications();
1428 // As per documentation:
1429 // "[...] When an error is detected while processing any extended-query message,
1430 // the backend issues ErrorResponse, then reads and discards messages until a
1431 // Sync is reached, then issues ReadyForQuery and returns to normal message processing.[...]"
1432 // So, send a sync command if we get any problems.
1440 connector.ResumeNotificationThread();