// created on 21/5/2002 at 20:03 // Npgsql.NpgsqlCommand.cs // // Author: // Francisco Jr. (fxjrlists@yahoo.com.br) // // Copyright (C) 2002 The Npgsql Development Team // npgsql-general@gborg.postgresql.org // http://gborg.postgresql.org/project/npgsql/projdisplay.php // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Data; using System.Text; using System.Resources; using System.ComponentModel; using System.Collections; using NpgsqlTypes; using System.Text.RegularExpressions; #if WITHDESIGN using Npgsql.Design; #endif namespace Npgsql { /// /// Represents a SQL statement or function (stored procedure) to execute /// against a PostgreSQL database. This class cannot be inherited. /// #if WITHDESIGN [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlCommand)), ToolboxItem(true)] #endif public sealed class NpgsqlCommand : Component, IDbCommand, ICloneable { // Logging related values private static readonly String CLASSNAME = "NpgsqlCommand"; private static ResourceManager resman = new ResourceManager(typeof(NpgsqlCommand)); private NpgsqlConnection connection; private NpgsqlConnector connector; private NpgsqlTransaction transaction; private String text; private Int32 timeout; private CommandType type; private NpgsqlParameterCollection parameters; private String planName; private NpgsqlParse parse; private NpgsqlBind bind; private Boolean invalidTransactionDetected = false; private CommandBehavior commandBehavior; // Constructors /// /// Initializes a new instance of the NpgsqlCommand class. /// public NpgsqlCommand() : this(String.Empty, null, null) {} /// /// Initializes a new instance of the NpgsqlCommand class with the text of the query. /// /// The text of the query. public NpgsqlCommand(String cmdText) : this(cmdText, null, null) {} /// /// Initializes a new instance of the NpgsqlCommand class with the text of the query and a NpgsqlConnection. /// /// The text of the query. /// A NpgsqlConnection that represents the connection to a PostgreSQL server. public NpgsqlCommand(String cmdText, NpgsqlConnection connection) : this(cmdText, connection, null) {} /// /// Initializes a new instance of the NpgsqlCommand class with the text of the query, a NpgsqlConnection, and the NpgsqlTransaction. /// /// The text of the query. /// A NpgsqlConnection that represents the connection to a PostgreSQL server. /// The NpgsqlTransaction in which the NpgsqlCommand executes. public NpgsqlCommand(String cmdText, NpgsqlConnection connection, NpgsqlTransaction transaction) { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME); planName = String.Empty; text = cmdText; this.connection = connection; if (this.connection != null) this.connector = connection.Connector; parameters = new NpgsqlParameterCollection(); timeout = 20; type = CommandType.Text; this.Transaction = transaction; commandBehavior = CommandBehavior.Default; } /// /// Used to execute internal commands. /// internal NpgsqlCommand(String cmdText, NpgsqlConnector connector) { resman = new System.Resources.ResourceManager(this.GetType()); NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME); planName = String.Empty; text = cmdText; this.connector = connector; type = CommandType.Text; commandBehavior = CommandBehavior.Default; parameters = new NpgsqlParameterCollection(); timeout = 20; } // Public properties. /// /// Gets or sets the SQL statement or function (stored procedure) to execute at the data source. /// /// The Transact-SQL statement or stored procedure to execute. The default is an empty string. [Category("Data"), DefaultValue("")] public String CommandText { get { return text; } set { // [TODO] Validate commandtext. NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandText", value); text = value; planName = String.Empty; parse = null; bind = null; commandBehavior = CommandBehavior.Default; } } /// /// Gets or sets the wait time before terminating the attempt /// to execute a command and generating an error. /// /// The time (in seconds) to wait for the command to execute. /// The default is 20 seconds. [DefaultValue(20)] public Int32 CommandTimeout { get { return timeout; } set { if (value < 0) throw new ArgumentOutOfRangeException(resman.GetString("Exception_CommandTimeoutLessZero")); timeout = value; NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandTimeout", value); } } /// /// Gets or sets a value indicating how the /// CommandText property is to be interpreted. /// /// One of the CommandType values. The default is CommandType.Text. [Category("Data"), DefaultValue(CommandType.Text)] public CommandType CommandType { get { return type; } set { type = value; NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandType", value); } } IDbConnection IDbCommand.Connection { get { return Connection; } set { Connection = (NpgsqlConnection) value; NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Connection", value); } } /// /// Gets or sets the NpgsqlConnection /// used by this instance of the NpgsqlCommand. /// /// The connection to a data source. The default value is a null reference. [Category("Behavior"), DefaultValue(null)] public NpgsqlConnection Connection { get { NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Connection"); return connection; } set { if (this.Connection == value) return; //if (this.transaction != null && this.transaction.Connection == null) // this.transaction = null; if (this.transaction != null && this.connection != null && this.Connector.Transaction != null) throw new InvalidOperationException(resman.GetString("Exception_SetConnectionInTransaction")); this.connection = value; Transaction = null; if (this.connection != null) connector = this.connection.Connector; NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Connection", value); } } internal NpgsqlConnector Connector { get { if (connector == null && this.connection != null) connector = this.connection.Connector; return connector; } } IDataParameterCollection IDbCommand.Parameters { get { return Parameters; } } /// /// Gets the NpgsqlParameterCollection. /// /// The parameters of the SQL statement or function (stored procedure). The default is an empty collection. #if WITHDESIGN [Category("Data"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] #endif public NpgsqlParameterCollection Parameters { get { NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Parameters"); return parameters; } } IDbTransaction IDbCommand.Transaction { get { return Transaction; } set { Transaction = (NpgsqlTransaction) value; NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Transaction", value); } } /// /// Gets or sets the NpgsqlTransaction /// within which the NpgsqlCommand executes. /// /// The NpgsqlTransaction. /// The default value is a null reference. #if WITHDESIGN [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] #endif public NpgsqlTransaction Transaction { get { NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Transaction"); if (this.transaction != null && this.transaction.Connection == null) { this.transaction = null; } return this.transaction; } set { NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Transaction" ,value); this.transaction = (NpgsqlTransaction) value; } } /// /// Gets or sets how command results are applied to the DataRow /// when used by the Update /// method of the DbDataAdapter. /// /// One of the UpdateRowSource values. #if WITHDESIGN [Category("Behavior"), DefaultValue(UpdateRowSource.Both)] #endif public UpdateRowSource UpdatedRowSource { get { NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "UpdatedRowSource"); return UpdateRowSource.Both; } set { throw new NotImplementedException(); } } /// /// Attempts to cancel the execution of a NpgsqlCommand. /// /// This Method isn't implemented yet. public void Cancel() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Cancel"); // [TODO] Finish method implementation. throw new NotImplementedException(); } /// /// Create a new command based on this one. /// /// A new NpgsqlCommand object. Object ICloneable.Clone() { return Clone(); } /// /// Create a new connection based on this one. /// /// A new NpgsqlConnection object. public NpgsqlCommand Clone() { // TODO: Add consistency checks. return new NpgsqlCommand(CommandText, Connection, Transaction); } /// /// Creates a new instance of an IDbDataParameter object. /// /// An IDbDataParameter object. IDbDataParameter IDbCommand.CreateParameter() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.CreateParameter"); return (NpgsqlParameter) CreateParameter(); } /// /// Creates a new instance of a NpgsqlParameter object. /// /// A NpgsqlParameter object. public NpgsqlParameter CreateParameter() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateParameter"); return new NpgsqlParameter(); } /// /// Executes a SQL statement against the connection and returns the number of rows affected. /// /// The number of rows affected. public Int32 ExecuteNonQuery() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteNonQuery"); ExecuteCommand(); UpdateOutputParameters(); // If nothing is returned, just return -1. if(Connector.Mediator.CompletedResponses.Count == 0) { return -1; } // Check if the response is available. String firstCompletedResponse = (String)Connector.Mediator.CompletedResponses[0]; if (firstCompletedResponse == null) return -1; String[] ret_string_tokens = firstCompletedResponse.Split(null); // whitespace separator. // Check if the command was insert, delete or update. // Only theses commands return rows affected. // [FIXME] Is there a better way to check this?? if ((String.Compare(ret_string_tokens[0], "INSERT", true) == 0) || (String.Compare(ret_string_tokens[0], "UPDATE", true) == 0) || (String.Compare(ret_string_tokens[0], "DELETE", true) == 0)) // The number of rows affected is in the third token for insert queries // and in the second token for update and delete queries. // In other words, it is the last token in the 0-based array. return Int32.Parse(ret_string_tokens[ret_string_tokens.Length - 1]); else return -1; } private void UpdateOutputParameters() { // Check if there was some resultset returned. If so, put the result in output parameters. NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "UpdateOutputParameters"); // Get ResultSets. ArrayList resultSets = Connector.Mediator.ResultSets; if (resultSets.Count != 0) { NpgsqlResultSet nrs = (NpgsqlResultSet)resultSets[0]; if ((nrs != null) && (nrs.Count > 0)) { NpgsqlAsciiRow nar = (NpgsqlAsciiRow)nrs[0]; Int32 i = 0; Boolean hasMapping = false; // First check if there is any mapping between parameter name and resultset name. // If so, just update output parameters which has mapping. foreach (NpgsqlParameter p in Parameters) { try { if (nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1)) > -1) { hasMapping = true; break; } } catch(ArgumentOutOfRangeException) {} } if (hasMapping) { foreach (NpgsqlParameter p in Parameters) { if (((p.Direction == ParameterDirection.Output) || (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields )) { try { p.Value = nar[nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1))]; i++; } catch(ArgumentOutOfRangeException) {} } } } else foreach (NpgsqlParameter p in Parameters) { if (((p.Direction == ParameterDirection.Output) || (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields )) { p.Value = nar[i]; i++; } } } } } /// /// Sends the CommandText to /// the Connection and builds a /// NpgsqlDataReader. /// /// A NpgsqlDataReader object. IDataReader IDbCommand.ExecuteReader() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader"); return (NpgsqlDataReader) ExecuteReader(); } /// /// Sends the CommandText to /// the Connection and builds a /// NpgsqlDataReader /// using one of the CommandBehavior values. /// /// One of the CommandBehavior values. /// A NpgsqlDataReader object. IDataReader IDbCommand.ExecuteReader(CommandBehavior cb) { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader", cb); return (NpgsqlDataReader) ExecuteReader(cb); } /// /// Sends the CommandText to /// the Connection and builds a /// NpgsqlDataReader. /// /// A NpgsqlDataReader object. public NpgsqlDataReader ExecuteReader() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader"); return ExecuteReader(CommandBehavior.Default); } /// /// Sends the CommandText to /// the Connection and builds a /// NpgsqlDataReader /// using one of the CommandBehavior values. /// /// One of the CommandBehavior values. /// A NpgsqlDataReader object. /// Currently the CommandBehavior parameter is ignored. public NpgsqlDataReader ExecuteReader(CommandBehavior cb) { // [FIXME] No command behavior handling. NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader", cb); commandBehavior = cb; ExecuteCommand(); UpdateOutputParameters(); // Get the resultsets and create a Datareader with them. return new NpgsqlDataReader(Connector.Mediator.ResultSets, Connector.Mediator.CompletedResponses, cb, this); } /// /// This method binds the parameters from parameters collection to the bind /// message. /// private void BindParameters() { if (parameters.Count != 0) { Object[] parameterValues = new Object[parameters.Count]; for (Int32 i = 0; i < parameters.Count; i++) { // Do not quote strings, or escape existing quotes - this will be handled by the backend. // DBNull or null values are returned as null. // TODO: Would it be better to remove this null special handling out of ConvertToBackend?? parameterValues[i] = parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, true); } bind.ParameterValues = parameterValues; } Connector.Bind(bind); Connector.Mediator.RequireReadyForQuery = false; Connector.Flush(); connector.CheckErrorsAndNotifications(); } /// /// Executes the query, and returns the first column of the first row /// in the result set returned by the query. Extra columns or rows are ignored. /// /// The first column of the first row in the result set, /// or a null reference if the result set is empty. public Object ExecuteScalar() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteScalar"); /*if ((type == CommandType.Text) || (type == CommandType.StoredProcedure)) if (parse == null) connection.Query(this); else { BindParameters(); connection.Execute(new NpgsqlExecute(bind.PortalName, 0)); } else throw new NotImplementedException(resman.GetString("Exception_CommandTypeTableDirect")); */ ExecuteCommand(); // Now get the results. // Only the first column of the first row must be returned. // Get ResultSets. ArrayList resultSets = Connector.Mediator.ResultSets; // First data is the RowDescription object. // Check all resultsets as insert commands could have been sent along // with resultset queries. The insert commands return null and and some queries // may return empty resultsets, so, if we find one of these, skip to next resultset. // If no resultset is found, return null as per specification. NpgsqlAsciiRow ascii_row = null; foreach( NpgsqlResultSet nrs in resultSets ) { if( (nrs != null) && (nrs.Count > 0) ) { ascii_row = (NpgsqlAsciiRow) nrs[0]; return ascii_row[0]; } } return null; } /// /// Creates a prepared version of the command on a PostgreSQL server. /// public void Prepare() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Prepare"); // Check the connection state. CheckConnectionState(); if (! Connector.SupportsPrepare) { return; // Do nothing. } if (connector.BackendProtocolVersion == ProtocolVersion.Version2) { NpgsqlCommand command = new NpgsqlCommand(GetPrepareCommandText(), connector ); command.ExecuteNonQuery(); } else { // Use the extended query parsing... //planName = "NpgsqlPlan" + Connector.NextPlanIndex(); planName = Connector.NextPlanName(); String portalName = Connector.NextPortalName(); parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] {}); Connector.Parse(parse); Connector.Mediator.RequireReadyForQuery = false; Connector.Flush(); // Check for errors and/or notifications and do the Right Thing. connector.CheckErrorsAndNotifications(); bind = new NpgsqlBind("", planName, new Int16[] {0}, null, new Int16[] {0}); } } /* /// /// Releases the resources used by the NpgsqlCommand. /// protected override void Dispose (bool disposing) { if (disposing) { // Only if explicitly calling Close or dispose we still have access to // managed resources. NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose"); if (connection != null) { connection.Dispose(); } base.Dispose(disposing); } }*/ /// /// This method checks the connection state to see if the connection /// is set or it is open. If one of this conditions is not met, throws /// an InvalidOperationException /// private void CheckConnectionState() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CheckConnectionState"); // Check the connection state. if (Connector == null || Connector.State != ConnectionState.Open) { throw new InvalidOperationException(resman.GetString("Exception_ConnectionNotOpen")); } } /// /// This method substitutes the Parameters, if exist, in the command /// to their actual values. /// The parameter name format is :ParameterName. /// /// A version of CommandText with the Parameters inserted. internal String GetCommandText() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetCommandText"); if (planName == String.Empty) return GetClearCommandText(); else return GetPreparedCommandText(); } private String GetClearCommandText() { if (NpgsqlEventLog.Level == LogLevel.Debug) NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetClearCommandText"); Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default. Boolean functionReturnsRecord = false; // Functions don't return record by default. Boolean functionReturnsRefcursor = false; // Functions don't return refcursor by default. String result = text; if (type == CommandType.StoredProcedure) { functionReturnsRecord = CheckFunctionReturnRecord(); functionReturnsRefcursor = CheckFunctionReturnRefcursor(); // 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. if (!result.Trim().EndsWith(")")) { addProcedureParenthesis = true; result += "("; } if (Connector.SupportsPrepare) result = "select * from " + result; // This syntax is only available in 7.3+ as well SupportsPrepare. else result = "select " + result; //Only a single result return supported. 7.2 and earlier. } else if (type == CommandType.TableDirect) return "select * from " + result; // There is no parameter support on table direct. if (parameters == null || parameters.Count == 0) { if (addProcedureParenthesis) result += ")"; // If function returns ref cursor just process refcursor-result function call // and return command which will be used to return data from refcursor. if (functionReturnsRefcursor) return ProcessRefcursorFunctionReturn(result); if (functionReturnsRecord) result = AddFunctionReturnsRecordSupport(result); result = AddSingleRowBehaviorSupport(result); return result; } // Get parameters in query string to translate them to their actual values. // This regular expression gets all the parameters in format :param or @param // and everythingelse. // This is only needed if query string has parameters. Else, just append the // parameter values in order they were put in parameter collection. // If parenthesis don't need to be added, they were added by user with parameter names. Replace them. if (!addProcedureParenthesis) { Regex a = new Regex(@"(:[\w]*)|(@[\w]*)|(.)"); //CheckParameters(); StringBuilder sb = new StringBuilder(); for ( Match m = a.Match(result); m.Success; m = m.NextMatch() ) { String s = m.Groups[0].ToString(); if ((s.StartsWith(":") || s.StartsWith("@")) && Parameters.Contains(s)) { // It's a parameter. Lets handle it. NpgsqlParameter p = Parameters[s]; if ((p.Direction == ParameterDirection.Input) || (p.Direction == ParameterDirection.InputOutput)) { // FIXME DEBUG ONLY // adding the '::' on the end of a parameter is a highly // questionable practice, but it is great for debugging! sb.Append(p.TypeInfo.ConvertToBackend(p.Value, false)); // Only add data type info if we are calling an stored procedure. if (type == CommandType.StoredProcedure) { sb.Append("::"); sb.Append(p.TypeInfo.Name); if (p.TypeInfo.UseSize && (p.Size > 0)) sb.Append("(").Append(p.Size).Append(")"); } } } else sb.Append(s); } result = sb.ToString(); } else { for (Int32 i = 0; i < parameters.Count; i++) { NpgsqlParameter Param = parameters[i]; if ((Param.Direction == ParameterDirection.Input) || (Param.Direction == ParameterDirection.InputOutput)) result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + "::" + Param.TypeInfo.Name + ","; } // Remove a trailing comma added from parameter handling above. If any. // Maybe there are only output parameters. If so, there will be no comma. if (result.EndsWith(",")) result = result.Remove(result.Length - 1, 1); result += ")"; } if (functionReturnsRecord) result = AddFunctionReturnsRecordSupport(result); // If function returns ref cursor just process refcursor-result function call // and return command which will be used to return data from refcursor. if (functionReturnsRefcursor) return ProcessRefcursorFunctionReturn(result); return AddSingleRowBehaviorSupport(result); } private Boolean CheckFunctionReturnRecord() { if (Parameters.Count == 0) return false; String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = 'record' ) and proargtypes='{0}' and proname='{1}';"; StringBuilder parameterTypes = new StringBuilder(""); foreach(NpgsqlParameter p in Parameters) { if ((p.Direction == ParameterDirection.Input) || (p.Direction == ParameterDirection.InputOutput)) { parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " "); } } NpgsqlCommand c = new NpgsqlCommand(String.Format(returnRecordQuery, parameterTypes.ToString(), CommandText), Connection); Boolean ret = (Boolean) c.ExecuteScalar(); // reset any responses just before getting new ones connector.Mediator.ResetResponses(); return ret; } private Boolean CheckFunctionReturnRefcursor() { String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = 'refcursor' ) and proargtypes='{0}' and proname='{1}';"; StringBuilder parameterTypes = new StringBuilder(""); foreach(NpgsqlParameter p in Parameters) { if ((p.Direction == ParameterDirection.Input) || (p.Direction == ParameterDirection.InputOutput)) { parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " "); } } NpgsqlCommand c = new NpgsqlCommand(String.Format(returnRecordQuery, parameterTypes.ToString(), CommandText), Connection); Boolean ret = (Boolean) c.ExecuteScalar(); // reset any responses just before getting new ones connector.Mediator.ResetResponses(); return ret; } private String AddFunctionReturnsRecordSupport(String OriginalResult) { StringBuilder sb = new StringBuilder(OriginalResult); sb.Append(" as ("); foreach(NpgsqlParameter p in Parameters) { if ((p.Direction == ParameterDirection.Output) || (p.Direction == ParameterDirection.InputOutput)) { sb.Append(String.Format("{0} {1}, ", p.ParameterName.Substring(1), p.TypeInfo.Name)); } } String result = sb.ToString(); result = result.Remove(result.Length - 2, 1); result += ")"; return result; } /// /// This methods takes a string with a function call witch returns a refcursor or a set of /// refcursor. It will return the names of the open cursors/portals which will hold /// results. In turn, it returns the string which is needed to get the data of this cursors /// in form of one resultset for each cursor open. This way, clients don't need to do anything /// else besides calling function normally to get results in this way. /// private String ProcessRefcursorFunctionReturn(String FunctionCall) { NpgsqlCommand c = new NpgsqlCommand(FunctionCall, Connection); NpgsqlDataReader dr = c.ExecuteReader(); StringBuilder sb = new StringBuilder(); while (dr.Read()) { sb.Append("fetch all from \"").Append(dr.GetString(0)).Append("\";"); } sb.Append(";"); // Just in case there is no response from refcursor function return. // reset any responses just before getting new ones connector.Mediator.ResetResponses(); return sb.ToString(); } private String GetPreparedCommandText() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPreparedCommandText"); if (parameters.Count == 0) return "execute " + planName; StringBuilder result = new StringBuilder("execute " + planName + '('); for (Int32 i = 0; i < parameters.Count; i++) { result.Append(parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false) + ','); } result = result.Remove(result.Length - 1, 1); result.Append(')'); return result.ToString(); } private String GetParseCommandText() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetParseCommandText"); Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default. String parseCommand = text; if (type == CommandType.StoredProcedure) { // 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. if (!parseCommand.Trim().EndsWith(")")) { addProcedureParenthesis = true; parseCommand += "("; } parseCommand = "select * from " + parseCommand; // This syntax is only available in 7.3+ as well SupportsPrepare. } else if (type == CommandType.TableDirect) return "select * from " + parseCommand; // There is no parameter support on TableDirect. if (parameters.Count > 0) { // The ReplaceParameterValue below, also checks if the parameter is present. String parameterName; Int32 i; for (i = 0; i < parameters.Count; i++) { if ((parameters[i].Direction == ParameterDirection.Input) || (parameters[i].Direction == ParameterDirection.InputOutput)) { if (!addProcedureParenthesis) { //result = result.Replace(":" + parameterName, parameters[i].Value.ToString()); parameterName = parameters[i].ParameterName; //textCommand = textCommand.Replace(':' + parameterName, "$" + (i+1)); parseCommand = ReplaceParameterValue(parseCommand, parameterName, "$" + (i+1) + "::" + parameters[i].TypeInfo.Name); } else parseCommand += "$" + (i+1) + "::" + parameters[i].TypeInfo.Name; } } } if (addProcedureParenthesis) return parseCommand + ")"; else return parseCommand; } private String GetPrepareCommandText() { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPrepareCommandText"); Boolean addProcedureParenthesis = false; // Do not add procedure parenthesis by default. planName = Connector.NextPlanName(); StringBuilder command = new StringBuilder("prepare " + planName); String textCommand = text; if (type == CommandType.StoredProcedure) { // 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. if (!textCommand.Trim().EndsWith(")")) { addProcedureParenthesis = true; textCommand += "("; } textCommand = "select * from " + textCommand; } else if (type == CommandType.TableDirect) return "select * from " + textCommand; // There is no parameter support on TableDirect. if (parameters.Count > 0) { // The ReplaceParameterValue below, also checks if the parameter is present. String parameterName; Int32 i; for (i = 0; i < parameters.Count; i++) { if ((parameters[i].Direction == ParameterDirection.Input) || (parameters[i].Direction == ParameterDirection.InputOutput)) { if (!addProcedureParenthesis) { //result = result.Replace(":" + parameterName, parameters[i].Value.ToString()); parameterName = parameters[i].ParameterName; // The space in front of '$' fixes a parsing problem in 7.3 server // which gives errors of operator when finding the caracters '=$' in // prepare text textCommand = ReplaceParameterValue(textCommand, parameterName, " $" + (i+1)); } else textCommand += " $" + (i+1); } } //[TODO] Check if there is any missing parameters in the query. // For while, an error is thrown saying about the ':' char. command.Append('('); for (i = 0; i < parameters.Count; i++) { // command.Append(NpgsqlTypesHelper.GetDefaultTypeInfo(parameters[i].DbType)); command.Append(parameters[i].TypeInfo.Name); command.Append(','); } command = command.Remove(command.Length - 1, 1); command.Append(')'); } if (addProcedureParenthesis) textCommand += ")"; command.Append(" as "); command.Append(textCommand); return command.ToString(); } private String ReplaceParameterValue(String result, String parameterName, String paramVal) { Int32 resLen = result.Length; Int32 paramStart = result.IndexOf(parameterName); Int32 paramLen = parameterName.Length; Int32 paramEnd = paramStart + paramLen; Boolean found = false; while(paramStart > -1) { if((resLen > paramEnd) && !Char.IsLetterOrDigit(result, paramEnd)) { result = result.Substring(0, paramStart) + paramVal + result.Substring(paramEnd); found = true; } else if(resLen == paramEnd) { result = result.Substring(0, paramStart)+ paramVal; found = true; } else break; resLen = result.Length; paramStart = result.IndexOf(parameterName, paramStart); paramEnd = paramStart + paramLen; }//while if(!found) throw new IndexOutOfRangeException (String.Format(resman.GetString("Exception_ParamNotInQuery"), parameterName)); return result; }//ReplaceParameterValue private String AddSingleRowBehaviorSupport(String ResultCommandText) { ResultCommandText = ResultCommandText.Trim(); if ((commandBehavior & CommandBehavior.SingleRow) > 0) { if (ResultCommandText.EndsWith(";")) ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1); ResultCommandText += " limit 1;"; } return ResultCommandText; } private void ExecuteCommand() { // Check the connection state first. CheckConnectionState(); // reset any responses just before getting new ones connector.Mediator.ResetResponses(); if (parse == null) { Connector.Query(this); // Check for errors and/or notifications and do the Right Thing. connector.CheckErrorsAndNotifications(); } else { try { BindParameters(); connector.Execute(new NpgsqlExecute(bind.PortalName, 0)); // Check for errors and/or notifications and do the Right Thing. connector.CheckErrorsAndNotifications(); } finally { // As per documentation: // "[...] When an error is detected while processing any extended-query message, // the backend issues ErrorResponse, then reads and discards messages until a // Sync is reached, then issues ReadyForQuery and returns to normal message processing.[...]" // So, send a sync command if we get any problems. connector.Sync(); } } } } }