// 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) { if (Parameters.Count > 0) functionReturnsRecord = CheckFunctionReturn("record"); functionReturnsRefcursor = CheckFunctionReturn("refcursor"); // 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. if ((!result.Trim().EndsWith(")")) && (!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]*)|(.)", RegexOptions.Singleline); //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 CheckFunctionReturn(String ReturnType) { String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = :typename ) and proargtypes=:proargtypes and proname=:proname;"; 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(returnRecordQuery, Connection); c.Parameters.Add(new NpgsqlParameter("typename", NpgsqlDbType.Text)); c.Parameters.Add(new NpgsqlParameter("proargtypes", NpgsqlDbType.Text)); c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text)); c.Parameters[0].Value = ReturnType; c.Parameters[1].Value = parameterTypes.ToString(); c.Parameters[2].Value = CommandText; 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(); } } } } }