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 // Permission to use, copy, modify, and distribute this software and its
13 // documentation for any purpose, without fee, and without a written
14 // agreement is hereby granted, provided that the above copyright notice
15 // and this paragraph and the following two paragraphs appear in all copies.
17 // IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
18 // FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
19 // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
20 // DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
21 // THE POSSIBILITY OF SUCH DAMAGE.
23 // THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
24 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25 // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
26 // ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
27 // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
30 using System.ComponentModel;
32 using System.Data.Common;
34 using System.Reflection;
35 using System.Resources;
37 using System.Text.RegularExpressions;
47 /// Represents a SQL statement or function (stored procedure) to execute
48 /// against a PostgreSQL database. This class cannot be inherited.
51 [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlCommand)), ToolboxItem(true)]
54 public sealed class NpgsqlCommand : DbCommand, ICloneable
56 // Logging related values
57 private static readonly String CLASSNAME = "NpgsqlCommand";
58 private static ResourceManager resman = new ResourceManager(MethodBase.GetCurrentMethod().DeclaringType);
59 private readonly Regex parameterReplace = new Regex(@"([:@][\w\.]*)", RegexOptions.Singleline);
61 private NpgsqlConnection connection;
62 private NpgsqlConnector m_Connector; //renamed to account for hiding it in a local function
63 //if all locals were named with this prefix, it would solve LOTS of issues.
64 private NpgsqlTransaction transaction;
66 private Int32 timeout;
67 private CommandType type;
68 private readonly NpgsqlParameterCollection parameters;
69 private String planName;
70 private Boolean designTimeVisible;
72 private NpgsqlParse parse;
73 private NpgsqlBind bind;
75 private Int64 lastInsertedOID = 0;
80 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class.
82 public NpgsqlCommand()
83 : this(String.Empty, null, null)
88 /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query.
90 /// <param name="cmdText">The text of the query.</param>
91 public NpgsqlCommand(String cmdText)
92 : this(cmdText, null, null)
97 /// 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>.
99 /// <param name="cmdText">The text of the query.</param>
100 /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
101 public NpgsqlCommand(String cmdText, NpgsqlConnection connection)
102 : this(cmdText, connection, null)
107 /// 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>.
109 /// <param name="cmdText">The text of the query.</param>
110 /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
111 /// <param name="transaction">The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see> in which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.</param>
112 public NpgsqlCommand(String cmdText, NpgsqlConnection connection, NpgsqlTransaction transaction)
114 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
116 planName = String.Empty;
118 this.connection = connection;
120 if (this.connection != null)
122 this.m_Connector = connection.Connector;
125 parameters = new NpgsqlParameterCollection();
126 type = CommandType.Text;
127 this.Transaction = transaction;
133 /// Used to execute internal commands.
135 internal NpgsqlCommand(String cmdText, NpgsqlConnector connector)
137 resman = new ResourceManager(this.GetType());
138 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
141 planName = String.Empty;
143 this.m_Connector = connector;
144 type = CommandType.Text;
146 parameters = new NpgsqlParameterCollection();
148 // Internal commands aren't affected by command timeout value provided by user.
152 // Public properties.
154 /// Gets or sets the SQL statement or function (stored procedure) to execute at the data source.
156 /// <value>The Transact-SQL statement or stored procedure to execute. The default is an empty string.</value>
157 [Category("Data"), DefaultValue("")]
158 public override String CommandText
164 // [TODO] Validate commandtext.
165 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandText", value);
167 planName = String.Empty;
174 /// Gets or sets the wait time before terminating the attempt
175 /// to execute a command and generating an error.
177 /// <value>The time (in seconds) to wait for the command to execute.
178 /// The default is 20 seconds.</value>
180 public override Int32 CommandTimeout
182 get { return timeout; }
188 throw new ArgumentOutOfRangeException(resman.GetString("Exception_CommandTimeoutLessZero"));
192 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandTimeout", value);
197 /// Gets or sets a value indicating how the
198 /// <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> property is to be interpreted.
200 /// <value>One of the <see cref="System.Data.CommandType">CommandType</see> values. The default is <see cref="System.Data.CommandType">CommandType.Text</see>.</value>
201 [Category("Data"), DefaultValue(CommandType.Text)]
202 public override CommandType CommandType
209 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandType", value);
213 protected override DbConnection DbConnection
215 get { return Connection; }
219 Connection = (NpgsqlConnection)value;
220 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "DbConnection", value);
225 /// Gets or sets the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>
226 /// used by this instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
228 /// <value>The connection to a data source. The default value is a null reference.</value>
229 [Category("Behavior"), DefaultValue(null)]
230 public new NpgsqlConnection Connection
234 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Connection");
240 if (this.Connection == value)
245 //if (this.transaction != null && this.transaction.Connection == null)
246 // this.transaction = null;
248 // All this checking needs revising. It should be simpler.
249 // This this.Connector != null check was added to remove the nullreferenceexception in case
250 // of the previous connection has been closed which makes Connector null and so the last check would fail.
251 // See bug 1000581 for more details.
252 if (this.transaction != null && this.connection != null && this.Connector != null && this.Connector.Transaction != null)
254 throw new InvalidOperationException(resman.GetString("Exception_SetConnectionInTransaction"));
257 this.connection = value;
259 if (this.connection != null)
261 m_Connector = this.connection.Connector;
266 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Connection", value);
270 internal NpgsqlConnector Connector
274 if (this.connection != null)
276 m_Connector = this.connection.Connector;
283 protected override DbParameterCollection DbParameterCollection
285 get { return Parameters; }
289 /// Gets the <see cref="Npgsql.NpgsqlParameterCollection">NpgsqlParameterCollection</see>.
291 /// <value>The parameters of the SQL statement or function (stored procedure). The default is an empty collection.</value>
293 [Category("Data"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
296 public new NpgsqlParameterCollection Parameters
300 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Parameters");
306 protected override DbTransaction DbTransaction
308 get { return Transaction; }
311 Transaction = (NpgsqlTransaction)value;
312 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Transaction", value);
317 /// Gets or sets the <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
318 /// within which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.
320 /// <value>The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>.
321 /// The default value is a null reference.</value>
323 [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
326 public new NpgsqlTransaction Transaction
330 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Transaction");
332 if (this.transaction != null && this.transaction.Connection == null)
334 this.transaction = null;
336 return this.transaction;
341 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Transaction", value);
343 this.transaction = value;
348 /// Gets or sets how command results are applied to the <see cref="System.Data.DataRow">DataRow</see>
349 /// when used by the <see cref="System.Data.Common.DbDataAdapter.Update(DataSet)">Update</see>
350 /// method of the <see cref="System.Data.Common.DbDataAdapter">DbDataAdapter</see>.
352 /// <value>One of the <see cref="System.Data.UpdateRowSource">UpdateRowSource</see> values.</value>
354 [Category("Behavior"), DefaultValue(UpdateRowSource.Both)]
357 public override UpdateRowSource UpdatedRowSource
361 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "UpdatedRowSource");
363 return UpdateRowSource.Both;
370 /// Returns oid of inserted row. This is only updated when using executenonQuery and when command inserts just a single row. If table is created without oids, this will always be 0.
372 public Int64 LastInsertedOID
374 get { return lastInsertedOID; }
379 /// Attempts to cancel the execution of a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
381 /// <remarks>This Method isn't implemented yet.</remarks>
382 public override void Cancel()
384 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Cancel");
388 // get copy for thread safety of null test
389 NpgsqlConnector connector = Connector;
390 if (connector != null)
392 connector.CancelRequest();
397 Connection.ClearPool();
399 catch (NpgsqlException)
401 // Cancel documentation says the Cancel doesn't throw on failure
406 /// Create a new command based on this one.
408 /// <returns>A new NpgsqlCommand object.</returns>
409 Object ICloneable.Clone()
415 /// Create a new connection based on this one.
417 /// <returns>A new NpgsqlConnection object.</returns>
418 public NpgsqlCommand Clone()
420 // TODO: Add consistency checks.
422 NpgsqlCommand clone = new NpgsqlCommand(CommandText, Connection, Transaction);
423 clone.CommandTimeout = CommandTimeout;
424 clone.CommandType = CommandType;
425 clone.DesignTimeVisible = DesignTimeVisible;
426 foreach (NpgsqlParameter parameter in Parameters)
428 clone.Parameters.Add(((ICloneable)parameter).Clone());
434 /// Creates a new instance of an <see cref="System.Data.Common.DbParameter">DbParameter</see> object.
436 /// <returns>An <see cref="System.Data.Common.DbParameter">DbParameter</see> object.</returns>
437 protected override DbParameter CreateDbParameter()
439 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateDbParameter");
441 return CreateParameter();
445 /// Creates a new instance of a <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.
447 /// <returns>A <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.</returns>
448 public new NpgsqlParameter CreateParameter()
450 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateParameter");
452 return new NpgsqlParameter();
456 /// Slightly optimised version of ExecuteNonQuery() for internal ues in cases where the number
457 /// of affected rows is of no interest.
459 internal void ExecuteBlind()
461 GetReader(CommandBehavior.SequentialAccess).Dispose();
465 /// Executes a SQL statement against the connection and returns the number of rows affected.
467 /// <returns>The number of rows affected if known; -1 otherwise.</returns>
468 public override Int32 ExecuteNonQuery()
470 //We treat this as a simple wrapper for calling ExecuteReader() and then
471 //update the records affected count at every call to NextResult();
472 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteNonQuery");
474 using (NpgsqlDataReader rdr = GetReader(CommandBehavior.SequentialAccess))
478 int thisRecord = rdr.RecordsAffected;
479 if (thisRecord != -1)
481 ret = (ret ?? 0) + thisRecord;
483 lastInsertedOID = rdr.LastInsertedOID ?? lastInsertedOID;
485 while (rdr.NextResult());
491 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
492 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
493 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
494 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
496 /// <param name="behavior">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
497 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
498 protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
500 return ExecuteReader(behavior);
504 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
505 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
506 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
508 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
509 public new NpgsqlDataReader ExecuteReader()
511 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader");
513 return ExecuteReader(CommandBehavior.Default);
517 /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
518 /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
519 /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
520 /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
522 /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
523 /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
524 /// <remarks>Currently the CommandBehavior parameter is ignored.</remarks>
525 public new NpgsqlDataReader ExecuteReader(CommandBehavior cb)
527 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader", cb);
529 // Close connection if requested even when there is an error.
533 if (connection != null)
535 if (connection.PreloadReader)
537 //Adjust behaviour so source reader is sequential access - for speed - and doesn't close the connection - or it'll do so at the wrong time.
538 CommandBehavior adjusted = (cb | CommandBehavior.SequentialAccess) & ~CommandBehavior.CloseConnection;
539 return new CachingDataReader(GetReader(adjusted), cb);
542 return GetReader(cb);
546 if ((cb & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection)
555 internal ForwardsOnlyDataReader GetReader(CommandBehavior cb)
559 CheckConnectionState();
561 // reset any responses just before getting new ones
562 Connector.Mediator.ResetResponses();
564 // Set command timeout.
565 m_Connector.Mediator.CommandTimeout = CommandTimeout;
568 using (m_Connector.BlockNotificationThread())
572 return new ForwardsOnlyDataReader(m_Connector.QueryEnum(this), cb, this, m_Connector.BlockNotificationThread(), false);
574 //return new ForwardsOnlyDataReader(m_Connector.QueryEnum(this), cb, this, m_Connector.BlockNotificationThread(), false);
579 new ForwardsOnlyDataReader(m_Connector.ExecuteEnum(new NpgsqlExecute(bind.PortalName, 0)), cb, this,
580 m_Connector.BlockNotificationThread(), true);
581 //return new ForwardsOnlyDataReader(m_Connector.ExecuteEnum(new NpgsqlExecute(bind.PortalName, 0)), cb, this, m_Connector.BlockNotificationThread(), true);
585 catch (IOException ex)
587 throw ClearPoolAndCreateException(ex);
592 /// This method binds the parameters from parameters collection to the bind
595 private void BindParameters()
597 if (parameters.Count != 0)
599 Object[] parameterValues = new Object[parameters.Count];
600 Int16[] parameterFormatCodes = bind.ParameterFormatCodes;
602 for (Int32 i = 0; i < parameters.Count; i++)
604 // Do not quote strings, or escape existing quotes - this will be handled by the backend.
605 // DBNull or null values are returned as null.
606 // TODO: Would it be better to remove this null special handling out of ConvertToBackend??
608 // Do special handling of bytea values. They will be send in binary form.
609 // TODO: Add binary format support for all supported types. Not only bytea.
610 if (parameters[i].TypeInfo.NpgsqlDbType != NpgsqlDbType.Bytea)
612 parameterValues[i] = parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, true);
616 if (parameters[i].Value != DBNull.Value)
618 parameterFormatCodes[i] = (Int16)FormatCode.Binary;
619 parameterValues[i] = (byte[])parameters[i].Value;
623 parameterValues[i] = parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, true);
627 bind.ParameterValues = parameterValues;
628 bind.ParameterFormatCodes = parameterFormatCodes;
631 Connector.Bind(bind);
637 /// Executes the query, and returns the first column of the first row
638 /// in the result set returned by the query. Extra columns or rows are ignored.
640 /// <returns>The first column of the first row in the result set,
641 /// or a null reference if the result set is empty.</returns>
642 public override Object ExecuteScalar()
645 NpgsqlDataReader reader =
646 GetReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
648 return reader.Read() && reader.FieldCount != 0 ? reader.GetValue(0) : null;
653 /// Creates a prepared version of the command on a PostgreSQL server.
655 public override void Prepare()
657 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Prepare");
659 // Check the connection state.
660 CheckConnectionState();
662 // reset any responses just before getting new ones
663 Connector.Mediator.ResetResponses();
665 // Set command timeout.
666 m_Connector.Mediator.CommandTimeout = CommandTimeout;
668 if (!m_Connector.SupportsPrepare)
670 return; // Do nothing.
673 if (m_Connector.BackendProtocolVersion == ProtocolVersion.Version2)
675 using (NpgsqlCommand command = new NpgsqlCommand(GetPrepareCommandText(), m_Connector))
677 command.ExecuteBlind();
682 using (m_Connector.BlockNotificationThread())
686 // Use the extended query parsing...
687 planName = m_Connector.NextPlanName();
688 String portalName = m_Connector.NextPortalName();
690 parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] { });
692 m_Connector.Parse(parse);
694 // We need that because Flush() doesn't cause backend to send
695 // ReadyForQuery on error. Without ReadyForQuery, we don't return
696 // from query extended processing.
698 // We could have used Connector.Flush() which sends us back a
699 // ReadyForQuery, but on postgresql server below 8.1 there is an error
700 // with extended query processing which hinders us from using it.
701 m_Connector.RequireReadyForQuery = false;
706 NpgsqlDescribe describe = new NpgsqlDescribe('S', planName);
709 m_Connector.Describe(describe);
711 NpgsqlRowDescription returnRowDesc = m_Connector.Sync();
713 Int16[] resultFormatCodes;
716 if (returnRowDesc != null)
718 resultFormatCodes = new Int16[returnRowDesc.NumFields];
720 for (int i = 0; i < returnRowDesc.NumFields; i++)
722 NpgsqlRowDescription.FieldData returnRowDescData = returnRowDesc[i];
725 if (returnRowDescData.TypeInfo != null && returnRowDescData.TypeInfo.NpgsqlDbType == NpgsqlDbType.Bytea)
728 resultFormatCodes[i] = (Int16)FormatCode.Binary;
733 resultFormatCodes[i] = (Int16)FormatCode.Text;
739 resultFormatCodes = new Int16[] { 0 };
742 bind = new NpgsqlBind("", planName, new Int16[Parameters.Count], null, resultFormatCodes);
744 catch (IOException e)
746 throw ClearPoolAndCreateException(e);
750 // See ExecuteCommand method for a discussion of this.
761 /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
763 protected override void Dispose (bool disposing)
768 // Only if explicitly calling Close or dispose we still have access to
769 // managed resources.
770 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
771 if (connection != null)
773 connection.Dispose();
775 base.Dispose(disposing);
781 /// This method checks the connection state to see if the connection
782 /// is set or it is open. If one of this conditions is not met, throws
783 /// an InvalidOperationException
785 private void CheckConnectionState()
787 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CheckConnectionState");
790 // Check the connection state.
791 if (Connector == null || Connector.State == ConnectionState.Closed)
793 throw new InvalidOperationException(resman.GetString("Exception_ConnectionNotOpen"));
795 if (Connector.State != ConnectionState.Open)
797 throw new InvalidOperationException(
798 "There is already an open DataReader associated with this Command which must be closed first.");
803 /// This method substitutes the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see>, if exist, in the command
804 /// to their actual values.
805 /// The parameter name format is <b>:ParameterName</b>.
807 /// <returns>A version of <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> with the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see> inserted.</returns>
808 internal String GetCommandText()
810 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetCommandText");
812 if (string.IsNullOrEmpty(planName))//== String.Empty)
814 return GetClearCommandText();
816 return GetPreparedCommandText();
821 private String GetClearCommandText()
823 if (NpgsqlEventLog.Level == LogLevel.Debug)
825 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetClearCommandText");
828 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
830 Boolean functionReturnsRecord = false; // Functions don't return record by default.
832 Boolean functionReturnsRefcursor = false; // Functions don't return refcursor by default.
834 String result = text;
837 if (type == CommandType.StoredProcedure)
839 if (Parameters.Count > 0)
841 functionReturnsRecord = !CheckFunctionHasOutParameters() && CheckFunctionReturn("record");
844 functionReturnsRefcursor = CheckFunctionReturn("refcursor");
846 // 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.
847 if ((!result.Trim().EndsWith(")")) && (!result.Trim().EndsWith(";")))
849 addProcedureParenthesis = true;
853 if (Connector.SupportsPrepare)
855 result = "select * from " + result; // This syntax is only available in 7.3+ as well SupportsPrepare.
859 result = "select " + result; //Only a single result return supported. 7.2 and earlier.
862 else if (type == CommandType.TableDirect)
864 return "select * from " + result; // There is no parameter support on table direct.
867 if (parameters == null || parameters.Count == 0)
869 if (addProcedureParenthesis)
875 // If function returns ref cursor just process refcursor-result function call
876 // and return command which will be used to return data from refcursor.
878 if (functionReturnsRefcursor)
880 return ProcessRefcursorFunctionReturn(result);
884 if (functionReturnsRecord)
886 result = AddFunctionReturnsRecordSupport(result);
893 // Get parameters in query string to translate them to their actual values.
895 // This regular expression gets all the parameters in format :param or @param
896 // and everythingelse.
897 // This is only needed if query string has parameters. Else, just append the
898 // parameter values in order they were put in parameter collection.
901 // If parenthesis don't need to be added, they were added by user with parameter names. Replace them.
902 if (!addProcedureParenthesis)
904 StringBuilder sb = new StringBuilder();
906 string[] queryparts = parameterReplace.Split(result);
908 foreach (String s in queryparts)
910 if (s == string.Empty)
915 if ((s[0] == ':' || s[0] == '@') && Parameters.TryGetValue(s, out p))
918 // It's a parameter. Lets handle it.
919 if ((p.Direction == ParameterDirection.Input) || (p.Direction == ParameterDirection.InputOutput))
922 sb.Append(p.TypeInfo.ConvertToBackend(p.Value, false));
927 sb.Append(p.TypeInfo.CastName);
930 if (p.TypeInfo.UseSize && (p.Size > 0))
932 sb.Append("(").Append(p.Size).Append(")");
943 result = sb.ToString();
948 for (Int32 i = 0; i < parameters.Count; i++)
950 NpgsqlParameter Param = parameters[i];
953 if ((Param.Direction == ParameterDirection.Input) || (Param.Direction == ParameterDirection.InputOutput))
956 result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + "::" + Param.TypeInfo.CastName + ",";
958 result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + ",";
963 // Remove a trailing comma added from parameter handling above. If any.
964 // Maybe there are only output parameters. If so, there will be no comma.
965 if (result.EndsWith(","))
967 result = result.Remove(result.Length - 1, 1);
973 if (functionReturnsRecord)
975 result = AddFunctionReturnsRecordSupport(result);
978 // If function returns ref cursor just process refcursor-result function call
979 // and return command which will be used to return data from refcursor.
981 if (functionReturnsRefcursor)
983 return ProcessRefcursorFunctionReturn(result);
990 private Boolean CheckFunctionHasOutParameters()
992 // Check if this function has output parameters.
993 // This is used to enable or not the colum definition list
994 // when calling functions which return record.
995 // Functions which has out or inout parameters have return record
996 // but doesn't allow column definition list.
997 // See http://pgfoundry.org/forum/forum.php?thread_id=1075&forum_id=519
998 // for discussion about that.
1001 // inout parameters are only supported from 8.1+ versions.
1002 if (Connection.PostgreSqlVersion < new Version(8, 1, 0))
1008 //String outParameterExistanceQuery =
1009 // "select count(*) > 0 from pg_proc where proname=:proname and ('o' = any (proargmodes) OR 'b' = any(proargmodes))";
1012 // Updated after 0.99.3 to support the optional existence of a name qualifying schema and allow for case insensitivity
1013 // when the schema or procedure name do not contain a quote.
1014 // The hard-coded schema name 'public' was replaced with code that uses schema as a qualifier, only if it is provided.
1016 String returnRecordQuery;
1018 StringBuilder parameterTypes = new StringBuilder("");
1021 // Process parameters
1023 foreach (NpgsqlParameter p in Parameters)
1025 if ((p.Direction == ParameterDirection.Input) || (p.Direction == ParameterDirection.InputOutput))
1027 parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " ");
1032 // Process schema name.
1034 String schemaName = String.Empty;
1035 String procedureName = String.Empty;
1038 String[] fullName = CommandText.Split('.');
1040 if (fullName.Length == 2)
1043 "select count(*) > 0 from pg_proc p left join pg_namespace n on p.pronamespace = n.oid where prorettype = ( select oid from pg_type where typname = 'record' ) and proargtypes=:proargtypes and proname=:proname and n.nspname=:nspname and ('o' = any (proargmodes) OR 'b' = any(proargmodes))";
1045 schemaName = (fullName[0].IndexOf("\"") != -1) ? fullName[0] : fullName[0].ToLower();
1046 procedureName = (fullName[1].IndexOf("\"") != -1) ? fullName[1] : fullName[1].ToLower();
1050 // Instead of defaulting don't use the nspname, as an alternative, query pg_proc and pg_namespace to try and determine the nspname.
1051 //schemaName = "public"; // This was removed after build 0.99.3 because the assumption that a function is in public is often incorrect.
1053 "select count(*) > 0 from pg_proc p where prorettype = ( select oid from pg_type where typname = 'record' ) and proargtypes=:proargtypes and proname=:proname and ('o' = any (proargmodes) OR 'b' = any(proargmodes))";
1055 procedureName = (CommandText.IndexOf("\"") != -1) ? CommandText : CommandText.ToLower();
1059 NpgsqlCommand c = new NpgsqlCommand(returnRecordQuery, Connection);
1061 c.Parameters.Add(new NpgsqlParameter("proargtypes", NpgsqlDbType.Oidvector));
1062 c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text));
1064 c.Parameters[0].Value = parameterTypes.ToString();
1065 c.Parameters[1].Value = procedureName;
1067 if (schemaName != null && schemaName.Length > 0)
1069 c.Parameters.Add(new NpgsqlParameter("nspname", NpgsqlDbType.Text));
1070 c.Parameters[2].Value = schemaName;
1074 Boolean ret = (Boolean)c.ExecuteScalar();
1076 // reset any responses just before getting new ones
1077 m_Connector.Mediator.ResetResponses();
1079 // Set command timeout.
1080 m_Connector.Mediator.CommandTimeout = CommandTimeout;
1085 private Boolean CheckFunctionReturn(String ReturnType)
1087 // Updated after 0.99.3 to support the optional existence of a name qualifying schema and allow for case insensitivity
1088 // when the schema or procedure name do not contain a quote.
1089 // The hard-coded schema name 'public' was replaced with code that uses schema as a qualifier, only if it is provided.
1091 String returnRecordQuery;
1093 StringBuilder parameterTypes = new StringBuilder("");
1096 // Process parameters
1098 foreach (NpgsqlParameter p in Parameters)
1100 if ((p.Direction == ParameterDirection.Input) || (p.Direction == ParameterDirection.InputOutput))
1102 parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " ");
1107 // Process schema name.
1109 String schemaName = String.Empty;
1110 String procedureName = String.Empty;
1113 String[] fullName = CommandText.Split('.');
1115 if (fullName.Length == 2)
1118 "select count(*) > 0 from pg_proc p left join pg_namespace n on p.pronamespace = n.oid where prorettype = ( select oid from pg_type where typname = :typename ) and proargtypes=:proargtypes and proname=:proname and n.nspname=:nspname";
1120 schemaName = (fullName[0].IndexOf("\"") != -1) ? fullName[0] : fullName[0].ToLower();
1121 procedureName = (fullName[1].IndexOf("\"") != -1) ? fullName[1] : fullName[1].ToLower();
1125 // Instead of defaulting don't use the nspname, as an alternative, query pg_proc and pg_namespace to try and determine the nspname.
1126 //schemaName = "public"; // This was removed after build 0.99.3 because the assumption that a function is in public is often incorrect.
1128 "select count(*) > 0 from pg_proc p where prorettype = ( select oid from pg_type where typname = :typename ) and proargtypes=:proargtypes and proname=:proname";
1130 procedureName = (CommandText.IndexOf("\"") != -1) ? CommandText : CommandText.ToLower();
1136 using (NpgsqlCommand c = new NpgsqlCommand(returnRecordQuery, Connection))
1138 c.Parameters.Add(new NpgsqlParameter("typename", NpgsqlDbType.Text));
1139 c.Parameters.Add(new NpgsqlParameter("proargtypes", NpgsqlDbType.Oidvector));
1140 c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text));
1142 c.Parameters[0].Value = ReturnType;
1143 c.Parameters[1].Value = parameterTypes.ToString();
1144 c.Parameters[2].Value = procedureName;
1146 if (schemaName != null && schemaName.Length > 0)
1148 c.Parameters.Add(new NpgsqlParameter("nspname", NpgsqlDbType.Text));
1149 c.Parameters[3].Value = schemaName;
1153 ret = (Boolean)c.ExecuteScalar();
1156 // reset any responses just before getting new ones
1157 m_Connector.Mediator.ResetResponses();
1159 // Set command timeout.
1160 m_Connector.Mediator.CommandTimeout = CommandTimeout;
1166 private String AddFunctionReturnsRecordSupport(String OriginalResult)
1168 StringBuilder sb = new StringBuilder(OriginalResult);
1172 foreach (NpgsqlParameter p in Parameters)
1174 if ((p.Direction == ParameterDirection.Output) || (p.Direction == ParameterDirection.InputOutput))
1176 sb.Append(String.Format("{0} {1}, ", p.CleanName, p.TypeInfo.Name));
1180 String result = sb.ToString();
1182 result = result.Remove(result.Length - 2, 1);
1191 /// This methods takes a string with a function call witch returns a refcursor or a set of
1192 /// refcursor. It will return the names of the open cursors/portals which will hold
1193 /// results. In turn, it returns the string which is needed to get the data of this cursors
1194 /// in form of one resultset for each cursor open. This way, clients don't need to do anything
1195 /// else besides calling function normally to get results in this way.
1197 private String ProcessRefcursorFunctionReturn(String FunctionCall)
1199 StringBuilder sb = new StringBuilder();
1200 using (NpgsqlCommand c = new NpgsqlCommand(FunctionCall, Connection))
1202 using (NpgsqlDataReader dr = c.GetReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
1206 sb.Append("fetch all from \"").Append(dr.GetString(0)).Append("\";");
1211 sb.Append(";"); // Just in case there is no response from refcursor function return.
1213 // reset any responses just before getting new ones
1214 m_Connector.Mediator.ResetResponses();
1216 // Set command timeout.
1217 m_Connector.Mediator.CommandTimeout = CommandTimeout;
1219 return sb.ToString();
1223 private String GetPreparedCommandText()
1225 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPreparedCommandText");
1227 if (parameters.Count == 0)
1229 return string.Format("execute {0}", planName);
1233 StringBuilder result = new StringBuilder("execute " + planName + '(');
1236 for (Int32 i = 0; i < parameters.Count; i++)
1238 if (parameters[i].UseCast)
1239 result.Append(string.Format("{0}::{1},", parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false), parameters[i].TypeInfo.CastName));
1241 result.Append(parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false) + ',');
1245 result = result.Remove(result.Length - 1, 1);
1248 return result.ToString();
1252 private String GetParseCommandText()
1254 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetParseCommandText");
1256 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1258 String parseCommand = text;
1260 if (type == CommandType.StoredProcedure)
1262 // 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.
1263 if (!parseCommand.Trim().EndsWith(")"))
1265 addProcedureParenthesis = true;
1266 parseCommand += "(";
1269 parseCommand = string.Format("select * from {0}", parseCommand); // This syntax is only available in 7.3+ as well SupportsPrepare.
1273 if (type == CommandType.TableDirect)
1275 return string.Format("select * from {0}", parseCommand); // There is no parameter support on TableDirect.
1278 if (parameters.Count > 0)
1280 // The ReplaceParameterValue below, also checks if the parameter is present.
1282 String parameterName;
1285 for (i = 0; i < parameters.Count; i++)
1287 if ((parameters[i].Direction == ParameterDirection.Input) ||
1288 (parameters[i].Direction == ParameterDirection.InputOutput))
1290 if (!addProcedureParenthesis)
1292 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1293 parameterName = parameters[i].CleanName;
1294 //textCommand = textCommand.Replace(':' + parameterName, "$" + (i+1));
1296 // Just add typecast if needed.
1297 if (parameters[i].UseCast)
1298 parseCommand = ReplaceParameterValue(parseCommand, parameterName, string.Format("${0}::{1}", (i + 1), parameters[i].TypeInfo.CastName));
1300 parseCommand = ReplaceParameterValue(parseCommand, parameterName, string.Format("${0}", (i + 1)));
1304 if (parameters[i].UseCast)
1305 parseCommand += string.Format("${0}::{1}", (i + 1), parameters[i].TypeInfo.CastName);
1307 parseCommand += string.Format("${0}", (i + 1));
1314 return string.Format("{0}{1}", parseCommand, addProcedureParenthesis ? ")" : string.Empty);
1317 //if (addProcedureParenthesis)
1319 // return parseCommand + ")";
1323 // return parseCommand;
1328 private String GetPrepareCommandText()
1330 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPrepareCommandText");
1332 Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default.
1334 planName = Connector.NextPlanName();
1336 StringBuilder command = new StringBuilder("prepare " + planName);
1338 String textCommand = text;
1340 if (type == CommandType.StoredProcedure)
1342 // 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.
1343 if (!textCommand.Trim().EndsWith(")"))
1345 addProcedureParenthesis = true;
1349 textCommand = "select * from " + textCommand;
1351 else if (type == CommandType.TableDirect)
1353 return "select * from " + textCommand; // There is no parameter support on TableDirect.
1357 if (parameters.Count > 0)
1359 // The ReplaceParameterValue below, also checks if the parameter is present.
1361 String parameterName;
1364 for (i = 0; i < parameters.Count; i++)
1366 if ((parameters[i].Direction == ParameterDirection.Input) ||
1367 (parameters[i].Direction == ParameterDirection.InputOutput))
1369 if (!addProcedureParenthesis)
1371 //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1372 parameterName = parameters[i].CleanName;
1373 // The space in front of '$' fixes a parsing problem in 7.3 server
1374 // which gives errors of operator when finding the caracters '=$' in
1376 textCommand = ReplaceParameterValue(textCommand, parameterName, " $" + (i + 1));
1380 textCommand += " $" + (i + 1);
1385 //[TODO] Check if there are any missing parameters in the query.
1386 // For while, an error is thrown saying about the ':' char.
1388 command.Append('(');
1390 for (i = 0; i < parameters.Count; i++)
1392 // command.Append(NpgsqlTypesHelper.GetDefaultTypeInfo(parameters[i].DbType));
1393 if (parameters[i].UseCast)
1394 command.Append(parameters[i].TypeInfo.Name);
1396 command.Append("unknown");
1398 command.Append(',');
1401 command = command.Remove(command.Length - 1, 1);
1402 command.Append(')');
1405 if (addProcedureParenthesis)
1410 command.Append(" as ");
1411 command.Append(textCommand);
1414 return command.ToString();
1418 private static String ReplaceParameterValue(String result, String parameterName, String paramVal)
1420 String quote_pattern = @"['][^']*[']";
1421 string parameterMarker = string.Empty;
1422 // search parameter marker since it is not part of the name
1423 String pattern = "[- |\n\r\t,)(;=+/<>][:|@]" + parameterMarker + parameterName + "([- |\n\r\t,)(;=+/<>]|$)";
1425 String withoutquote = result;
1426 Boolean found = false;
1428 // Suppress quoted string from query (because we ave to ignore them)
1429 MatchCollection results = Regex.Matches(result, quote_pattern);
1430 foreach (Match match in results)
1432 start = match.Index;
1433 end = match.Index + match.Length;
1434 String spaces = new String(' ', match.Length - 2);
1435 withoutquote = withoutquote.Substring(0, start + 1) + spaces + withoutquote.Substring(end - 1);
1439 // Now we look for the searched parameters on the "withoutquote" string
1440 results = Regex.Matches(withoutquote, pattern);
1441 if (results.Count == 0)
1443 // If no parameter is found, go out!
1446 // We take the first parameter found
1448 Match match = results[0];
1449 start = match.Index;
1450 if ((match.Length - parameterName.Length) == 3)
1452 // If the found string is not the end of the string
1453 end = match.Index + match.Length - 1;
1457 // If the found string is the end of the string
1458 end = match.Index + match.Length;
1460 result = result.Substring(0, start + 1) + paramVal + result.Substring(end);
1461 withoutquote = withoutquote.Substring(0, start + 1) + paramVal + withoutquote.Substring(end);
1466 throw new IndexOutOfRangeException(String.Format(resman.GetString("Exception_ParamNotInQuery"), parameterName));
1471 private void SetCommandTimeout()
1473 if (Connection != null)
1475 timeout = Connection.CommandTimeout;
1479 timeout = (int)NpgsqlConnectionStringBuilder.GetDefaultValue(Keywords.CommandTimeout);
1483 internal NpgsqlException ClearPoolAndCreateException(Exception e)
1485 Connection.ClearPool();
1486 return new NpgsqlException(resman.GetString("Exception_ConnectionBroken"), e);
1489 public override bool DesignTimeVisible
1491 get { return designTimeVisible; }
1492 set { designTimeVisible = value; }