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));
56 private NpgsqlConnection connection;
57 private NpgsqlConnector connector;
58 private NpgsqlTransaction transaction;
60 private Int32 timeout;
61 private CommandType type;
62 private NpgsqlParameterCollection parameters;
63 private String planName;
65 private NpgsqlParse parse;
66 private NpgsqlBind bind;
68 private Boolean invalidTransactionDetected = false;
70 private CommandBehavior commandBehavior;
75 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class.
77 public NpgsqlCommand() : this(String.Empty, null, null)
80 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query.
82 /// <param name="cmdText">The text of the query.</param>
83 public NpgsqlCommand(String cmdText) : this(cmdText, null, null)
86 /// 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>.
88 /// <param name="cmdText">The text of the query.</param>
89 /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
90 public NpgsqlCommand(String cmdText, NpgsqlConnection connection) : this(cmdText, connection, null)
93 /// 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>.
95 /// <param name="cmdText">The text of the query.</param>
96 /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
97 /// <param name="transaction">The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see> in which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.</param>
98 public NpgsqlCommand(String cmdText, NpgsqlConnection connection, NpgsqlTransaction transaction)
100 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
102 planName = String.Empty;
104 this.connection = connection;
106 if (this.connection != null)
107 this.connector = connection.Connector;
109 parameters = new NpgsqlParameterCollection();
111 type = CommandType.Text;
112 this.Transaction = transaction;
113 commandBehavior = CommandBehavior.Default;
119 /// Used to execute internal commands.
121 internal NpgsqlCommand(String cmdText, NpgsqlConnector connector)
123 resman = new System.Resources.ResourceManager(this.GetType());
124 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
127 planName = String.Empty;
129 this.connector = connector;
130 type = CommandType.Text;
131 commandBehavior = CommandBehavior.Default;
133 parameters = new NpgsqlParameterCollection();
137 // Public properties.
139 /// Gets or sets the SQL statement or function (stored procedure) to execute at the data source.
141 /// <value>The Transact-SQL statement or stored procedure to execute. The default is an empty string.</value>
142 [Category("Data"), DefaultValue("")]
143 public String CommandText {
151 // [TODO] Validate commandtext.
152 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandText", value);
154 planName = String.Empty;
157 commandBehavior = CommandBehavior.Default;
162 /// Gets or sets the wait time before terminating the attempt
163 /// to execute a command and generating an error.
165 /// <value>The time (in seconds) to wait for the command to execute.
166 /// The default is 20 seconds.</value>
168 public Int32 CommandTimeout {
177 throw new ArgumentOutOfRangeException(resman.GetString("Exception_CommandTimeoutLessZero"));
180 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandTimeout", value);
185 /// Gets or sets a value indicating how the
186 /// <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> property is to be interpreted.
188 /// <value>One of the <see cref="System.Data.CommandType">CommandType</see> values. The default is <see cref="System.Data.CommandType">CommandType.Text</see>.</value>
189 [Category("Data"), DefaultValue(CommandType.Text)]
190 public CommandType CommandType {
199 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandType", value);
203 IDbConnection IDbCommand.Connection
212 Connection = (NpgsqlConnection) value;
213 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Connection", value);
218 /// Gets or sets the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>
219 /// used by this instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
221 /// <value>The connection to a data source. The default value is a null reference.</value>
222 [Category("Behavior"), DefaultValue(null)]
223 public NpgsqlConnection Connection {
226 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Connection");
232 if (this.Connection == value)
235 //if (this.transaction != null && this.transaction.Connection == null)
236 // this.transaction = null;
238 if (this.transaction != null && this.connection != null && this.Connector.Transaction != null)
239 throw new InvalidOperationException(resman.GetString("Exception_SetConnectionInTransaction"));
242 this.connection = value;
244 if (this.connection != null)
245 connector = this.connection.Connector;
247 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Connection", value);
251 internal NpgsqlConnector Connector {
254 if (this.connection != null)
255 connector = this.connection.Connector;
261 IDataParameterCollection IDbCommand.Parameters {
269 /// Gets the <see cref="Npgsql.NpgsqlParameterCollection">NpgsqlParameterCollection</see>.
271 /// <value>The parameters of the SQL statement or function (stored procedure). The default is an empty collection.</value>
273 [Category("Data"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
276 public NpgsqlParameterCollection Parameters {
279 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Parameters");
285 IDbTransaction IDbCommand.Transaction
294 Transaction = (NpgsqlTransaction) value;
295 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Transaction", value);
300 /// Gets or sets the <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
301 /// within which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.
303 /// <value>The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>.
304 /// The default value is a null reference.</value>
306 [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
309 public NpgsqlTransaction Transaction {
312 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Transaction");
314 if (this.transaction != null && this.transaction.Connection == null)
316 this.transaction = null;
318 return this.transaction;
323 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Transaction" ,value);
325 this.transaction = (NpgsqlTransaction) value;
330 /// Gets or sets how command results are applied to the <see cref="System.Data.DataRow">DataRow</see>
331 /// when used by the <see cref="System.Data.Common.DbDataAdapter.Update">Update</see>
332 /// method of the <see cref="System.Data.Common.DbDataAdapter">DbDataAdapter</see>.
334 /// <value>One of the <see cref="System.Data.UpdateRowSource">UpdateRowSource</see> values.</value>
336 [Category("Behavior"), DefaultValue(UpdateRowSource.Both)]
339 public UpdateRowSource UpdatedRowSource {
343 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "UpdatedRowSource");
345 return UpdateRowSource.Both;
350 throw new NotImplementedException();
355 /// Attempts to cancel the execution of a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
357 /// <remarks>This Method isn't implemented yet.</remarks>
360 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Cancel");
362 // [TODO] Finish method implementation.
363 throw new NotImplementedException();
367 /// Create a new command based on this one.
369 /// <returns>A new NpgsqlCommand object.</returns>
370 Object ICloneable.Clone()
376 /// Create a new connection based on this one.
378 /// <returns>A new NpgsqlConnection object.</returns>
379 public NpgsqlCommand Clone()
381 // TODO: Add consistency checks.
383 return new NpgsqlCommand(CommandText, Connection, Transaction);
387 /// Creates a new instance of an <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.
389 /// <returns>An <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.</returns>
390 IDbDataParameter IDbCommand.CreateParameter()
392 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.CreateParameter");
394 return (NpgsqlParameter) CreateParameter();
398 /// Creates a new instance of a <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.
400 /// <returns>A <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.</returns>
401 public NpgsqlParameter CreateParameter()
403 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateParameter");
405 return new NpgsqlParameter();
409 /// Executes a SQL statement against the connection and returns the number of rows affected.
411 /// <returns>The number of rows affected.</returns>
412 public Int32 ExecuteNonQuery()
414 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteNonQuery");
418 UpdateOutputParameters();
421 // If nothing is returned, just return -1.
422 if(Connector.Mediator.CompletedResponses.Count == 0)
427 // Check if the response is available.
428 String firstCompletedResponse = (String)Connector.Mediator.CompletedResponses[0];
430 if (firstCompletedResponse == null)
433 String[] ret_string_tokens = firstCompletedResponse.Split(null); // whitespace separator.
436 // Check if the command was insert, delete or update.
437 // Only theses commands return rows affected.
438 // [FIXME] Is there a better way to check this??
439 if ((String.Compare(ret_string_tokens[0], "INSERT", true) == 0) ||
440 (String.Compare(ret_string_tokens[0], "UPDATE", true) == 0) ||
441 (String.Compare(ret_string_tokens[0], "DELETE", true) == 0))
443 // The number of rows affected is in the third token for insert queries
444 // and in the second token for update and delete queries.
445 // In other words, it is the last token in the 0-based array.
447 return Int32.Parse(ret_string_tokens[ret_string_tokens.Length - 1]);
454 private void UpdateOutputParameters()
456 // Check if there was some resultset returned. If so, put the result in output parameters.
457 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "UpdateOutputParameters");
460 ArrayList resultSets = Connector.Mediator.ResultSets;
462 if (resultSets.Count != 0)
464 NpgsqlResultSet nrs = (NpgsqlResultSet)resultSets[0];
466 if ((nrs != null) && (nrs.Count > 0))
468 NpgsqlAsciiRow nar = (NpgsqlAsciiRow)nrs[0];
471 Boolean hasMapping = false;
473 // First check if there is any mapping between parameter name and resultset name.
474 // If so, just update output parameters which has mapping.
476 foreach (NpgsqlParameter p in Parameters)
480 if (nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1)) > -1)
486 catch(ArgumentOutOfRangeException)
493 foreach (NpgsqlParameter p in Parameters)
495 if (((p.Direction == ParameterDirection.Output) ||
496 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
500 p.Value = nar[nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1))];
503 catch(ArgumentOutOfRangeException)
510 foreach (NpgsqlParameter p in Parameters)
512 if (((p.Direction == ParameterDirection.Output) ||
513 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
527 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
528 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
529 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
531 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
532 IDataReader IDbCommand.ExecuteReader()
534 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader");
536 return (NpgsqlDataReader) ExecuteReader();
540 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
541 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
542 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
543 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
545 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
546 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
547 IDataReader IDbCommand.ExecuteReader(CommandBehavior cb)
549 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader", cb);
551 return (NpgsqlDataReader) ExecuteReader(cb);
555 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
556 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
557 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
559 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
560 public NpgsqlDataReader ExecuteReader()
562 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader");
564 return ExecuteReader(CommandBehavior.Default);
568 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
569 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
570 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
571 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
573 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
574 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
575 /// <remarks>Currently the CommandBehavior parameter is ignored.</remarks>
576 public NpgsqlDataReader ExecuteReader(CommandBehavior cb)
578 // [FIXME] No command behavior handling.
580 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader", cb);
581 commandBehavior = cb;
585 UpdateOutputParameters();
587 // Get the resultsets and create a Datareader with them.
588 return new NpgsqlDataReader(Connector.Mediator.ResultSets, Connector.Mediator.CompletedResponses, cb, this);
592 /// This method binds the parameters from parameters collection to the bind
595 private void BindParameters()
598 if (parameters.Count != 0)
600 Object[] parameterValues = new Object[parameters.Count];
601 for (Int32 i = 0; i < parameters.Count; i++)
603 // Do not quote strings, or escape existing quotes - this will be handled by the backend.
604 // DBNull or null values are returned as null.
605 // TODO: Would it be better to remove this null special handling out of ConvertToBackend??
606 parameterValues[i] = parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, true);
608 bind.ParameterValues = parameterValues;
611 Connector.Bind(bind);
612 Connector.Mediator.RequireReadyForQuery = false;
615 connector.CheckErrorsAndNotifications();
619 /// Executes the query, and returns the first column of the first row
620 /// in the result set returned by the query. Extra columns or rows are ignored.
622 /// <returns>The first column of the first row in the result set,
623 /// or a null reference if the result set is empty.</returns>
624 public Object ExecuteScalar()
626 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteScalar");
628 /*if ((type == CommandType.Text) || (type == CommandType.StoredProcedure))
630 connection.Query(this);
634 connection.Execute(new NpgsqlExecute(bind.PortalName, 0));
637 throw new NotImplementedException(resman.GetString("Exception_CommandTypeTableDirect"));
642 // Now get the results.
643 // Only the first column of the first row must be returned.
646 ArrayList resultSets = Connector.Mediator.ResultSets;
648 // First data is the RowDescription object.
649 // Check all resultsets as insert commands could have been sent along
650 // with resultset queries. The insert commands return null and and some queries
651 // may return empty resultsets, so, if we find one of these, skip to next resultset.
652 // If no resultset is found, return null as per specification.
654 NpgsqlAsciiRow ascii_row = null;
655 foreach( NpgsqlResultSet nrs in resultSets )
657 if( (nrs != null) && (nrs.Count > 0) )
659 ascii_row = (NpgsqlAsciiRow) nrs[0];
668 /// Creates a prepared version of the command on a PostgreSQL server.
670 public void Prepare()
672 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Prepare");
674 // Check the connection state.
675 CheckConnectionState();
677 if (! Connector.SupportsPrepare)
679 return; // Do nothing.
682 if (connector.BackendProtocolVersion == ProtocolVersion.Version2)
684 NpgsqlCommand command = new NpgsqlCommand(GetPrepareCommandText(), connector );
685 command.ExecuteNonQuery();
689 // Use the extended query parsing...
690 //planName = "NpgsqlPlan" + Connector.NextPlanIndex();
691 planName = Connector.NextPlanName();
692 String portalName = Connector.NextPortalName();
694 parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] {});
696 Connector.Parse(parse);
697 Connector.Mediator.RequireReadyForQuery = false;
700 // Check for errors and/or notifications and do the Right Thing.
701 connector.CheckErrorsAndNotifications();
703 bind = new NpgsqlBind("", planName, new Int16[] {0}, null, new Int16[] {0});
709 /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
711 protected override void Dispose (bool disposing)
716 // Only if explicitly calling Close or dispose we still have access to
717 // managed resources.
718 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
719 if (connection != null)
721 connection.Dispose();
723 base.Dispose(disposing);
729 /// This method checks the connection state to see if the connection
730 /// is set or it is open. If one of this conditions is not met, throws
731 /// an InvalidOperationException
733 private void CheckConnectionState()
735 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CheckConnectionState");
738 // Check the connection state.
739 if (Connector == null || Connector.State != ConnectionState.Open)
741 throw new InvalidOperationException(resman.GetString("Exception_ConnectionNotOpen"));
746 /// This method substitutes the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see>, if exist, in the command
747 /// to their actual values.
748 /// The parameter name format is <b>:ParameterName</b>.
750 /// <returns>A version of <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> with the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see> inserted.</returns>
751 internal String GetCommandText()
753 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetCommandText");
755 if (planName == String.Empty)
756 return GetClearCommandText();
758 return GetPreparedCommandText();
762 private String GetClearCommandText()
764 if (NpgsqlEventLog.Level == LogLevel.Debug)
765 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetClearCommandText");
767 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
769 Boolean functionReturnsRecord = false; // Functions don't return record by default.
771 Boolean functionReturnsRefcursor = false; // Functions don't return refcursor by default.
773 String result = text;
775 if (type == CommandType.StoredProcedure)
778 if (Parameters.Count > 0)
779 functionReturnsRecord = CheckFunctionReturn("record");
781 functionReturnsRefcursor = CheckFunctionReturn("refcursor");
783 // 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.
784 if ((!result.Trim().EndsWith(")")) && (!result.Trim().EndsWith(";")))
786 addProcedureParenthesis = true;
790 if (Connector.SupportsPrepare)
791 result = "select * from " + result; // This syntax is only available in 7.3+ as well SupportsPrepare.
793 result = "select " + result; //Only a single result return supported. 7.2 and earlier.
795 else if (type == CommandType.TableDirect)
796 return "select * from " + result; // There is no parameter support on table direct.
798 if (parameters == null || parameters.Count == 0)
800 if (addProcedureParenthesis)
804 // If function returns ref cursor just process refcursor-result function call
805 // and return command which will be used to return data from refcursor.
807 if (functionReturnsRefcursor)
808 return ProcessRefcursorFunctionReturn(result);
811 if (functionReturnsRecord)
812 result = AddFunctionReturnsRecordSupport(result);
815 result = AddSingleRowBehaviorSupport(result);
817 result = AddSchemaOnlyBehaviorSupport(result);
823 // Get parameters in query string to translate them to their actual values.
825 // This regular expression gets all the parameters in format :param or @param
826 // and everythingelse.
827 // This is only needed if query string has parameters. Else, just append the
828 // parameter values in order they were put in parameter collection.
831 // If parenthesis don't need to be added, they were added by user with parameter names. Replace them.
832 if (!addProcedureParenthesis)
835 Regex a = new Regex(@"(:[\w]*)|(@[\w]*)|(.)", RegexOptions.Singleline);
839 StringBuilder sb = new StringBuilder();
841 for ( Match m = a.Match(result); m.Success; m = m.NextMatch() )
843 String s = m.Groups[0].ToString();
845 if ((s.StartsWith(":") ||
846 s.StartsWith("@")) &&
847 Parameters.Contains(s))
849 // It's a parameter. Lets handle it.
851 NpgsqlParameter p = Parameters[s];
852 if ((p.Direction == ParameterDirection.Input) ||
853 (p.Direction == ParameterDirection.InputOutput))
857 // adding the '::<datatype>' on the end of a parameter is a highly
858 // questionable practice, but it is great for debugging!
859 sb.Append(p.TypeInfo.ConvertToBackend(p.Value, false));
861 // Only add data type info if we are calling an stored procedure.
863 if (type == CommandType.StoredProcedure)
866 sb.Append(p.TypeInfo.Name);
868 if (p.TypeInfo.UseSize && (p.Size > 0))
869 sb.Append("(").Append(p.Size).Append(")");
879 result = sb.ToString();
886 for (Int32 i = 0; i < parameters.Count; i++)
888 NpgsqlParameter Param = parameters[i];
891 if ((Param.Direction == ParameterDirection.Input) ||
892 (Param.Direction == ParameterDirection.InputOutput))
895 result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + "::" + Param.TypeInfo.Name + ",";
899 // Remove a trailing comma added from parameter handling above. If any.
900 // Maybe there are only output parameters. If so, there will be no comma.
901 if (result.EndsWith(","))
902 result = result.Remove(result.Length - 1, 1);
907 if (functionReturnsRecord)
908 result = AddFunctionReturnsRecordSupport(result);
910 // If function returns ref cursor just process refcursor-result function call
911 // and return command which will be used to return data from refcursor.
913 if (functionReturnsRefcursor)
914 return ProcessRefcursorFunctionReturn(result);
917 result = AddSingleRowBehaviorSupport(result);
919 result = AddSchemaOnlyBehaviorSupport(result);
926 private Boolean CheckFunctionReturn(String ReturnType)
929 String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = :typename ) and proargtypes=:proargtypes and proname=:proname;";
931 StringBuilder parameterTypes = new StringBuilder("");
933 foreach(NpgsqlParameter p in Parameters)
935 if ((p.Direction == ParameterDirection.Input) ||
936 (p.Direction == ParameterDirection.InputOutput))
938 parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " ");
943 NpgsqlCommand c = new NpgsqlCommand(returnRecordQuery, Connection);
945 c.Parameters.Add(new NpgsqlParameter("typename", NpgsqlDbType.Text));
946 c.Parameters.Add(new NpgsqlParameter("proargtypes", NpgsqlDbType.Text));
947 c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text));
949 c.Parameters[0].Value = ReturnType;
950 c.Parameters[1].Value = parameterTypes.ToString();
951 c.Parameters[2].Value = CommandText;
954 Boolean ret = (Boolean) c.ExecuteScalar();
956 // reset any responses just before getting new ones
957 connector.Mediator.ResetResponses();
964 private String AddFunctionReturnsRecordSupport(String OriginalResult)
967 StringBuilder sb = new StringBuilder(OriginalResult);
971 foreach(NpgsqlParameter p in Parameters)
973 if ((p.Direction == ParameterDirection.Output) ||
974 (p.Direction == ParameterDirection.InputOutput))
976 sb.Append(String.Format("{0} {1}, ", p.ParameterName.Substring(1), p.TypeInfo.Name));
980 String result = sb.ToString();
982 result = result.Remove(result.Length - 2, 1);
994 /// This methods takes a string with a function call witch returns a refcursor or a set of
995 /// refcursor. It will return the names of the open cursors/portals which will hold
996 /// results. In turn, it returns the string which is needed to get the data of this cursors
997 /// in form of one resultset for each cursor open. This way, clients don't need to do anything
998 /// else besides calling function normally to get results in this way.
1001 private String ProcessRefcursorFunctionReturn(String FunctionCall)
1003 NpgsqlCommand c = new NpgsqlCommand(FunctionCall, Connection);
1005 NpgsqlDataReader dr = c.ExecuteReader();
1007 StringBuilder sb = new StringBuilder();
1011 sb.Append("fetch all from \"").Append(dr.GetString(0)).Append("\";");
1015 sb.Append(";"); // Just in case there is no response from refcursor function return.
1017 // reset any responses just before getting new ones
1018 connector.Mediator.ResetResponses();
1020 return sb.ToString();
1027 private String GetPreparedCommandText()
1029 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPreparedCommandText");
1031 if (parameters.Count == 0)
1032 return "execute " + planName;
1035 StringBuilder result = new StringBuilder("execute " + planName + '(');
1038 for (Int32 i = 0; i < parameters.Count; i++)
1040 result.Append(parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false) + ',');
1043 result = result.Remove(result.Length - 1, 1);
1046 return result.ToString();
1052 private String GetParseCommandText()
1054 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetParseCommandText");
1056 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1058 String parseCommand = text;
1060 if (type == CommandType.StoredProcedure)
1062 // 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.
1063 if (!parseCommand.Trim().EndsWith(")"))
1065 addProcedureParenthesis = true;
1066 parseCommand += "(";
1069 parseCommand = "select * from " + parseCommand; // This syntax is only available in 7.3+ as well SupportsPrepare.
1071 else if (type == CommandType.TableDirect)
1072 return "select * from " + parseCommand; // There is no parameter support on TableDirect.
1074 if (parameters.Count > 0)
1076 // The ReplaceParameterValue below, also checks if the parameter is present.
1078 String parameterName;
1081 for (i = 0; i < parameters.Count; i++)
1083 if ((parameters[i].Direction == ParameterDirection.Input) ||
1084 (parameters[i].Direction == ParameterDirection.InputOutput))
1087 if (!addProcedureParenthesis)
1089 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1090 parameterName = parameters[i].ParameterName;
1091 //textCommand = textCommand.Replace(':' + parameterName, "$" + (i+1));
1092 parseCommand = ReplaceParameterValue(parseCommand, parameterName, "$" + (i+1) + "::" + parameters[i].TypeInfo.Name);
1095 parseCommand += "$" + (i+1) + "::" + parameters[i].TypeInfo.Name;
1101 if (addProcedureParenthesis)
1102 return parseCommand + ")";
1104 return parseCommand;
1109 private String GetPrepareCommandText()
1111 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPrepareCommandText");
1113 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1115 planName = Connector.NextPlanName();
1117 StringBuilder command = new StringBuilder("prepare " + planName);
1119 String textCommand = text;
1121 if (type == CommandType.StoredProcedure)
1123 // 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.
1124 if (!textCommand.Trim().EndsWith(")"))
1126 addProcedureParenthesis = true;
1130 textCommand = "select * from " + textCommand;
1132 else if (type == CommandType.TableDirect)
1133 return "select * from " + textCommand; // There is no parameter support on TableDirect.
1136 if (parameters.Count > 0)
1138 // The ReplaceParameterValue below, also checks if the parameter is present.
1140 String parameterName;
1143 for (i = 0; i < parameters.Count; i++)
1145 if ((parameters[i].Direction == ParameterDirection.Input) ||
1146 (parameters[i].Direction == ParameterDirection.InputOutput))
1149 if (!addProcedureParenthesis)
1151 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1152 parameterName = parameters[i].ParameterName;
1153 // The space in front of '$' fixes a parsing problem in 7.3 server
1154 // which gives errors of operator when finding the caracters '=$' in
1156 textCommand = ReplaceParameterValue(textCommand, parameterName, " $" + (i+1));
1159 textCommand += " $" + (i+1);
1164 //[TODO] Check if there is any missing parameters in the query.
1165 // For while, an error is thrown saying about the ':' char.
1167 command.Append('(');
1169 for (i = 0; i < parameters.Count; i++)
1171 // command.Append(NpgsqlTypesHelper.GetDefaultTypeInfo(parameters[i].DbType));
1172 command.Append(parameters[i].TypeInfo.Name);
1174 command.Append(',');
1177 command = command.Remove(command.Length - 1, 1);
1178 command.Append(')');
1182 if (addProcedureParenthesis)
1185 command.Append(" as ");
1186 command.Append(textCommand);
1189 return command.ToString();
1194 private String ReplaceParameterValue(String result, String parameterName, String paramVal)
1196 Int32 resLen = result.Length;
1197 Int32 paramStart = result.IndexOf(parameterName);
1198 Int32 paramLen = parameterName.Length;
1199 Int32 paramEnd = paramStart + paramLen;
1200 Boolean found = false;
1203 while(paramStart > -1)
1205 if((resLen > paramEnd) && !Char.IsLetterOrDigit(result, paramEnd))
1207 result = result.Substring(0, paramStart) + paramVal + result.Substring(paramEnd);
1210 else if(resLen == paramEnd)
1212 result = result.Substring(0, paramStart)+ paramVal;
1217 resLen = result.Length;
1218 paramStart = result.IndexOf(parameterName, paramStart);
1219 paramEnd = paramStart + paramLen;
1223 throw new IndexOutOfRangeException (String.Format(resman.GetString("Exception_ParamNotInQuery"), parameterName));
1227 }//ReplaceParameterValue
1230 private String AddSingleRowBehaviorSupport(String ResultCommandText)
1233 ResultCommandText = ResultCommandText.Trim();
1235 // Do not add SingleRowBehavior if SchemaOnly behavior is set.
1237 if ((commandBehavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly)
1238 return ResultCommandText;
1240 if ((commandBehavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow)
1242 if (ResultCommandText.EndsWith(";"))
1243 ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1244 ResultCommandText += " limit 1;";
1250 return ResultCommandText;
1254 private String AddSchemaOnlyBehaviorSupport(String ResultCommandText)
1257 ResultCommandText = ResultCommandText.Trim();
1259 if ((commandBehavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly)
1261 if (ResultCommandText.EndsWith(";"))
1262 ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1263 ResultCommandText += " limit 0;";
1268 return ResultCommandText;
1273 private void ExecuteCommand()
1275 // Check the connection state first.
1276 CheckConnectionState();
1278 // reset any responses just before getting new ones
1279 Connector.Mediator.ResetResponses();
1284 Connector.Query(this);
1286 // Check for errors and/or notifications and do the Right Thing.
1287 connector.CheckErrorsAndNotifications();
1296 connector.Execute(new NpgsqlExecute(bind.PortalName, 0));
1298 // Check for errors and/or notifications and do the Right Thing.
1299 connector.CheckErrorsAndNotifications();
1303 // As per documentation:
1304 // "[...] When an error is detected while processing any extended-query message,
1305 // the backend issues ErrorResponse, then reads and discards messages until a
1306 // Sync is reached, then issues ReadyForQuery and returns to normal message processing.[...]"
1307 // So, send a sync command if we get any problems.