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 (connector == null && 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);
821 // Get parameters in query string to translate them to their actual values.
823 // This regular expression gets all the parameters in format :param or @param
824 // and everythingelse.
825 // This is only needed if query string has parameters. Else, just append the
826 // parameter values in order they were put in parameter collection.
829 // If parenthesis don't need to be added, they were added by user with parameter names. Replace them.
830 if (!addProcedureParenthesis)
833 Regex a = new Regex(@"(:[\w]*)|(@[\w]*)|(.)", RegexOptions.Singleline);
837 StringBuilder sb = new StringBuilder();
839 for ( Match m = a.Match(result); m.Success; m = m.NextMatch() )
841 String s = m.Groups[0].ToString();
843 if ((s.StartsWith(":") ||
844 s.StartsWith("@")) &&
845 Parameters.Contains(s))
847 // It's a parameter. Lets handle it.
849 NpgsqlParameter p = Parameters[s];
850 if ((p.Direction == ParameterDirection.Input) ||
851 (p.Direction == ParameterDirection.InputOutput))
855 // adding the '::<datatype>' on the end of a parameter is a highly
856 // questionable practice, but it is great for debugging!
857 sb.Append(p.TypeInfo.ConvertToBackend(p.Value, false));
859 // Only add data type info if we are calling an stored procedure.
861 if (type == CommandType.StoredProcedure)
864 sb.Append(p.TypeInfo.Name);
866 if (p.TypeInfo.UseSize && (p.Size > 0))
867 sb.Append("(").Append(p.Size).Append(")");
877 result = sb.ToString();
884 for (Int32 i = 0; i < parameters.Count; i++)
886 NpgsqlParameter Param = parameters[i];
889 if ((Param.Direction == ParameterDirection.Input) ||
890 (Param.Direction == ParameterDirection.InputOutput))
893 result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + "::" + Param.TypeInfo.Name + ",";
897 // Remove a trailing comma added from parameter handling above. If any.
898 // Maybe there are only output parameters. If so, there will be no comma.
899 if (result.EndsWith(","))
900 result = result.Remove(result.Length - 1, 1);
905 if (functionReturnsRecord)
906 result = AddFunctionReturnsRecordSupport(result);
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 return AddSingleRowBehaviorSupport(result);
920 private Boolean CheckFunctionReturn(String ReturnType)
923 String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = :typename ) and proargtypes=:proargtypes and proname=:proname;";
925 StringBuilder parameterTypes = new StringBuilder("");
927 foreach(NpgsqlParameter p in Parameters)
929 if ((p.Direction == ParameterDirection.Input) ||
930 (p.Direction == ParameterDirection.InputOutput))
932 parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " ");
937 NpgsqlCommand c = new NpgsqlCommand(returnRecordQuery, Connection);
939 c.Parameters.Add(new NpgsqlParameter("typename", NpgsqlDbType.Text));
940 c.Parameters.Add(new NpgsqlParameter("proargtypes", NpgsqlDbType.Text));
941 c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text));
943 c.Parameters[0].Value = ReturnType;
944 c.Parameters[1].Value = parameterTypes.ToString();
945 c.Parameters[2].Value = CommandText;
948 Boolean ret = (Boolean) c.ExecuteScalar();
950 // reset any responses just before getting new ones
951 connector.Mediator.ResetResponses();
958 private String AddFunctionReturnsRecordSupport(String OriginalResult)
961 StringBuilder sb = new StringBuilder(OriginalResult);
965 foreach(NpgsqlParameter p in Parameters)
967 if ((p.Direction == ParameterDirection.Output) ||
968 (p.Direction == ParameterDirection.InputOutput))
970 sb.Append(String.Format("{0} {1}, ", p.ParameterName.Substring(1), p.TypeInfo.Name));
974 String result = sb.ToString();
976 result = result.Remove(result.Length - 2, 1);
988 /// This methods takes a string with a function call witch returns a refcursor or a set of
989 /// refcursor. It will return the names of the open cursors/portals which will hold
990 /// results. In turn, it returns the string which is needed to get the data of this cursors
991 /// in form of one resultset for each cursor open. This way, clients don't need to do anything
992 /// else besides calling function normally to get results in this way.
995 private String ProcessRefcursorFunctionReturn(String FunctionCall)
997 NpgsqlCommand c = new NpgsqlCommand(FunctionCall, Connection);
999 NpgsqlDataReader dr = c.ExecuteReader();
1001 StringBuilder sb = new StringBuilder();
1005 sb.Append("fetch all from \"").Append(dr.GetString(0)).Append("\";");
1009 sb.Append(";"); // Just in case there is no response from refcursor function return.
1011 // reset any responses just before getting new ones
1012 connector.Mediator.ResetResponses();
1014 return sb.ToString();
1021 private String GetPreparedCommandText()
1023 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPreparedCommandText");
1025 if (parameters.Count == 0)
1026 return "execute " + planName;
1029 StringBuilder result = new StringBuilder("execute " + planName + '(');
1032 for (Int32 i = 0; i < parameters.Count; i++)
1034 result.Append(parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false) + ',');
1037 result = result.Remove(result.Length - 1, 1);
1040 return result.ToString();
1046 private String GetParseCommandText()
1048 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetParseCommandText");
1050 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1052 String parseCommand = text;
1054 if (type == CommandType.StoredProcedure)
1056 // 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.
1057 if (!parseCommand.Trim().EndsWith(")"))
1059 addProcedureParenthesis = true;
1060 parseCommand += "(";
1063 parseCommand = "select * from " + parseCommand; // This syntax is only available in 7.3+ as well SupportsPrepare.
1065 else if (type == CommandType.TableDirect)
1066 return "select * from " + parseCommand; // There is no parameter support on TableDirect.
1068 if (parameters.Count > 0)
1070 // The ReplaceParameterValue below, also checks if the parameter is present.
1072 String parameterName;
1075 for (i = 0; i < parameters.Count; i++)
1077 if ((parameters[i].Direction == ParameterDirection.Input) ||
1078 (parameters[i].Direction == ParameterDirection.InputOutput))
1081 if (!addProcedureParenthesis)
1083 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1084 parameterName = parameters[i].ParameterName;
1085 //textCommand = textCommand.Replace(':' + parameterName, "$" + (i+1));
1086 parseCommand = ReplaceParameterValue(parseCommand, parameterName, "$" + (i+1) + "::" + parameters[i].TypeInfo.Name);
1089 parseCommand += "$" + (i+1) + "::" + parameters[i].TypeInfo.Name;
1095 if (addProcedureParenthesis)
1096 return parseCommand + ")";
1098 return parseCommand;
1103 private String GetPrepareCommandText()
1105 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPrepareCommandText");
1107 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1109 planName = Connector.NextPlanName();
1111 StringBuilder command = new StringBuilder("prepare " + planName);
1113 String textCommand = text;
1115 if (type == CommandType.StoredProcedure)
1117 // 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.
1118 if (!textCommand.Trim().EndsWith(")"))
1120 addProcedureParenthesis = true;
1124 textCommand = "select * from " + textCommand;
1126 else if (type == CommandType.TableDirect)
1127 return "select * from " + textCommand; // There is no parameter support on TableDirect.
1130 if (parameters.Count > 0)
1132 // The ReplaceParameterValue below, also checks if the parameter is present.
1134 String parameterName;
1137 for (i = 0; i < parameters.Count; i++)
1139 if ((parameters[i].Direction == ParameterDirection.Input) ||
1140 (parameters[i].Direction == ParameterDirection.InputOutput))
1143 if (!addProcedureParenthesis)
1145 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1146 parameterName = parameters[i].ParameterName;
1147 // The space in front of '$' fixes a parsing problem in 7.3 server
1148 // which gives errors of operator when finding the caracters '=$' in
1150 textCommand = ReplaceParameterValue(textCommand, parameterName, " $" + (i+1));
1153 textCommand += " $" + (i+1);
1158 //[TODO] Check if there is any missing parameters in the query.
1159 // For while, an error is thrown saying about the ':' char.
1161 command.Append('(');
1163 for (i = 0; i < parameters.Count; i++)
1165 // command.Append(NpgsqlTypesHelper.GetDefaultTypeInfo(parameters[i].DbType));
1166 command.Append(parameters[i].TypeInfo.Name);
1168 command.Append(',');
1171 command = command.Remove(command.Length - 1, 1);
1172 command.Append(')');
1176 if (addProcedureParenthesis)
1179 command.Append(" as ");
1180 command.Append(textCommand);
1183 return command.ToString();
1188 private String ReplaceParameterValue(String result, String parameterName, String paramVal)
1190 Int32 resLen = result.Length;
1191 Int32 paramStart = result.IndexOf(parameterName);
1192 Int32 paramLen = parameterName.Length;
1193 Int32 paramEnd = paramStart + paramLen;
1194 Boolean found = false;
1197 while(paramStart > -1)
1199 if((resLen > paramEnd) && !Char.IsLetterOrDigit(result, paramEnd))
1201 result = result.Substring(0, paramStart) + paramVal + result.Substring(paramEnd);
1204 else if(resLen == paramEnd)
1206 result = result.Substring(0, paramStart)+ paramVal;
1211 resLen = result.Length;
1212 paramStart = result.IndexOf(parameterName, paramStart);
1213 paramEnd = paramStart + paramLen;
1217 throw new IndexOutOfRangeException (String.Format(resman.GetString("Exception_ParamNotInQuery"), parameterName));
1221 }//ReplaceParameterValue
1224 private String AddSingleRowBehaviorSupport(String ResultCommandText)
1227 ResultCommandText = ResultCommandText.Trim();
1229 if ((commandBehavior & CommandBehavior.SingleRow) > 0)
1231 if (ResultCommandText.EndsWith(";"))
1232 ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1233 ResultCommandText += " limit 1;";
1239 return ResultCommandText;
1244 private void ExecuteCommand()
1246 // Check the connection state first.
1247 CheckConnectionState();
1249 // reset any responses just before getting new ones
1250 connector.Mediator.ResetResponses();
1255 Connector.Query(this);
1257 // Check for errors and/or notifications and do the Right Thing.
1258 connector.CheckErrorsAndNotifications();
1267 connector.Execute(new NpgsqlExecute(bind.PortalName, 0));
1269 // Check for errors and/or notifications and do the Right Thing.
1270 connector.CheckErrorsAndNotifications();
1274 // As per documentation:
1275 // "[...] When an error is detected while processing any extended-query message,
1276 // the backend issues ErrorResponse, then reads and discards messages until a
1277 // Sync is reached, then issues ReadyForQuery and returns to normal message processing.[...]"
1278 // So, send a sync command if we get any problems.