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;
114 /// Used to execute internal commands.
116 internal NpgsqlCommand(String cmdText, NpgsqlConnector connector)
118 resman = new System.Resources.ResourceManager(this.GetType());
119 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
121 planName = String.Empty;
123 this.connector = connector;
124 type = CommandType.Text;
125 commandBehavior = CommandBehavior.Default;
128 // Public properties.
130 /// Gets or sets the SQL statement or function (stored procedure) to execute at the data source.
132 /// <value>The Transact-SQL statement or stored procedure to execute. The default is an empty string.</value>
133 [Category("Data"), DefaultValue("")]
134 public String CommandText {
142 // [TODO] Validate commandtext.
143 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandText", value);
145 planName = String.Empty;
148 commandBehavior = CommandBehavior.Default;
153 /// Gets or sets the wait time before terminating the attempt
154 /// to execute a command and generating an error.
156 /// <value>The time (in seconds) to wait for the command to execute.
157 /// The default is 20 seconds.</value>
159 public Int32 CommandTimeout {
168 throw new ArgumentOutOfRangeException(resman.GetString("Exception_CommandTimeoutLessZero"));
171 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandTimeout", value);
176 /// Gets or sets a value indicating how the
177 /// <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> property is to be interpreted.
179 /// <value>One of the <see cref="System.Data.CommandType">CommandType</see> values. The default is <see cref="System.Data.CommandType">CommandType.Text</see>.</value>
180 [Category("Data"), DefaultValue(CommandType.Text)]
181 public CommandType CommandType {
190 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandType", value);
194 IDbConnection IDbCommand.Connection
203 Connection = (NpgsqlConnection) value;
204 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Connection", value);
209 /// Gets or sets the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>
210 /// used by this instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
212 /// <value>The connection to a data source. The default value is a null reference.</value>
213 [Category("Behavior"), DefaultValue(null)]
214 public NpgsqlConnection Connection {
217 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Connection");
223 if (this.Connection == value)
226 if (this.transaction != null && this.transaction.Connection == null)
227 this.transaction = null;
228 if (this.connection != null && this.Connector.Transaction != null)
229 throw new InvalidOperationException(resman.GetString("Exception_SetConnectionInTransaction"));
232 this.connection = value;
233 if (this.connection != null)
234 connector = this.connection.Connector;
236 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Connection", value);
240 internal NpgsqlConnector Connector {
243 if (connector == null && this.connection != null)
244 connector = this.connection.Connector;
250 IDataParameterCollection IDbCommand.Parameters {
258 /// Gets the <see cref="Npgsql.NpgsqlParameterCollection">NpgsqlParameterCollection</see>.
260 /// <value>The parameters of the SQL statement or function (stored procedure). The default is an empty collection.</value>
262 [Category("Data"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
265 public NpgsqlParameterCollection Parameters {
268 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Parameters");
274 IDbTransaction IDbCommand.Transaction
283 Transaction = (NpgsqlTransaction) value;
284 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Transaction", value);
289 /// Gets or sets the <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
290 /// within which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.
292 /// <value>The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>.
293 /// The default value is a null reference.</value>
295 [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
298 public NpgsqlTransaction Transaction {
301 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Transaction");
303 if (this.transaction != null && this.transaction.Connection == null)
305 this.transaction = null;
307 return this.transaction;
312 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Transaction" ,value);
314 this.transaction = (NpgsqlTransaction) value;
319 /// Gets or sets how command results are applied to the <see cref="System.Data.DataRow">DataRow</see>
320 /// when used by the <see cref="System.Data.Common.DbDataAdapter.Update">Update</see>
321 /// method of the <see cref="System.Data.Common.DbDataAdapter">DbDataAdapter</see>.
323 /// <value>One of the <see cref="System.Data.UpdateRowSource">UpdateRowSource</see> values.</value>
325 [Category("Behavior"), DefaultValue(UpdateRowSource.Both)]
328 public UpdateRowSource UpdatedRowSource {
332 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "UpdatedRowSource");
334 return UpdateRowSource.Both;
339 throw new NotImplementedException();
344 /// Attempts to cancel the execution of a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
346 /// <remarks>This Method isn't implemented yet.</remarks>
349 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Cancel");
351 // [TODO] Finish method implementation.
352 throw new NotImplementedException();
356 /// Create a new command based on this one.
358 /// <returns>A new NpgsqlCommand object.</returns>
359 Object ICloneable.Clone()
365 /// Create a new connection based on this one.
367 /// <returns>A new NpgsqlConnection object.</returns>
368 public NpgsqlCommand Clone()
370 // TODO: Add consistency checks.
372 return new NpgsqlCommand(CommandText, Connection, Transaction);
376 /// Creates a new instance of an <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.
378 /// <returns>An <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.</returns>
379 IDbDataParameter IDbCommand.CreateParameter()
381 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.CreateParameter");
383 return (NpgsqlParameter) CreateParameter();
387 /// Creates a new instance of a <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.
389 /// <returns>A <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.</returns>
390 public NpgsqlParameter CreateParameter()
392 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateParameter");
394 return new NpgsqlParameter();
398 /// Executes a SQL statement against the connection and returns the number of rows affected.
400 /// <returns>The number of rows affected.</returns>
401 public Int32 ExecuteNonQuery()
403 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteNonQuery");
407 UpdateOutputParameters();
410 // If nothing is returned, just return -1.
411 if(Connector.Mediator.CompletedResponses.Count == 0)
416 // Check if the response is available.
417 String firstCompletedResponse = (String)Connector.Mediator.CompletedResponses[0];
419 if (firstCompletedResponse == null)
422 String[] ret_string_tokens = firstCompletedResponse.Split(null); // whitespace separator.
425 // Check if the command was insert, delete or update.
426 // Only theses commands return rows affected.
427 // [FIXME] Is there a better way to check this??
428 if ((String.Compare(ret_string_tokens[0], "INSERT", true) == 0) ||
429 (String.Compare(ret_string_tokens[0], "UPDATE", true) == 0) ||
430 (String.Compare(ret_string_tokens[0], "DELETE", true) == 0))
432 // The number of rows affected is in the third token for insert queries
433 // and in the second token for update and delete queries.
434 // In other words, it is the last token in the 0-based array.
436 return Int32.Parse(ret_string_tokens[ret_string_tokens.Length - 1]);
443 private void UpdateOutputParameters()
445 // Check if there was some resultset returned. If so, put the result in output parameters.
448 ArrayList resultSets = Connector.Mediator.ResultSets;
450 if (resultSets.Count != 0)
452 NpgsqlResultSet nrs = (NpgsqlResultSet)resultSets[0];
454 if ((nrs != null) && (nrs.Count > 0))
456 NpgsqlAsciiRow nar = (NpgsqlAsciiRow)nrs[0];
459 Boolean hasMapping = false;
461 // First check if there is any mapping between parameter name and resultset name.
462 // If so, just update output parameters which has mapping.
464 foreach (NpgsqlParameter p in Parameters)
468 if (nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1)) > -1)
474 catch(ArgumentOutOfRangeException)
481 foreach (NpgsqlParameter p in Parameters)
483 if (((p.Direction == ParameterDirection.Output) ||
484 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
488 p.Value = nar[nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1))];
491 catch(ArgumentOutOfRangeException)
498 foreach (NpgsqlParameter p in Parameters)
500 if (((p.Direction == ParameterDirection.Output) ||
501 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
515 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
516 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
517 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
519 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
520 IDataReader IDbCommand.ExecuteReader()
522 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader");
524 return (NpgsqlDataReader) ExecuteReader();
528 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
529 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
530 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
531 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
533 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
534 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
535 IDataReader IDbCommand.ExecuteReader(CommandBehavior cb)
537 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader", cb);
539 return (NpgsqlDataReader) ExecuteReader(cb);
543 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
544 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
545 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
547 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
548 public NpgsqlDataReader ExecuteReader()
550 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader");
552 return ExecuteReader(CommandBehavior.Default);
556 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
557 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
558 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
559 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
561 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
562 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
563 /// <remarks>Currently the CommandBehavior parameter is ignored.</remarks>
564 public NpgsqlDataReader ExecuteReader(CommandBehavior cb)
566 // [FIXME] No command behavior handling.
568 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader", cb);
569 commandBehavior = cb;
573 // Get the resultsets and create a Datareader with them.
574 return new NpgsqlDataReader(Connector.Mediator.ResultSets, Connector.Mediator.CompletedResponses, connection, cb);
578 /// This method binds the parameters from parameters collection to the bind
581 private void BindParameters()
584 if (parameters.Count != 0)
586 Object[] parameterValues = new Object[parameters.Count];
587 for (Int32 i = 0; i < parameters.Count; i++)
589 // Do not quote strings, or escape existing quotes - this will be handled by the backend.
590 // DBNull or null values are returned as null.
591 // TODO: Would it be better to remove this null special handling out of ConvertToBackend??
592 parameterValues[i] = parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, true);
594 bind.ParameterValues = parameterValues;
597 Connector.Bind(bind);
598 Connector.Mediator.RequireReadyForQuery = false;
601 connector.CheckErrorsAndNotifications();
605 /// Executes the query, and returns the first column of the first row
606 /// in the result set returned by the query. Extra columns or rows are ignored.
608 /// <returns>The first column of the first row in the result set,
609 /// or a null reference if the result set is empty.</returns>
610 public Object ExecuteScalar()
612 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteScalar");
614 /*if ((type == CommandType.Text) || (type == CommandType.StoredProcedure))
616 connection.Query(this);
620 connection.Execute(new NpgsqlExecute(bind.PortalName, 0));
623 throw new NotImplementedException(resman.GetString("Exception_CommandTypeTableDirect"));
628 // Now get the results.
629 // Only the first column of the first row must be returned.
632 ArrayList resultSets = Connector.Mediator.ResultSets;
634 // First data is the RowDescription object.
635 // Check all resultsets as insert commands could have been sent along
636 // with resultset queries. The insert commands return null and and some queries
637 // may return empty resultsets, so, if we find one of these, skip to next resultset.
638 // If no resultset is found, return null as per specification.
640 NpgsqlAsciiRow ascii_row = null;
641 foreach( NpgsqlResultSet nrs in resultSets )
643 if( (nrs != null) && (nrs.Count > 0) )
645 ascii_row = (NpgsqlAsciiRow) nrs[0];
654 /// Creates a prepared version of the command on a PostgreSQL server.
656 public void Prepare()
658 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Prepare");
660 // Check the connection state.
661 CheckConnectionState();
663 if (! Connector.SupportsPrepare)
665 return; // Do nothing.
668 if (connector.BackendProtocolVersion == ProtocolVersion.Version2)
670 NpgsqlCommand command = new NpgsqlCommand(GetPrepareCommandText(), connector );
671 command.ExecuteNonQuery();
675 // Use the extended query parsing...
676 //planName = "NpgsqlPlan" + Connector.NextPlanIndex();
677 planName = Connector.NextPlanName();
678 String portalName = Connector.NextPortalName();
680 parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] {});
682 Connector.Parse(parse);
683 Connector.Mediator.RequireReadyForQuery = false;
686 // Check for errors and/or notifications and do the Right Thing.
687 connector.CheckErrorsAndNotifications();
689 bind = new NpgsqlBind(portalName, planName, new Int16[] {0}, null, new Int16[] {0});
695 /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
697 protected override void Dispose (bool disposing)
702 // Only if explicitly calling Close or dispose we still have access to
703 // managed resources.
704 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
705 if (connection != null)
707 connection.Dispose();
709 base.Dispose(disposing);
715 /// This method checks the connection state to see if the connection
716 /// is set or it is open. If one of this conditions is not met, throws
717 /// an InvalidOperationException
719 private void CheckConnectionState()
721 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CheckConnectionState");
724 // Check the connection state.
725 if (Connector == null || Connector.State != ConnectionState.Open)
727 throw new InvalidOperationException(resman.GetString("Exception_ConnectionNotOpen"));
732 /// This method substitutes the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see>, if exist, in the command
733 /// to their actual values.
734 /// The parameter name format is <b>:ParameterName</b>.
736 /// <returns>A version of <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> with the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see> inserted.</returns>
737 internal String GetCommandText()
739 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetCommandText");
741 if (planName == String.Empty)
742 return GetClearCommandText();
744 return GetPreparedCommandText();
748 private String GetClearCommandText()
750 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetClearCommandText");
752 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
754 Boolean functionReturnsRecord = false; // Functions don't return record by default.
756 String result = text;
758 if (type == CommandType.StoredProcedure)
761 functionReturnsRecord = CheckFunctionReturnRecord();
763 // 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.
764 if (!result.Trim().EndsWith(")"))
766 addProcedureParenthesis = true;
770 if (Connector.SupportsPrepare)
771 result = "select * from " + result; // This syntax is only available in 7.3+ as well SupportsPrepare.
773 result = "select " + result; //Only a single result return supported. 7.2 and earlier.
775 else if (type == CommandType.TableDirect)
776 return "select * from " + result; // There is no parameter support on table direct.
778 if (parameters == null || parameters.Count == 0)
780 if (addProcedureParenthesis)
783 if (functionReturnsRecord)
784 result = AddFunctionReturnsRecordSupport(result);
787 result = AddSingleRowBehaviorSupport(result);
795 for (Int32 i = 0; i < parameters.Count; i++)
797 NpgsqlParameter Param = parameters[i];
800 if ((Param.Direction == ParameterDirection.Input) ||
801 (Param.Direction == ParameterDirection.InputOutput))
804 // If parenthesis don't need to be added, they were added by user with parameter names. Replace them.
805 if (!addProcedureParenthesis)
807 // adding the '::<datatype>' on the end of a parameter is a highly
808 // questionable practice, but it is great for debugging!
809 // Removed as this was going in infinite loop when the parameter name had the same name of parameter
810 // type name. i.e.: parameter name called :text of type text. It would conflict with the parameter type name ::text.
811 result = ReplaceParameterValue(
814 Param.TypeInfo.ConvertToBackend(Param.Value, false)
817 result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + ",";
821 if (addProcedureParenthesis)
823 // Remove a trailing comma added from parameter handling above. If any.
824 // Maybe there are only output parameters.
825 if (result.EndsWith(","))
826 result = result.Remove(result.Length - 1, 1);
831 if (functionReturnsRecord)
832 result = AddFunctionReturnsRecordSupport(result);
834 return AddSingleRowBehaviorSupport(result);
839 private Boolean CheckFunctionReturnRecord()
842 if (Parameters.Count == 0)
845 String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = 'record' ) and proargtypes='{0}' and proname='{1}';";
847 StringBuilder parameterTypes = new StringBuilder("");
849 foreach(NpgsqlParameter p in Parameters)
851 if ((p.Direction == ParameterDirection.Input) ||
852 (p.Direction == ParameterDirection.InputOutput))
854 parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " ");
859 NpgsqlCommand c = new NpgsqlCommand(String.Format(returnRecordQuery, parameterTypes.ToString(), CommandText), Connection);
861 Boolean ret = (Boolean) c.ExecuteScalar();
863 // reset any responses just before getting new ones
864 connector.Mediator.ResetResponses();
871 private String AddFunctionReturnsRecordSupport(String OriginalResult)
874 StringBuilder sb = new StringBuilder(OriginalResult);
878 foreach(NpgsqlParameter p in Parameters)
880 if ((p.Direction == ParameterDirection.Output) ||
881 (p.Direction == ParameterDirection.InputOutput))
883 sb.Append(String.Format("{0} {1}, ", p.ParameterName.Substring(1), p.TypeInfo.Name));
887 String result = sb.ToString();
889 result = result.Remove(result.Length - 2, 1);
902 private String GetPreparedCommandText()
904 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPreparedCommandText");
906 if (parameters.Count == 0)
907 return "execute " + planName;
910 StringBuilder result = new StringBuilder("execute " + planName + '(');
913 for (Int32 i = 0; i < parameters.Count; i++)
915 result.Append(parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false) + ',');
918 result = result.Remove(result.Length - 1, 1);
921 return result.ToString();
927 private String GetParseCommandText()
929 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetParseCommandText");
931 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
933 String parseCommand = text;
935 if (type == CommandType.StoredProcedure)
937 // 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.
938 if (!parseCommand.Trim().EndsWith(")"))
940 addProcedureParenthesis = true;
944 parseCommand = "select * from " + parseCommand; // This syntax is only available in 7.3+ as well SupportsPrepare.
946 else if (type == CommandType.TableDirect)
947 return "select * from " + parseCommand; // There is no parameter support on TableDirect.
949 if (parameters.Count > 0)
951 // The ReplaceParameterValue below, also checks if the parameter is present.
953 String parameterName;
956 for (i = 0; i < parameters.Count; i++)
958 if ((parameters[i].Direction == ParameterDirection.Input) ||
959 (parameters[i].Direction == ParameterDirection.InputOutput))
962 if (!addProcedureParenthesis)
964 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
965 parameterName = parameters[i].ParameterName;
966 //textCommand = textCommand.Replace(':' + parameterName, "$" + (i+1));
967 parseCommand = ReplaceParameterValue(parseCommand, parameterName, "$" + (i+1) + "::" + parameters[i].TypeInfo.Name);
970 parseCommand += "$" + (i+1) + "::" + parameters[i].TypeInfo.Name;
976 if (addProcedureParenthesis)
977 return parseCommand + ")";
984 private String GetPrepareCommandText()
986 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPrepareCommandText");
988 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
990 planName = Connector.NextPlanName();
992 StringBuilder command = new StringBuilder("prepare " + planName);
994 String textCommand = text;
996 if (type == CommandType.StoredProcedure)
998 // 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.
999 if (!textCommand.Trim().EndsWith(")"))
1001 addProcedureParenthesis = true;
1005 textCommand = "select * from " + textCommand;
1007 else if (type == CommandType.TableDirect)
1008 return "select * from " + textCommand; // There is no parameter support on TableDirect.
1011 if (parameters.Count > 0)
1013 // The ReplaceParameterValue below, also checks if the parameter is present.
1015 String parameterName;
1018 for (i = 0; i < parameters.Count; i++)
1020 if ((parameters[i].Direction == ParameterDirection.Input) ||
1021 (parameters[i].Direction == ParameterDirection.InputOutput))
1024 if (!addProcedureParenthesis)
1026 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1027 parameterName = parameters[i].ParameterName;
1028 // The space in front of '$' fixes a parsing problem in 7.3 server
1029 // which gives errors of operator when finding the caracters '=$' in
1031 textCommand = ReplaceParameterValue(textCommand, parameterName, " $" + (i+1));
1034 textCommand += " $" + (i+1);
1039 //[TODO] Check if there is any missing parameters in the query.
1040 // For while, an error is thrown saying about the ':' char.
1042 command.Append('(');
1044 for (i = 0; i < parameters.Count; i++)
1046 // command.Append(NpgsqlTypesHelper.GetDefaultTypeInfo(parameters[i].DbType));
1047 command.Append(parameters[i].TypeInfo.Name);
1049 command.Append(',');
1052 command = command.Remove(command.Length - 1, 1);
1053 command.Append(')');
1057 if (addProcedureParenthesis)
1060 command.Append(" as ");
1061 command.Append(textCommand);
1064 return command.ToString();
1069 private String ReplaceParameterValue(String result, String parameterName, String paramVal)
1071 Int32 resLen = result.Length;
1072 Int32 paramStart = result.IndexOf(parameterName);
1073 Int32 paramLen = parameterName.Length;
1074 Int32 paramEnd = paramStart + paramLen;
1075 Boolean found = false;
1078 while(paramStart > -1)
1080 if((resLen > paramEnd) &&
1081 (result[paramEnd] == ' ' ||
1082 result[paramEnd] == ',' ||
1083 result[paramEnd] == ')' ||
1084 result[paramEnd] == ';' ||
1085 result[paramEnd] == '\n' ||
1086 result[paramEnd] == '\r' ||
1087 result[paramEnd] == '\t'))
1089 result = result.Substring(0, paramStart) + paramVal + result.Substring(paramEnd);
1092 else if(resLen == paramEnd)
1094 result = result.Substring(0, paramStart)+ paramVal;
1099 resLen = result.Length;
1100 paramStart = result.IndexOf(parameterName, paramStart);
1101 paramEnd = paramStart + paramLen;
1105 throw new IndexOutOfRangeException (String.Format(resman.GetString("Exception_ParamNotInQuery"), parameterName));
1109 }//ReplaceParameterValue
1112 private String AddSingleRowBehaviorSupport(String ResultCommandText)
1115 ResultCommandText = ResultCommandText.Trim();
1117 if ((commandBehavior & CommandBehavior.SingleRow) > 0)
1119 if (ResultCommandText.EndsWith(";"))
1120 ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1121 ResultCommandText += " limit 1;";
1127 return ResultCommandText;
1132 private void ExecuteCommand()
1134 // Check the connection state first.
1135 CheckConnectionState();
1137 // reset any responses just before getting new ones
1138 connector.Mediator.ResetResponses();
1143 Connector.Query(this);
1145 // Check for errors and/or notifications and do the Right Thing.
1146 connector.CheckErrorsAndNotifications();
1155 connector.Execute(new NpgsqlExecute(bind.PortalName, 0));
1157 // Check for errors and/or notifications and do the Right Thing.
1158 connector.CheckErrorsAndNotifications();
1162 // As per documentation:
1163 // "[...] When an error is detected while processing any extended-query message,
1164 // the backend issues ErrorResponse, then reads and discards messages until a
1165 // Sync is reached, then issues ReadyForQuery and returns to normal message processing.[...]"
1166 // So, send a sync command if we get any problems.