// 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
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Text;
using System.Text.RegularExpressions;
using NpgsqlTypes;
#if WITHDESIGN
#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 partial class NpgsqlCommand : DbCommand, ICloneable
{
private enum PrepareStatus
{
NotPrepared,
NeedsPrepare,
Prepared
}
// Logging related values
private static readonly String CLASSNAME = MethodBase.GetCurrentMethod().DeclaringType.Name;
private static readonly ResourceManager resman = new ResourceManager(MethodBase.GetCurrentMethod().DeclaringType);
private NpgsqlConnection connection;
private NpgsqlConnector m_Connector; //renamed to account for hiding it in a local function
//if all locals were named with this prefix, it would solve LOTS of issues.
private NpgsqlTransaction transaction;
private String commandText;
private Int32 timeout;
private CommandType commandType;
private readonly NpgsqlParameterCollection parameters = new NpgsqlParameterCollection();
private String planName;
private Boolean designTimeVisible;
private PrepareStatus prepared = PrepareStatus.NotPrepared;
private byte[] preparedCommandText = null;
private NpgsqlBind bind = null;
private NpgsqlExecute execute = null;
private NpgsqlRowDescription currentRowDescription = null;
private Int64 lastInsertedOID = 0;
// locals about function support so we don`t need to check it everytime a function is called.
private Boolean functionChecksDone = false;
private Boolean functionNeedsColumnListDefinition = false; // Functions don't return record by default.
private Boolean commandTimeoutSet = false;
private UpdateRowSource updateRowSource = UpdateRowSource.Both;
private static readonly Array ParamNameCharTable;
// Constructors
static NpgsqlCommand()
{
ParamNameCharTable = BuildParameterNameCharacterTable();
}
private static Array BuildParameterNameCharacterTable()
{
Array paramNameCharTable;
// Table has lower bound of (int)'.';
paramNameCharTable = Array.CreateInstance(typeof(byte), new int[] {'z' - '.' + 1}, new int[] {'.'});
paramNameCharTable.SetValue((byte)'.', (int)'.');
for (int i = '0' ; i <= '9' ; i++)
{
paramNameCharTable.SetValue((byte)i, i);
}
for (int i = 'A' ; i <= 'Z' ; i++)
{
paramNameCharTable.SetValue((byte)i, i);
}
paramNameCharTable.SetValue((byte)'_', (int)'_');
for (int i = 'a' ; i <= 'z' ; i++)
{
paramNameCharTable.SetValue((byte)i, i);
}
return paramNameCharTable;
}
///
/// 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;
commandText = cmdText;
this.connection = connection;
if (this.connection != null)
{
this.m_Connector = connection.Connector;
if (m_Connector != null && m_Connector.AlwaysPrepare)
{
CommandTimeout = m_Connector.DefaultCommandTimeout;
prepared = PrepareStatus.NeedsPrepare;
}
}
commandType = CommandType.Text;
this.Transaction = transaction;
SetCommandTimeout();
}
///
/// Used to execute internal commands.
///
internal NpgsqlCommand(String cmdText, NpgsqlConnector connector, int CommandTimeout = 20)
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
planName = String.Empty;
commandText = cmdText;
this.m_Connector = connector;
this.CommandTimeout = CommandTimeout;
commandType = CommandType.Text;
// Removed this setting. It was causing too much problem.
// Do internal commands really need different timeout setting?
// Internal commands aren't affected by command timeout value provided by user.
// 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 override String CommandText
{
get { return commandText; }
set
{
// [TODO] Validate commandtext.
NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandText", value);
commandText = value;
UnPrepare();
functionChecksDone = false;
}
}
///
/// 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 override Int32 CommandTimeout
{
get { return timeout; }
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("value", resman.GetString("Exception_CommandTimeoutLessZero"));
}
timeout = value;
NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandTimeout", value);
commandTimeoutSet = true;
}
}
///
/// 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 override CommandType CommandType
{
get { return commandType; }
set
{
commandType = value;
NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandType", value);
}
}
///
/// DB connection.
///
protected override DbConnection DbConnection
{
get { return Connection; }
set
{
Connection = (NpgsqlConnection)value;
NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "DbConnection", 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 new 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;
// All this checking needs revising. It should be simpler.
// This this.Connector != null check was added to remove the nullreferenceexception in case
// of the previous connection has been closed which makes Connector null and so the last check would fail.
// See bug 1000581 for more details.
if (this.transaction != null && this.connection != null && this.Connector != null && this.Connector.Transaction != null)
{
throw new InvalidOperationException(resman.GetString("Exception_SetConnectionInTransaction"));
}
this.connection = value;
Transaction = null;
if (this.connection != null)
{
m_Connector = this.connection.Connector;
if (m_Connector != null && m_Connector.AlwaysPrepare)
{
prepared = PrepareStatus.NeedsPrepare;
}
}
SetCommandTimeout();
NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Connection", value);
}
}
internal NpgsqlConnector Connector
{
get
{
if (this.connection != null)
{
m_Connector = this.connection.Connector;
}
return m_Connector;
}
}
internal Type[] ExpectedTypes { get; set; }
///
/// DB parameter collection.
///
protected override DbParameterCollection DbParameterCollection
{
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 new NpgsqlParameterCollection Parameters
{
get
{
NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Parameters");
return parameters;
}
}
///
/// DB transaction.
///
protected override DbTransaction DbTransaction
{
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 new 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 = 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 override UpdateRowSource UpdatedRowSource
{
get
{
NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "UpdatedRowSource");
return updateRowSource;
}
set
{
switch (value)
{
// validate value (required based on base type contract)
case UpdateRowSource.None:
case UpdateRowSource.OutputParameters:
case UpdateRowSource.FirstReturnedRecord:
case UpdateRowSource.Both:
updateRowSource = value;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
///
/// 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.
///
public Int64 LastInsertedOID
{
get { return lastInsertedOID; }
}
///
/// Attempts to cancel the execution of a NpgsqlCommand.
///
/// This Method isn't implemented yet.
public override void Cancel()
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Cancel");
try
{
// get copy for thread safety of null test
NpgsqlConnector connector = Connector;
if (connector != null)
{
connector.CancelRequest();
}
}
catch (IOException)
{
Connection.ClearPool();
}
catch (NpgsqlException)
{
// Cancel documentation says the Cancel doesn't throw on failure
}
}
///
/// Create a new command based on this one.
///
/// A new NpgsqlCommand object.
Object ICloneable.Clone()
{
return Clone();
}
///
/// Create a new command based on this one.
///
/// A new NpgsqlCommand object.
public NpgsqlCommand Clone()
{
// TODO: Add consistency checks.
NpgsqlCommand clone = new NpgsqlCommand(CommandText, Connection, Transaction);
clone.CommandTimeout = CommandTimeout;
clone.CommandType = CommandType;
clone.DesignTimeVisible = DesignTimeVisible;
if (ExpectedTypes != null)
{
clone.ExpectedTypes = (Type[])ExpectedTypes.Clone();
}
foreach (NpgsqlParameter parameter in Parameters)
{
clone.Parameters.Add(parameter.Clone());
}
return clone;
}
///
/// Creates a new instance of an DbParameter object.
///
/// An DbParameter object.
protected override DbParameter CreateDbParameter()
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateDbParameter");
return CreateParameter();
}
///
/// Creates a new instance of a NpgsqlParameter object.
///
/// A NpgsqlParameter object.
public new NpgsqlParameter CreateParameter()
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateParameter");
return new NpgsqlParameter();
}
/*
///
/// 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);
}
}*/
private void SetCommandTimeout()
{
if (commandTimeoutSet)
return;
if (Connection != null)
{
timeout = Connection.CommandTimeout;
}
else
{
timeout = (int)NpgsqlConnectionStringBuilder.GetDefaultValue(Keywords.CommandTimeout);
}
}
internal NpgsqlException ClearPoolAndCreateException(Exception e)
{
Connection.ClearPool();
return new NpgsqlException(resman.GetString("Exception_ConnectionBroken"), e);
}
///
/// Design time visible.
///
public override bool DesignTimeVisible
{
get { return designTimeVisible; }
set { designTimeVisible = value; }
}
}
}